Skip to content

Commit 9daeaa0

Browse files
committed
Fix #6375: Add miniphase for simple beta reductions
1 parent f548a18 commit 9daeaa0

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class Compiler {
5959
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes
6060
new CookComments, // Cook the comments: expand variables, doc, etc.
6161
new CheckStatic, // Check restrictions that apply to @static members
62+
new BetaReduce, // Reduce closure applications
6263
new init.Checker) :: // Check initialization of objects
6364
List(new CompleteJavaEnums, // Fill in constructors for Java enums
6465
new ElimRepeated, // Rewrite vararg parameters and arguments
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import core._
6+
import MegaPhase._
7+
import Symbols._, Contexts._, Types._, Decorators._
8+
import StdNames.nme
9+
import ast.Trees._
10+
import ast.TreeTypeMap
11+
12+
/** Rewrite an application
13+
*
14+
* (((x1, ..., xn) => b): T)(y1, ..., yn)
15+
*
16+
* where
17+
*
18+
* - all yi are pure references without a prefix
19+
* - the closure can also be contextual or erased, but cannot be a SAM type
20+
* _ the type ascription ...: T is optional
21+
*
22+
* to
23+
*
24+
* [xi := yi]b
25+
*
26+
* This is more limited than beta reduction in inlining since it only works for simple variables `yi`.
27+
* It is more general since it also works for type-ascripted closures.
28+
*
29+
* A typical use case is eliminating redundant closures for blackbox macros that
30+
* return context functions. See i6375.scala.
31+
*/
32+
class BetaReduce extends MiniPhase:
33+
import ast.tpd._
34+
35+
def phaseName: String = "betaReduce"
36+
37+
override def transformApply(app: Apply)(using ctx: Context): Tree = app.fun match
38+
case Select(fn, nme.apply) if defn.isFunctionType(fn.tpe) =>
39+
val app1 = betaReduce(app, fn, app.args)
40+
if app1 ne app then ctx.log(i"beta reduce $app -> $app1")
41+
app1
42+
case _ =>
43+
app
44+
45+
private def betaReduce(tree: Apply, fn: Tree, args: List[Tree])(using ctx: Context): Tree =
46+
fn match
47+
case Typed(expr, _) => betaReduce(tree, expr, args)
48+
case Block(Nil, expr) => betaReduce(tree, expr, args)
49+
case Block((anonFun: DefDef) :: Nil, closure: Closure) =>
50+
val argSyms =
51+
for arg <- args yield
52+
arg.tpe.dealias match
53+
case ref @ TermRef(NoPrefix, _) if isPurePath(arg) => ref.symbol
54+
case _ => NoSymbol
55+
val vparams = anonFun.vparamss.head
56+
if argSyms.forall(_.exists) && argSyms.hasSameLengthAs(vparams) then
57+
TreeTypeMap(
58+
oldOwners = anonFun.symbol :: Nil,
59+
newOwners = ctx.owner :: Nil,
60+
substFrom = vparams.map(_.symbol),
61+
substTo = argSyms).transform(anonFun.rhs)
62+
else tree
63+
case _ => tree

tests/pos/i6375.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* In the following we should never have two nested closures after phase betaReduce
2+
* The output of the program should instead look like this:
3+
4+
package <empty> {
5+
@scala.annotation.internal.SourceFile("i6375.scala") class Test() extends
6+
Object
7+
() {
8+
final given def given_Int: Int = 0
9+
@scala.annotation.internal.ContextResultCount(1) def f(): (Int) ?=> Boolean
10+
=
11+
{
12+
def $anonfun(using evidence$1: Int): Boolean = true
13+
closure($anonfun)
14+
}
15+
@scala.annotation.internal.ContextResultCount(1) inline def g():
16+
(Int) ?=> Boolean
17+
=
18+
{
19+
def $anonfun(using evidence$3: Int): Boolean = true
20+
closure($anonfun)
21+
}
22+
{
23+
{
24+
def $anonfun(using evidence$3: Int): Boolean = true
25+
closure($anonfun)
26+
}
27+
}.apply(this.given_Int)
28+
}
29+
}
30+
*/
31+
class Test:
32+
given Int = 0
33+
34+
def f(): Int ?=> Boolean = true : (Int ?=> Boolean)
35+
36+
inline def g(): Int ?=> Boolean = true
37+
g()
38+

0 commit comments

Comments
 (0)