Skip to content

Expand SAM closures to anonymous classes if needed #509

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
}
def parentSymbols: List[Symbol] = toDenot(sym).info.parents.map(_.typeSymbol)
def superClass: Symbol = {
val t = toDenot(sym).superClass
val t = toDenot(sym).asClass.superClass
if (t.exists) t
else if (sym is Flags.ModuleClass) {
// workaround #371
Expand Down Expand Up @@ -712,7 +712,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
* All interfaces implemented by a class, except for those inherited through the superclass.
*
*/
def superInterfaces: List[Symbol] = decorateSymbol(sym).superInterfaces
def superInterfaces: List[Symbol] = decorateSymbol(sym).directlyInheritedTraits

/**
* True for module classes of package level objects. The backend will generate a mirror class for
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Compiler {
new ElimRepeated,
new NormalizeFlags,
new ExtensionMethods,
new ExpandSAMs,
new TailRec),
List(new PatternMatcher,
new ExplicitOuter,
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Run(comp: Compiler)(implicit ctx: Context) {
compileSources(sources)
} catch {
case NonFatal(ex) =>
println(s"exception occurred while compiling $units%, %")
println(i"exception occurred while compiling $units%, %")
throw ex
}

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
/** Does tree contain an initialization part when seen as a member of a class or trait?
*/
def isNoInitMember(tree: Tree): Boolean = unsplice(tree) match {
case EmptyTree | Import(_, _) | TypeDef(_, _) => true
case EmptyTree | Import(_, _) | TypeDef(_, _) | DefDef(_, _, _, _, _) => true
case tree: ValDef => tree.unforcedRhs == EmptyTree
case _ => false
}
Expand Down
33 changes: 30 additions & 3 deletions src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
ta.assignType(untpd.TypeDef(sym.name, TypeTree(sym.info)), sym)

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

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

/** An anonymous class
*
* new parents { forwarders }
*
* where `forwarders` contains forwarders for all functions in `fns`.
* @param parents a non-empty list of class types
* @param fns a non-empty of functions for which forwarders should be defined in the class.
* The class has the same owner as the first function in `fns`.
* Its position is the union of all functions in `fns`.
*/
def AnonClass(parents: List[Type], fns: List[TermSymbol], methNames: List[TermName])(implicit ctx: Context): Block = {
val owner = fns.head.owner
val parents1 =
if (parents.head.classSymbol.is(Trait)) defn.ObjectClass.typeRef :: parents
else parents
val cls = ctx.newNormalizedClassSymbol(owner, tpnme.ANON_FUN, Synthetic, parents1,
coord = fns.map(_.pos).reduceLeft(_ union _))
val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil).entered
def forwarder(fn: TermSymbol, name: TermName) = {
val fwdMeth = fn.copy(cls, name, Synthetic | Method).entered.asTerm
DefDef(fwdMeth, prefss => ref(fn).appliedToArgss(prefss))
}
val forwarders = (fns, methNames).zipped.map(forwarder)
val cdef = ClassDef(cls, DefDef(constr), forwarders)
Block(cdef :: Nil, New(cls.typeRef, Nil))
}

// { <label> def while$(): Unit = if (cond) { body; while$() } ; while$() }
def WhileDo(owner: Symbol, cond: Tree, body: List[Tree])(implicit ctx: Context): Tree = {
val sym = ctx.newSymbol(owner, nme.WHILE_PREFIX, Flags.Label | Flags.Synthetic,
Expand All @@ -254,7 +282,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
Block(List(DefDef(sym, rhs)), call)
}


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

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ object Flags {
final val JavaStaticTerm = JavaStatic.toTermFlags
final val JavaStaticType = JavaStatic.toTypeFlags

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

/** Variable is accessed from nested function. */
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ object StdNames {
val isArray: N = "isArray"
val isDefined: N = "isDefined"
val isDefinedAt: N = "isDefinedAt"
val isDefinedAtImpl: N = "$isDefinedAt"
val isEmpty: N = "isEmpty"
val isInstanceOf_ : N = "isInstanceOf"
val java: N = "java"
Expand Down
24 changes: 24 additions & 0 deletions src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,30 @@ trait Symbols { this: Context =>
ClassInfo(owner.thisType, _, parents, decls, selfInfo),
privateWithin, coord, assocFile)

/** Same as `newCompleteClassSymbol` except that `parents` can be a list of arbitary
* types which get normalized into type refs and parameter bindings.
*/
def newNormalizedClassSymbol(
owner: Symbol,
name: TypeName,
flags: FlagSet,
parentTypes: List[Type],
decls: Scope = newScope,
selfInfo: Type = NoType,
privateWithin: Symbol = NoSymbol,
coord: Coord = NoCoord,
assocFile: AbstractFile = null): ClassSymbol = {
def completer = new LazyType {
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
val cls = denot.asClass.classSymbol
val decls = newScope
val parentRefs: List[TypeRef] = normalizeToClassRefs(parentTypes, cls, decls)
denot.info = ClassInfo(owner.thisType, cls, parentRefs, decls)
}
}
newClassSymbol(owner, name, flags, completer, privateWithin, coord, assocFile)
}

/** Create a module symbol with associated module class
* from its non-info fields and a function producing the info
* of the module class (this info may be lazy).
Expand Down
10 changes: 10 additions & 0 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,11 @@ object Types {
(name, buf) => buf ++= member(name).altsWith(x => x.isClass))
}

final def fields(implicit ctx: Context): Seq[SingleDenotation] = track("fields") {
memberDenots(fieldFilter,
(name, buf) => buf ++= member(name).altsWith(x => !x.is(Method)))
}

/** The set of members of this type having at least one of `requiredFlags` but none of `excludedFlags` set */
final def membersBasedOnFlags(requiredFlags: FlagSet, excludedFlags: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("implicitMembers") {
memberDenots(takeAllFilter,
Expand Down Expand Up @@ -3049,6 +3054,11 @@ object Types {
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = name.isTypeName
}

object fieldFilter extends NameFilter {
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
name.isTermName && (pre member name).hasAltWith(!_.symbol.is(Method))
}

object takeAllFilter extends NameFilter {
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true
}
Expand Down
88 changes: 88 additions & 0 deletions src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package dotty.tools.dotc
package transform

import core._
import Contexts._, Symbols._, Types._, Flags._, Decorators._, StdNames._, Constants._
import SymDenotations.SymDenotation
import TreeTransforms._
import SymUtils._
import ast.untpd
import ast.Trees._

/** Expand SAM closures that cannot be represented by the JVM as lambdas to anonymous classes.
* These fall into five categories
*
* 1. Partial function closures, we need to generate a isDefinedAt method for these.
* 2. Closures implementing non-trait classes.
* 3. Closures implementing classes that inherit from a class other than Object
* (a lambda cannot not be a run-time subtype of such a class)
* 4. Closures that implement traits which run initialization code.
* 5. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be
* (1) superaccessors, (2) outer references, (3) accessors for fields.
*/
class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
override def phaseName = "expandSAMs"

import ast.tpd._

def noJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
!cls.is(Trait) ||
cls.superClass != defn.ObjectClass ||
!cls.is(NoInits) ||
!cls.directlyInheritedTraits.forall(_.is(NoInits)) ||
ExplicitOuter.needsOuterIfReferenced(cls) ||
cls.typeRef.fields.nonEmpty // Superaccessors already show up as abstract methods here, so no test necessary


override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match {
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
tpt.tpe match {
case NoType => tree // it's a plain function
case tpe @ SAMType(_) if !noJvmSam(tpe.classSymbol.asClass) =>
if (tpe isRef defn.PartialFunctionClass) toPartialFunction(tree)
else tree
case tpe =>
cpy.Block(tree)(stats,
AnonClass(tpe :: Nil, fn.symbol.asTerm :: Nil, nme.apply :: Nil))
}
case _ =>
tree
}

private def toPartialFunction(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = {
val Block(
(applyDef @ DefDef(nme.ANON_FUN, Nil, List(List(param)), _, _)) :: Nil,
Closure(_, _, tpt)) = tree
val applyRhs: Tree = applyDef.rhs
val applyFn = applyDef.symbol.asTerm

val MethodType(paramNames, paramTypes) = applyFn.info
val isDefinedAtFn = applyFn.copy(
name = nme.isDefinedAtImpl,
flags = Synthetic | Method,
info = MethodType(paramNames, paramTypes, defn.BooleanType)).asTerm
val tru = Literal(Constant(true))
def isDefinedAtRhs(paramRefss: List[List[Tree]]) = applyRhs match {
case Match(selector, cases) =>
assert(selector.symbol == param.symbol)
val paramRef = paramRefss.head.head
// Again, the alternative
// val List(List(paramRef)) = paramRefs
// fails with a similar self instantiation error
def translateCase(cdef: CaseDef): CaseDef =
cpy.CaseDef(cdef)(body = tru).changeOwner(applyFn, isDefinedAtFn)
val defaultSym = ctx.newSymbol(isDefinedAtFn, nme.WILDCARD, Synthetic, selector.tpe.widen)
val defaultCase =
CaseDef(
Bind(defaultSym, untpd.Ident(nme.WILDCARD).withType(selector.tpe.widen)),
EmptyTree,
Literal(Constant(false)))
cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase)
case _ =>
tru
}
val isDefinedAtDef = transformFollowingDeep(DefDef(isDefinedAtFn, isDefinedAtRhs(_)))
val anonCls = AnonClass(tpt.tpe :: Nil, List(applyFn, isDefinedAtFn), List(nme.apply, nme.isDefinedAt))
cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls)
}
}
24 changes: 7 additions & 17 deletions src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,20 @@ object SymUtils {
class SymUtils(val self: Symbol) extends AnyVal {
import SymUtils._

def superClass(implicit ctx: Context) = {
val parents = self.asClass.classInfo.parents
if (parents.isEmpty) NoSymbol
else parents.head.symbol
}


/**
* For a class: All interfaces implemented by a class except for those inherited through the superclass.
* For a trait: all parent traits
*/

def superInterfaces(implicit ctx: Context) = {
val superCls = self.superClass
/** All traits implemented by a class or trait except for those inherited through the superclass. */
def directlyInheritedTraits(implicit ctx: Context) = {
val superCls = self.asClass.superClass
val baseClasses = self.asClass.baseClasses
if (baseClasses.isEmpty) Nil
else baseClasses.tail.takeWhile(_ ne superCls).reverse

}

/** All interfaces implemented by a class, except for those inherited through the superclass. */
/** All traits implemented by a class, except for those inherited through the superclass.
* The empty list if `self` is a trait.
*/
def mixins(implicit ctx: Context) = {
if (self is Trait) Nil
else superInterfaces
else directlyInheritedTraits
}

def isTypeTestOrCast(implicit ctx: Context): Boolean =
Expand Down
9 changes: 8 additions & 1 deletion src/dotty/tools/dotc/transform/TreeTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,14 @@ object TreeTransforms {
var nxCopied = false
var result = info.transformers
var resultNX = info.nx
var i = mutationPlan(0) // if TreeTransform.transform() method didn't exist we could have used mutationPlan(cur)
var i = mutationPlan(cur)
// @DarkDimius You commented on the previous version
//
// var i = mutationPlan(0) // if TreeTransform.transform() method didn't exist we could have used mutationPlan(cur)
//
// But we need to use `cur` or otherwise we call prepare actions preceding the
// phase that issued a transformFollowing. This can lead to "denotation not defined
// here" errors. Note that tests still pass with the current modified code.
val l = result.length
var allDone = i < l
while (i < l) {
Expand Down
5 changes: 3 additions & 2 deletions src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -844,8 +844,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
}

def typedBind(tree: untpd.Bind, pt: Type)(implicit ctx: Context): Bind = track("typedBind") {
val body1 = typed(tree.body, pt)
typr.println(i"typed bind $tree pt = $pt bodytpe = ${body1.tpe}")
val pt1 = fullyDefinedType(pt, "pattern variable", tree.pos)
val body1 = typed(tree.body, pt1)
typr.println(i"typed bind $tree pt = $pt1 bodytpe = ${body1.tpe}")
val flags = if (tree.isType) BindDefinedType else EmptyFlags
val sym = ctx.newSymbol(ctx.owner, tree.name, flags, body1.tpe, coord = tree.pos)
assignType(cpy.Bind(tree)(tree.name, body1), sym)
Expand Down
3 changes: 0 additions & 3 deletions test/dotc/tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,6 @@ class tests extends CompilerTest {
@Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 6)
@Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1)
@Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12)
@Test def neg_sam = compileFile(negDir, "sammy_poly", xerrors = 1)
// TODO: this test file doesn't exist (anymore?), remove?
// @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1)

val negTailcallDir = negDir + "tailcall/"
@Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b", xerrors = 6)
Expand Down
9 changes: 9 additions & 0 deletions tests/disabled/simplesams.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package test

trait X { def foo(x: Int): Int; def bar = foo(2) }
trait XX extends X

object test {
val x: X = (x: Int) => 2 // should be a closure
val xx: XX = (x: Int) => 2 // should be a closure, but blows up in backend
}
7 changes: 0 additions & 7 deletions tests/neg/sammy_poly.scala

This file was deleted.

5 changes: 5 additions & 0 deletions tests/pos/Patterns.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,8 @@ object Patterns {
t
}
}

object NestedPattern {
val xss: List[List[String]] = ???
val List(List(x)) = xss
}
Loading