Skip to content

Commit c4dba24

Browse files
committed
Merge pull request #509 from dotty-staging/add/expandSAMs
Expand SAM closures to anonymous classes if needed
2 parents 7c8693b + 349c436 commit c4dba24

17 files changed

+265
-37
lines changed

src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
647647
}
648648
def parentSymbols: List[Symbol] = toDenot(sym).info.parents.map(_.typeSymbol)
649649
def superClass: Symbol = {
650-
val t = toDenot(sym).superClass
650+
val t = toDenot(sym).asClass.superClass
651651
if (t.exists) t
652652
else if (sym is Flags.ModuleClass) {
653653
// workaround #371
@@ -712,7 +712,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
712712
* All interfaces implemented by a class, except for those inherited through the superclass.
713713
*
714714
*/
715-
def superInterfaces: List[Symbol] = decorateSymbol(sym).superInterfaces
715+
def superInterfaces: List[Symbol] = decorateSymbol(sym).directlyInheritedTraits
716716

717717
/**
718718
* True for module classes of package level objects. The backend will generate a mirror class for

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/Run.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class Run(comp: Compiler)(implicit ctx: Context) {
3333
compileSources(sources)
3434
} catch {
3535
case NonFatal(ex) =>
36-
println(s"exception occurred while compiling $units%, %")
36+
println(i"exception occurred while compiling $units%, %")
3737
throw ex
3838
}
3939

src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
210210
ta.assignType(untpd.TypeDef(sym.name, TypeTree(sym.info)), sym)
211211

212212
def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree], superArgs: List[Tree] = Nil)(implicit ctx: Context): TypeDef = {
213-
val firstParent :: otherParents = cls.info.parents
213+
val firstParentRef :: otherParentRefs = cls.info.parents
214+
val firstParent = cls.typeRef.baseTypeWithArgs(firstParentRef.symbol)
214215
val superRef =
215216
if (cls is Trait) TypeTree(firstParent)
216217
else {
@@ -225,7 +226,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
225226
val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(constr => isApplicable(constr.info))
226227
New(firstParent, constr.symbol.asTerm, superArgs)
227228
}
228-
val parents = superRef :: otherParents.map(TypeTree(_))
229+
val parents = superRef :: otherParentRefs.map(TypeTree(_))
229230

230231
val selfType =
231232
if (cls.classInfo.selfInfo ne NoType) ValDef(ctx.newSelfSym(cls))
@@ -244,6 +245,33 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
244245
ta.assignType(untpd.TypeDef(cls.name, impl), cls)
245246
}
246247

248+
/** An anonymous class
249+
*
250+
* new parents { forwarders }
251+
*
252+
* where `forwarders` contains forwarders for all functions in `fns`.
253+
* @param parents a non-empty list of class types
254+
* @param fns a non-empty of functions for which forwarders should be defined in the class.
255+
* The class has the same owner as the first function in `fns`.
256+
* Its position is the union of all functions in `fns`.
257+
*/
258+
def AnonClass(parents: List[Type], fns: List[TermSymbol], methNames: List[TermName])(implicit ctx: Context): Block = {
259+
val owner = fns.head.owner
260+
val parents1 =
261+
if (parents.head.classSymbol.is(Trait)) defn.ObjectClass.typeRef :: parents
262+
else parents
263+
val cls = ctx.newNormalizedClassSymbol(owner, tpnme.ANON_FUN, Synthetic, parents1,
264+
coord = fns.map(_.pos).reduceLeft(_ union _))
265+
val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil).entered
266+
def forwarder(fn: TermSymbol, name: TermName) = {
267+
val fwdMeth = fn.copy(cls, name, Synthetic | Method).entered.asTerm
268+
DefDef(fwdMeth, prefss => ref(fn).appliedToArgss(prefss))
269+
}
270+
val forwarders = (fns, methNames).zipped.map(forwarder)
271+
val cdef = ClassDef(cls, DefDef(constr), forwarders)
272+
Block(cdef :: Nil, New(cls.typeRef, Nil))
273+
}
274+
247275
// { <label> def while$(): Unit = if (cond) { body; while$() } ; while$() }
248276
def WhileDo(owner: Symbol, cond: Tree, body: List[Tree])(implicit ctx: Context): Tree = {
249277
val sym = ctx.newSymbol(owner, nme.WHILE_PREFIX, Flags.Label | Flags.Synthetic,
@@ -254,7 +282,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
254282
Block(List(DefDef(sym, rhs)), call)
255283
}
256284

257-
258285
def Import(expr: Tree, selectors: List[untpd.Tree])(implicit ctx: Context): Import =
259286
ta.assignType(untpd.Import(expr, selectors), ctx.newImportSymbol(ctx.owner, expr))
260287

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ object Flags {
332332
final val JavaStaticTerm = JavaStatic.toTermFlags
333333
final val JavaStaticType = JavaStatic.toTypeFlags
334334

335-
/** Trait is not an interface, but does not have fields or initialization code */
335+
/** Trait does not have fields or initialization code */
336336
final val NoInits = typeFlag(32, "<noInits>")
337337

338338
/** Variable is accessed from nested function. */

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ object StdNames {
418418
val isArray: N = "isArray"
419419
val isDefined: N = "isDefined"
420420
val isDefinedAt: N = "isDefinedAt"
421+
val isDefinedAtImpl: N = "$isDefinedAt"
421422
val isEmpty: N = "isEmpty"
422423
val isInstanceOf_ : N = "isInstanceOf"
423424
val java: N = "java"

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,30 @@ trait Symbols { this: Context =>
109109
ClassInfo(owner.thisType, _, parents, decls, selfInfo),
110110
privateWithin, coord, assocFile)
111111

112+
/** Same as `newCompleteClassSymbol` except that `parents` can be a list of arbitary
113+
* types which get normalized into type refs and parameter bindings.
114+
*/
115+
def newNormalizedClassSymbol(
116+
owner: Symbol,
117+
name: TypeName,
118+
flags: FlagSet,
119+
parentTypes: List[Type],
120+
decls: Scope = newScope,
121+
selfInfo: Type = NoType,
122+
privateWithin: Symbol = NoSymbol,
123+
coord: Coord = NoCoord,
124+
assocFile: AbstractFile = null): ClassSymbol = {
125+
def completer = new LazyType {
126+
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
127+
val cls = denot.asClass.classSymbol
128+
val decls = newScope
129+
val parentRefs: List[TypeRef] = normalizeToClassRefs(parentTypes, cls, decls)
130+
denot.info = ClassInfo(owner.thisType, cls, parentRefs, decls)
131+
}
132+
}
133+
newClassSymbol(owner, name, flags, completer, privateWithin, coord, assocFile)
134+
}
135+
112136
/** Create a module symbol with associated module class
113137
* from its non-info fields and a function producing the info
114138
* of the module class (this info may be lazy).

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,
@@ -3049,6 +3054,11 @@ object Types {
30493054
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = name.isTypeName
30503055
}
30513056

3057+
object fieldFilter extends NameFilter {
3058+
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
3059+
name.isTermName && (pre member name).hasAltWith(!_.symbol.is(Method))
3060+
}
3061+
30523062
object takeAllFilter extends NameFilter {
30533063
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true
30543064
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 SymUtils._
9+
import ast.untpd
10+
import ast.Trees._
11+
12+
/** Expand SAM closures that cannot be represented by the JVM as lambdas to anonymous classes.
13+
* These fall into five categories
14+
*
15+
* 1. Partial function closures, we need to generate a isDefinedAt method for these.
16+
* 2. Closures implementing non-trait classes.
17+
* 3. Closures implementing classes that inherit from a class other than Object
18+
* (a lambda cannot not be a run-time subtype of such a class)
19+
* 4. Closures that implement traits which run initialization code.
20+
* 5. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be
21+
* (1) superaccessors, (2) outer references, (3) accessors for fields.
22+
*/
23+
class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
24+
override def phaseName = "expandSAMs"
25+
26+
import ast.tpd._
27+
28+
def noJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
29+
!cls.is(Trait) ||
30+
cls.superClass != defn.ObjectClass ||
31+
!cls.is(NoInits) ||
32+
!cls.directlyInheritedTraits.forall(_.is(NoInits)) ||
33+
ExplicitOuter.needsOuterIfReferenced(cls) ||
34+
cls.typeRef.fields.nonEmpty // Superaccessors already show up as abstract methods here, so no test necessary
35+
36+
37+
override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match {
38+
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
39+
tpt.tpe match {
40+
case NoType => tree // it's a plain function
41+
case tpe @ SAMType(_) if !noJvmSam(tpe.classSymbol.asClass) =>
42+
if (tpe isRef defn.PartialFunctionClass) toPartialFunction(tree)
43+
else tree
44+
case tpe =>
45+
cpy.Block(tree)(stats,
46+
AnonClass(tpe :: Nil, fn.symbol.asTerm :: Nil, nme.apply :: Nil))
47+
}
48+
case _ =>
49+
tree
50+
}
51+
52+
private def toPartialFunction(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = {
53+
val Block(
54+
(applyDef @ DefDef(nme.ANON_FUN, Nil, List(List(param)), _, _)) :: Nil,
55+
Closure(_, _, tpt)) = tree
56+
val applyRhs: Tree = applyDef.rhs
57+
val applyFn = applyDef.symbol.asTerm
58+
59+
val MethodType(paramNames, paramTypes) = applyFn.info
60+
val isDefinedAtFn = applyFn.copy(
61+
name = nme.isDefinedAtImpl,
62+
flags = Synthetic | Method,
63+
info = MethodType(paramNames, paramTypes, defn.BooleanType)).asTerm
64+
val tru = Literal(Constant(true))
65+
def isDefinedAtRhs(paramRefss: List[List[Tree]]) = applyRhs match {
66+
case Match(selector, cases) =>
67+
assert(selector.symbol == param.symbol)
68+
val paramRef = paramRefss.head.head
69+
// Again, the alternative
70+
// val List(List(paramRef)) = paramRefs
71+
// fails with a similar self instantiation error
72+
def translateCase(cdef: CaseDef): CaseDef =
73+
cpy.CaseDef(cdef)(body = tru).changeOwner(applyFn, isDefinedAtFn)
74+
val defaultSym = ctx.newSymbol(isDefinedAtFn, nme.WILDCARD, Synthetic, selector.tpe.widen)
75+
val defaultCase =
76+
CaseDef(
77+
Bind(defaultSym, untpd.Ident(nme.WILDCARD).withType(selector.tpe.widen)),
78+
EmptyTree,
79+
Literal(Constant(false)))
80+
cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase)
81+
case _ =>
82+
tru
83+
}
84+
val isDefinedAtDef = transformFollowingDeep(DefDef(isDefinedAtFn, isDefinedAtRhs(_)))
85+
val anonCls = AnonClass(tpt.tpe :: Nil, List(applyFn, isDefinedAtFn), List(nme.apply, nme.isDefinedAt))
86+
cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls)
87+
}
88+
}

src/dotty/tools/dotc/transform/SymUtils.scala

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,20 @@ object SymUtils {
2424
class SymUtils(val self: Symbol) extends AnyVal {
2525
import SymUtils._
2626

27-
def superClass(implicit ctx: Context) = {
28-
val parents = self.asClass.classInfo.parents
29-
if (parents.isEmpty) NoSymbol
30-
else parents.head.symbol
31-
}
32-
33-
34-
/**
35-
* For a class: All interfaces implemented by a class except for those inherited through the superclass.
36-
* For a trait: all parent traits
37-
*/
38-
39-
def superInterfaces(implicit ctx: Context) = {
40-
val superCls = self.superClass
27+
/** All traits implemented by a class or trait except for those inherited through the superclass. */
28+
def directlyInheritedTraits(implicit ctx: Context) = {
29+
val superCls = self.asClass.superClass
4130
val baseClasses = self.asClass.baseClasses
4231
if (baseClasses.isEmpty) Nil
4332
else baseClasses.tail.takeWhile(_ ne superCls).reverse
44-
4533
}
4634

47-
/** All interfaces implemented by a class, except for those inherited through the superclass. */
35+
/** All traits implemented by a class, except for those inherited through the superclass.
36+
* The empty list if `self` is a trait.
37+
*/
4838
def mixins(implicit ctx: Context) = {
4939
if (self is Trait) Nil
50-
else superInterfaces
40+
else directlyInheritedTraits
5141
}
5242

5343
def isTypeTestOrCast(implicit ctx: Context): Boolean =

src/dotty/tools/dotc/transform/TreeTransform.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,14 @@ object TreeTransforms {
486486
var nxCopied = false
487487
var result = info.transformers
488488
var resultNX = info.nx
489-
var i = mutationPlan(0) // if TreeTransform.transform() method didn't exist we could have used mutationPlan(cur)
489+
var i = mutationPlan(cur)
490+
// @DarkDimius You commented on the previous version
491+
//
492+
// var i = mutationPlan(0) // if TreeTransform.transform() method didn't exist we could have used mutationPlan(cur)
493+
//
494+
// But we need to use `cur` or otherwise we call prepare actions preceding the
495+
// phase that issued a transformFollowing. This can lead to "denotation not defined
496+
// here" errors. Note that tests still pass with the current modified code.
490497
val l = result.length
491498
var allDone = i < l
492499
while (i < l) {

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -844,8 +844,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
844844
}
845845

846846
def typedBind(tree: untpd.Bind, pt: Type)(implicit ctx: Context): Bind = track("typedBind") {
847-
val body1 = typed(tree.body, pt)
848-
typr.println(i"typed bind $tree pt = $pt bodytpe = ${body1.tpe}")
847+
val pt1 = fullyDefinedType(pt, "pattern variable", tree.pos)
848+
val body1 = typed(tree.body, pt1)
849+
typr.println(i"typed bind $tree pt = $pt1 bodytpe = ${body1.tpe}")
849850
val flags = if (tree.isType) BindDefinedType else EmptyFlags
850851
val sym = ctx.newSymbol(ctx.owner, tree.name, flags, body1.tpe, coord = tree.pos)
851852
assignType(cpy.Bind(tree)(tree.name, body1), sym)

test/dotc/tests.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@ class tests extends CompilerTest {
106106
@Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 6)
107107
@Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1)
108108
@Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12)
109-
@Test def neg_sam = compileFile(negDir, "sammy_poly", xerrors = 1)
110-
// TODO: this test file doesn't exist (anymore?), remove?
111-
// @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1)
112109

113110
val negTailcallDir = negDir + "tailcall/"
114111
@Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b", xerrors = 6)

tests/disabled/simplesams.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package test
2+
3+
trait X { def foo(x: Int): Int; def bar = foo(2) }
4+
trait XX extends X
5+
6+
object test {
7+
val x: X = (x: Int) => 2 // should be a closure
8+
val xx: XX = (x: Int) => 2 // should be a closure, but blows up in backend
9+
}

tests/neg/sammy_poly.scala

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

tests/pos/Patterns.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,8 @@ object Patterns {
9494
t
9595
}
9696
}
97+
98+
object NestedPattern {
99+
val xss: List[List[String]] = ???
100+
val List(List(x)) = xss
101+
}

0 commit comments

Comments
 (0)