Skip to content

Commit 266234c

Browse files
committed
New miniphase: ExpandSAMs
The phase replaces SAM closures with anonymous classes when necessary.
1 parent 66e9ea8 commit 266234c

File tree

7 files changed

+169
-13
lines changed

7 files changed

+169
-13
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Compiler {
4545
new ElimRepeated,
4646
new NormalizeFlags,
4747
new ExtensionMethods,
48+
new ExpandSAMs,
4849
new TailRec),
4950
List(new PatternMatcher,
5051
new ExplicitOuter,

src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ object StdNames {
416416
val isArray: N = "isArray"
417417
val isDefined: N = "isDefined"
418418
val isDefinedAt: N = "isDefinedAt"
419+
val isDefinedAtImpl: N = "$isDefinedAt"
419420
val isEmpty: N = "isEmpty"
420421
val isInstanceOf_ : N = "isInstanceOf"
421422
val java: N = "java"

src/dotty/tools/dotc/core/Types.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,11 @@ object Types {
545545
(name, buf) => buf ++= member(name).altsWith(x => x.isClass))
546546
}
547547

548+
final def fields(implicit ctx: Context): Seq[SingleDenotation] = track("fields") {
549+
memberDenots(fieldFilter,
550+
(name, buf) => buf ++= member(name).altsWith(x => !x.is(Method)))
551+
}
552+
548553
/** The set of members of this type having at least one of `requiredFlags` but none of `excludedFlags` set */
549554
final def membersBasedOnFlags(requiredFlags: FlagSet, excludedFlags: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("implicitMembers") {
550555
memberDenots(takeAllFilter,
@@ -3047,6 +3052,11 @@ object Types {
30473052
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = name.isTypeName
30483053
}
30493054

3055+
object fieldFilter extends NameFilter {
3056+
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
3057+
name.isTermName && (pre member name).hasAltWith(!_.symbol.is(Method))
3058+
}
3059+
30503060
object takeAllFilter extends NameFilter {
30513061
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true
30523062
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import Contexts._, Symbols._, Types._, Flags._, Decorators._, StdNames._, Constants._
6+
import SymDenotations.SymDenotation
7+
import TreeTransforms._
8+
import ast.untpd
9+
import ast.Trees._
10+
11+
/** Expand SAM closures that cannot be represented by the JVM to anonymous classes.
12+
* These fall into three categories
13+
*
14+
* 1. Partial function closures, we need to generate a isDefinedAt method for these.
15+
* 2. Closures implementaing non-trait classes.
16+
* 3. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be
17+
* (1) superaccessors, (2) outer references, (3) accessors for fields.
18+
*/
19+
class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
20+
override def phaseName = "expandSAMs"
21+
22+
import ast.tpd._
23+
24+
def noJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
25+
!cls.is(Trait) || ExplicitOuter.needsOuterIfReferenced(cls) || cls.typeRef.fields.nonEmpty
26+
27+
override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match {
28+
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
29+
tpt.tpe match {
30+
case NoType => tree // it's a plain function
31+
case tpe @ SAMType(_) if !noJvmSam(tpe.classSymbol.asClass) =>
32+
if (tpe isRef defn.PartialFunctionClass) toPartialFunction(tree)
33+
else tree
34+
case tpe =>
35+
cpy.Block(tree)(stats,
36+
AnonClass(tpe, fn.symbol.asTerm :: Nil, nme.apply :: Nil))
37+
}
38+
case _ =>
39+
tree
40+
}
41+
42+
private def toPartialFunction(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = {
43+
val Block(
44+
(applyDef @ DefDef(nme.ANON_FUN, Nil, List(params), _, _)) :: Nil,
45+
Closure(_, _, tpt)) = tree
46+
val List(param) = params
47+
// Dotty problem: If we match instead List(List(param)) directly,
48+
// we get:
49+
// Exception in thread "main" java.lang.AssertionError: assertion failed: self instantiation of (A?
50+
// ...
51+
// at scala.Predef$.assert(Predef.scala:165)
52+
// at dotty.tools.dotc.core.Types$TypeVar.instantiateWith(Types.scala:2308)
53+
// at dotty.tools.dotc.core.Types$TypeVar.instantiate(Types.scala:2363)
54+
// at dotty.tools.dotc.typer.Inferencing$$anonfun$interpolate$1$1$$anonfun$apply$mcV$sp$4.apply(Inferencing.scala:198)
55+
// at dotty.tools.dotc.typer.Inferencing$$anonfun$interpolate$1$1$$anonfun$apply$mcV$sp$4.apply(Inferencing.scala:195)
56+
//
57+
// I think it has to do with the double :: (or List) pattern to extract `param`.
58+
59+
val applyRhs: Tree = applyDef.rhs
60+
val applyFn = applyDef.symbol.asTerm
61+
62+
val MethodType(paramNames, paramTypes) = applyFn.info
63+
val isDefinedAtFn = applyFn.copy(
64+
name = nme.isDefinedAtImpl,
65+
flags = Synthetic | Method,
66+
info = MethodType(paramNames, paramTypes, defn.BooleanType)).asTerm
67+
val tru = Literal(Constant(true))
68+
def isDefinedAtRhs(paramRefss: List[List[Tree]]) = applyRhs match {
69+
case Match(selector, cases) =>
70+
assert(selector.symbol == param.symbol)
71+
val paramRef = paramRefss.head.head
72+
// Again, the alternative
73+
// val List(List(paramRef)) = paramRefs
74+
// fails with a similar self instantiation error
75+
def translateCase(cdef: CaseDef): CaseDef =
76+
cpy.CaseDef(cdef)(body = tru).changeOwner(applyFn, isDefinedAtFn)
77+
val defaultSym = ctx.newSymbol(isDefinedAtFn, nme.WILDCARD, Synthetic, selector.tpe.widen)
78+
val defaultCase =
79+
CaseDef(
80+
Bind(defaultSym, untpd.Ident(nme.WILDCARD).withType(selector.tpe.widen)),
81+
EmptyTree,
82+
Literal(Constant(false)))
83+
cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase)
84+
case _ =>
85+
tru
86+
}
87+
val isDefinedAtDef = transformFollowingDeep(DefDef(isDefinedAtFn, isDefinedAtRhs(_)))
88+
val anonCls = AnonClass(tpt.tpe, List(applyFn, isDefinedAtFn), List(nme.apply, nme.isDefinedAt))
89+
cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls)
90+
}
91+
}

test/dotc/tests.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class tests extends CompilerTest {
3535
val allowDoubleBindings = defaultOptions diff List("-Yno-double-bindings")
3636

3737
val testsDir = "./tests/"
38-
val posDir = testsDir + "pos/"
38+
val posDir = testsDir + "pos/"
3939
val posSpecialDir = testsDir + "pos-special/"
4040
val negDir = testsDir + "neg/"
4141
val newDir = testsDir + "new/"
@@ -50,7 +50,7 @@ class tests extends CompilerTest {
5050
@Test def pickle_ast = compileDir(dotcDir, "ast", testPickling)
5151

5252
//@Test def pickle_core = compileDir(dotcDir, "core", testPickling, xerrors = 2) // two spurious comparison errors in Types and TypeOps
53-
53+
5454
@Test def pos_t2168_pat = compileFile(posDir, "t2168", twice)
5555
@Test def pos_erasure = compileFile(posDir, "erasure", twice)
5656
@Test def pos_Coder() = compileFile(posDir, "Coder", twice)
@@ -108,9 +108,6 @@ class tests extends CompilerTest {
108108
@Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 6)
109109
@Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1)
110110
@Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12)
111-
@Test def neg_sam = compileFile(negDir, "sammy_poly", xerrors = 1)
112-
// TODO: this test file doesn't exist (anymore?), remove?
113-
// @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1)
114111

115112
val negTailcallDir = negDir + "tailcall/"
116113
@Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b", xerrors = 6)
@@ -119,7 +116,7 @@ class tests extends CompilerTest {
119116
@Test def neg_tailcall = compileFile(negTailcallDir, "tailrec", xerrors = 7)
120117
@Test def neg_tailcall2 = compileFile(negTailcallDir, "tailrec-2", xerrors = 2)
121118
@Test def neg_tailcall3 = compileFile(negTailcallDir, "tailrec-3", xerrors = 2)
122-
119+
123120
@Test def neg_t1279a = compileFile(negDir, "t1279a", xerrors = 1)
124121
@Test def neg_t1843_variances = compileFile(negDir, "t1843-variances", xerrors = 1)
125122
@Test def neg_t2660_ambi = compileFile(negDir, "t2660", xerrors = 2)

tests/neg/sammy_poly.scala

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/pos/sams.scala

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
object test {
2+
3+
trait X { def foo(x: Int): Int; def bar = foo(2) }
4+
5+
val x: X = (x: Int) => 2 // should be a closure
6+
7+
trait T {
8+
var f = 2
9+
def foo(x: Int): Int
10+
}
11+
12+
val t: T = (x: Int) => 2 // needs to be an anonymous class because of defined field
13+
14+
trait U extends T
15+
16+
val u: U = (x: Int) => 2 // needs to be an anonymous class because of inherited field
17+
18+
trait Y extends X {
19+
def baz = super.bar
20+
}
21+
22+
val y: Y = (x: Int) => 2 // needs to be an anonymous class because of super accessor
23+
24+
abstract class C {
25+
def foo(x: Int): Int
26+
27+
trait I { def foo(x: Int): Int }
28+
29+
}
30+
31+
val c: C = (x: Int) => 2 // needs to be an anonymous class because C is not a trait
32+
33+
val ci: c.I = (x: Int) => 2 // needs to be an anonymous class because it needs an outer pointer
34+
35+
36+
val pf: PartialFunction[Int, Int] = {
37+
case 1 => 1
38+
case 2 => 2
39+
}
40+
41+
val qf: PartialFunction[(Int, String), Int] = {
42+
case (1, "abc") => 1
43+
case _ => 2
44+
}
45+
46+
val rf: PartialFunction[(Int, AnyRef), Int] = {
47+
case (_: Int, _: String) => 1
48+
case _ => 2
49+
}
50+
51+
val sf: PartialFunction[Any, Int] = {
52+
case x: String if x == "abc" => 1
53+
}
54+
}
55+
56+
// From: neg/sammy_poly
57+
// synthesizeSAMFunction where the sam type is not fully defined
58+
class T {
59+
trait F[T, U] { def apply(x: T): U }
60+
// this is an inner trait, that will recieve an abstract $outer pointer. Not a SAM.
61+
def app[T, U](x: T)(f: F[T, U]): U = f(x)
62+
app(1)(x => List(x))
63+
}

0 commit comments

Comments
 (0)