diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform')
68 files changed, 12937 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala b/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala new file mode 100644 index 000000000..74213d332 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala @@ -0,0 +1,59 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import Contexts.Context +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import StdNames._ +import NameOps._ +import ast.Trees._ +import dotty.tools.dotc.ast.tpd +import util.Positions._ +import Names._ + +import collection.mutable +import ResolveSuper._ + +import scala.collection.immutable.:: + + +/** This phase rewrites calls to array constructors to newArray method in Dotty.runtime.Arrays module. + * + * It assummes that generic arrays have already been handled by typer(see Applications.convertNewGenericArray). + * Additionally it optimizes calls to scala.Array.ofDim functions by replacing them with calls to newArray with specific dimensions + */ +class ArrayConstructors extends MiniPhaseTransform { thisTransform => + import ast.tpd._ + + override def phaseName: String = "arrayConstructors" + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + def rewrite(elemType: Type, dims: List[Tree]) = + tpd.newArray(elemType, tree.tpe, tree.pos, JavaSeqLiteral(dims, TypeTree(defn.IntClass.typeRef))) + + if (tree.fun.symbol eq defn.ArrayConstructor) { + val TypeApply(tycon, targ :: Nil) = tree.fun + rewrite(targ.tpe, tree.args) + } else if ((tree.fun.symbol.maybeOwner eq defn.ArrayModule) && (tree.fun.symbol.name eq nme.ofDim) && !tree.tpe.isInstanceOf[MethodicType]) { + val Apply(Apply(TypeApply(_, List(tp)), _), _) = tree + val cs = tp.tpe.widen.classSymbol + tree.fun match { + case Apply(TypeApply(t: Ident, targ), dims) + if !TypeErasure.isUnboundedGeneric(targ.head.tpe) && !ValueClasses.isDerivedValueClass(cs) => + rewrite(targ.head.tpe, dims) + case Apply(TypeApply(t: Select, targ), dims) + if !TypeErasure.isUnboundedGeneric(targ.head.tpe) && !ValueClasses.isDerivedValueClass(cs) => + Block(t.qualifier :: Nil, rewrite(targ.head.tpe, dims)) + case _ => tree + } + + } else tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala b/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala new file mode 100644 index 000000000..9c01aaa9a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala @@ -0,0 +1,101 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import Contexts.Context +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import Annotations._ +import StdNames._ +import NameOps._ +import ast.Trees._ + +/** This phase augments Scala2 traits with implementation classes and with additional members + * needed for mixin composition. + * These symbols would have been added between Unpickling and Mixin in the Scala2 pipeline. + * Specifcally, it adds + * + * - an implementation class which defines a trait constructor and trait method implementations + * - trait setters for vals defined in traits + * + * Furthermore, it expands the names of all private getters and setters as well as super accessors in the trait and makes + * them not-private. + */ +class AugmentScala2Traits extends MiniPhaseTransform with IdentityDenotTransformer with FullParameterization { thisTransform => + import ast.tpd._ + + override def phaseName: String = "augmentScala2Traits" + + override def rewiredTarget(referenced: Symbol, derived: Symbol)(implicit ctx: Context) = NoSymbol + + override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = { + val cls = impl.symbol.owner.asClass + for (mixin <- cls.mixins) + if (mixin.is(Scala2x)) + augmentScala2Trait(mixin, cls) + impl + } + + private def augmentScala2Trait(mixin: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context): Unit = { + if (mixin.implClass.is(Scala2x)) () // nothing to do, mixin was already augmented + else { + //println(i"creating new implclass for $mixin ${mixin.implClass}") + val ops = new MixinOps(cls, thisTransform) + import ops._ + + val implClass = ctx.newCompleteClassSymbol( + owner = mixin.owner, + name = mixin.name.implClassName, + flags = Abstract | Scala2x, + parents = defn.ObjectType :: Nil, + assocFile = mixin.assocFile).enteredAfter(thisTransform) + + def implMethod(meth: TermSymbol): Symbol = { + val mold = + if (meth.isConstructor) + meth.copySymDenotation( + name = nme.TRAIT_CONSTRUCTOR, + info = MethodType(Nil, defn.UnitType)) + else meth.ensureNotPrivate + meth.copy( + owner = implClass, + name = mold.name.asTermName, + flags = Method | JavaStatic | mold.flags & ExpandedName, + info = fullyParameterizedType(mold.info, mixin)) + } + + def traitSetter(getter: TermSymbol) = + getter.copy( + name = getter.ensureNotPrivate.name + .expandedName(getter.owner, nme.TRAIT_SETTER_SEPARATOR) + .asTermName.setterName, + flags = Method | Accessor | ExpandedName, + info = MethodType(getter.info.resultType :: Nil, defn.UnitType)) + + for (sym <- mixin.info.decls) { + if (needsForwarder(sym) || sym.isConstructor || sym.isGetter && sym.is(Lazy) || sym.is(Method, butNot = Deferred)) + implClass.enter(implMethod(sym.asTerm)) + if (sym.isGetter) + if (sym.is(Lazy)) { + if (!sym.hasAnnotation(defn.VolatileAnnot)) + sym.addAnnotation(Annotation(defn.VolatileAnnot, Nil)) + } + else if (!sym.is(Deferred) && !sym.setter.exists && + !sym.info.resultType.isInstanceOf[ConstantType]) + traitSetter(sym.asTerm).enteredAfter(thisTransform) + if ((sym.is(PrivateAccessor, butNot = ExpandedName) && + (sym.isGetter || sym.isSetter)) // strangely, Scala 2 fields are also methods that have Accessor set. + || sym.is(SuperAccessor)) // scala2 superaccessors are pickled as private, but are compiled as public expanded + sym.ensureNotPrivate.installAfter(thisTransform) + } + ctx.log(i"Scala2x trait decls of $mixin = ${mixin.info.decls.toList.map(_.showDcl)}%\n %") + ctx.log(i"Scala2x impl decls of $mixin = ${implClass.info.decls.toList.map(_.showDcl)}%\n %") + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/CapturedVars.scala b/compiler/src/dotty/tools/dotc/transform/CapturedVars.scala new file mode 100644 index 000000000..cd05589c3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CapturedVars.scala @@ -0,0 +1,149 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.SymDenotations._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import ast.Trees._ +import SymUtils._ +import collection.{ mutable, immutable } +import collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet } + +class CapturedVars extends MiniPhase with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + /** the following two members override abstract members in Transform */ + val phaseName: String = "capturedVars" + val treeTransform = new Transform(Set()) + + private class RefInfo(implicit ctx: Context) { + /** The classes for which a Ref type exists. */ + val refClassKeys: collection.Set[Symbol] = + defn.ScalaNumericValueClasses() + defn.BooleanClass + defn.ObjectClass + + val refClass: Map[Symbol, Symbol] = + refClassKeys.map(rc => rc -> ctx.requiredClass(s"scala.runtime.${rc.name}Ref")).toMap + + val volatileRefClass: Map[Symbol, Symbol] = + refClassKeys.map(rc => rc -> ctx.requiredClass(s"scala.runtime.Volatile${rc.name}Ref")).toMap + + val boxedRefClasses: collection.Set[Symbol] = + refClassKeys.flatMap(k => Set(refClass(k), volatileRefClass(k))) + } + + class Transform(captured: collection.Set[Symbol]) extends TreeTransform { + def phase = thisTransform + + private var myRefInfo: RefInfo = null + private def refInfo(implicit ctx: Context) = { + if (myRefInfo == null) myRefInfo = new RefInfo() + myRefInfo + } + + private class CollectCaptured(implicit ctx: Context) extends EnclosingMethodTraverser { + private val captured = mutable.HashSet[Symbol]() + def traverse(enclMeth: Symbol, tree: Tree)(implicit ctx: Context) = tree match { + case id: Ident => + val sym = id.symbol + if (sym.is(Mutable, butNot = Method) && sym.owner.isTerm && sym.enclosingMethod != enclMeth) { + ctx.log(i"capturing $sym in ${sym.enclosingMethod}, referenced from $enclMeth") + captured += sym + } + case _ => + foldOver(enclMeth, tree) + } + def runOver(tree: Tree): collection.Set[Symbol] = { + apply(NoSymbol, tree) + captured + } + } + + override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { + val captured = (new CollectCaptured)(ctx.withPhase(thisTransform)) + .runOver(ctx.compilationUnit.tpdTree) + new Transform(captured) + } + + /** The {Volatile|}{Int|Double|...|Object}Ref class corresponding to the class `cls`, + * depending on whether the reference should be @volatile + */ + def refClass(cls: Symbol, isVolatile: Boolean)(implicit ctx: Context): Symbol = { + val refMap = if (isVolatile) refInfo.volatileRefClass else refInfo.refClass + if (cls.isClass) { + refMap.getOrElse(cls, refMap(defn.ObjectClass)) + } + else refMap(defn.ObjectClass) + } + + override def prepareForValDef(vdef: ValDef)(implicit ctx: Context) = { + val sym = vdef.symbol + if (captured contains sym) { + val newd = sym.denot(ctx.withPhase(thisTransform)).copySymDenotation( + info = refClass(sym.info.classSymbol, sym.hasAnnotation(defn.VolatileAnnot)).typeRef, + initFlags = sym.flags &~ Mutable) + newd.removeAnnotation(defn.VolatileAnnot) + newd.installAfter(thisTransform) + } + this + } + + override def transformValDef(vdef: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val vble = vdef.symbol + if (captured contains vble) { + def boxMethod(name: TermName): Tree = + ref(vble.info.classSymbol.companionModule.info.member(name).symbol) + cpy.ValDef(vdef)( + rhs = vdef.rhs match { + case EmptyTree => boxMethod(nme.zero).appliedToNone.withPos(vdef.pos) + case arg => boxMethod(nme.create).appliedTo(arg) + }, + tpt = TypeTree(vble.info).withPos(vdef.tpt.pos)) + } else vdef + } + + override def transformIdent(id: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = { + val vble = id.symbol + if (captured(vble)) + (id select nme.elem).ensureConforms(vble.denot(ctx.withPhase(thisTransform)).info) + else id + } + + /** If assignment is to a boxed ref type, e.g. + * + * intRef.elem = expr + * + * rewrite using a temporary var to + * + * val ev$n = expr + * intRef.elem = ev$n + * + * That way, we avoid the problem that `expr` might contain a `try` that would + * run on a non-empty stack (which is illegal under JVM rules). Note that LiftTry + * has already run before, so such `try`s would not be eliminated. + * + * Also: If the ref type lhs is followed by a cast (can be an artifact of nested translation), + * drop the cast. + */ + override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = { + def recur(lhs: Tree): Tree = lhs match { + case TypeApply(Select(qual, nme.asInstanceOf_), _) => + val Select(_, nme.elem) = qual + recur(qual) + case Select(_, nme.elem) if refInfo.boxedRefClasses.contains(lhs.symbol.maybeOwner) => + val tempDef = transformFollowing(SyntheticValDef(ctx.freshName("ev$").toTermName, tree.rhs)) + transformFollowing(Block(tempDef :: Nil, cpy.Assign(tree)(lhs, ref(tempDef.symbol)))) + case _ => + tree + } + recur(tree.lhs) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/CheckReentrant.scala b/compiler/src/dotty/tools/dotc/transform/CheckReentrant.scala new file mode 100644 index 000000000..c9eefb22f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CheckReentrant.scala @@ -0,0 +1,95 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, MiniPhaseTransform, TreeTransformer} +import ast.Trees._ +import Flags._ +import Types._ +import Constants.Constant +import Contexts.Context +import Symbols._ +import SymDenotations._ +import Decorators._ +import dotty.tools.dotc.core.Annotations.ConcreteAnnotation +import dotty.tools.dotc.core.Denotations.SingleDenotation +import scala.collection.mutable +import DenotTransformers._ +import typer.Checking +import Names.Name +import NameOps._ +import StdNames._ + + +/** A no-op transform that checks whether the compiled sources are re-entrant. + * If -Ycheck:reentrant is set, the phase makes sure that there are no variables + * that are accessible from a global object. It excludes from checking paths that + * are labeled with one of the annotations + * + * @sharable Indicating a class or val can be safely shared + * @unshared Indicating an object will not be accessed from multiple threads + * + * Currently the analysis is only intended to check the dotty compiler itself. To make + * it generally useful we'd need to add at least the following: + * + * - Handle polymorphic instantiation: We might instantiate a generic class + * with a type that contains vars. If the class contains fields of the generic + * type, this may constitute a path to a shared var, which currently goes undetected. + * - Handle arrays: Array elements are currently ignored because they are often used + * in an immutable way anyway. To do better, it would be helpful to have a type + * for immutable array. + */ +class CheckReentrant extends MiniPhaseTransform { thisTransformer => + import ast.tpd._ + + override def phaseName = "checkReentrant" + + private var shared: Set[Symbol] = Set() + private var seen: Set[ClassSymbol] = Set() + private var indent: Int = 0 + + private val sharableAnnot = new CtxLazy(implicit ctx => + ctx.requiredClass("dotty.tools.sharable")) + private val unsharedAnnot = new CtxLazy(implicit ctx => + ctx.requiredClass("dotty.tools.unshared")) + + def isIgnored(sym: Symbol)(implicit ctx: Context) = + sym.hasAnnotation(sharableAnnot()) || + sym.hasAnnotation(unsharedAnnot()) + + def scanning(sym: Symbol)(op: => Unit)(implicit ctx: Context): Unit = { + ctx.log(i"${" " * indent}scanning $sym") + indent += 1 + try op + finally indent -= 1 + } + + def addVars(cls: ClassSymbol)(implicit ctx: Context): Unit = { + if (!seen.contains(cls) && !isIgnored(cls)) { + seen += cls + scanning(cls) { + for (sym <- cls.classInfo.decls) + if (sym.isTerm && !sym.isSetter && !isIgnored(sym)) + if (sym.is(Mutable)) { + ctx.error( + i"""possible data race involving globally reachable ${sym.showLocated}: ${sym.info} + | use -Ylog:checkReentrant+ to find out more about why the variable is reachable.""") + shared += sym + } else if (!sym.is(Method) || sym.is(Accessor | ParamAccessor)) { + scanning(sym) { + sym.info.widenExpr.classSymbols.foreach(addVars) + } + } + for (parent <- cls.classInfo.classParents) + addVars(parent.symbol.asClass) + } + } + } + + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (ctx.settings.YcheckReentrant.value && tree.symbol.owner.isStaticOwner) + addVars(tree.symbol.owner.asClass) + tree + } +}
\ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/CheckStatic.scala b/compiler/src/dotty/tools/dotc/transform/CheckStatic.scala new file mode 100644 index 000000000..937a4f1cc --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CheckStatic.scala @@ -0,0 +1,96 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import StdNames.nme +import Types._ +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, MiniPhaseTransform, TreeTransformer} +import ast.Trees._ +import Flags._ +import Contexts.Context +import Symbols._ +import Constants._ +import Denotations._, SymDenotations._ +import Decorators.StringInterpolators +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Annotations.ConcreteAnnotation +import scala.collection.mutable +import DenotTransformers._ +import Names.Name +import NameOps._ +import Decorators._ +import TypeUtils._ + +/** A transformer that check that requirements of Static fields\methods are implemented: + * 1. Only objects can have members annotated with `@static` + * 2. The fields annotated with `@static` should preceed any non-`@static` fields. + * This ensures that we do not introduce surprises for users in initialization order. + * 3. If a member `foo` of an `object C` is annotated with `@static`, + * the companion class `C` is not allowed to define term members with name `foo`. + * 4. If a member `foo` of an `object C` is annotated with `@static`, the companion class `C` + * is not allowed to inherit classes that define a term member with name `foo`. + * 5. Only `@static` methods and vals are supported in companions of traits. + * Java8 supports those, but not vars, and JavaScript does not have interfaces at all. + * 6. `@static` Lazy vals are currently unsupported. + */ +class CheckStatic extends MiniPhaseTransform { thisTransformer => + import ast.tpd._ + + override def phaseName = "checkStatic" + + + def check(tree: tpd.DefTree)(implicit ctx: Context) = { + + } + + override def transformTemplate(tree: tpd.Template)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val defns = tree.body.collect{case t: ValOrDefDef => t} + var hadNonStaticField = false + for(defn <- defns) { + if (defn.symbol.hasAnnotation(ctx.definitions.ScalaStaticAnnot)) { + if(!ctx.owner.is(Module)) { + ctx.error("@static fields are only allowed inside objects", defn.pos) + } + + if (defn.isInstanceOf[ValDef] && hadNonStaticField) { + ctx.error("@static fields should preceed non-static ones", defn.pos) + } + + val companion = ctx.owner.companionClass + def clashes = companion.asClass.membersNamed(defn.name) + + if (!companion.exists) { + ctx.error("object that contains @static members should have companion class", defn.pos) + } else if (clashes.exists) { + ctx.error("companion classes cannot define members with same name as @static member", defn.pos) + } else if (defn.symbol.is(Flags.Mutable) && companion.is(Flags.Trait)) { + ctx.error("Companions of traits cannot define mutable @static fields", defn.pos) + } else if (defn.symbol.is(Flags.Lazy)) { + ctx.error("Lazy @static fields are not supported", defn.pos) + } else if (defn.symbol.allOverriddenSymbols.nonEmpty) { + ctx.error("@static members cannot override or implement non-static ones", defn.pos) + } + } else hadNonStaticField = hadNonStaticField || defn.isInstanceOf[ValDef] + + } + tree + } + + override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (tree.symbol.hasAnnotation(defn.ScalaStaticAnnot)) { + val symbolWhitelist = tree.symbol.ownersIterator.flatMap(x => if (x.is(Flags.Module)) List(x, x.companionModule) else List(x)).toSet + def isSafeQual(t: Tree): Boolean = { // follow the desugared paths created by typer + t match { + case t: This => true + case t: Select => isSafeQual(t.qualifier) && symbolWhitelist.contains(t.symbol) + case t: Ident => symbolWhitelist.contains(t.symbol) + case t: Block => t.stats.forall(tpd.isPureExpr) && isSafeQual(t.expr) + } + } + if (isSafeQual(tree.qualifier)) + ref(tree.symbol) + else tree + } else tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ClassOf.scala b/compiler/src/dotty/tools/dotc/transform/ClassOf.scala new file mode 100644 index 000000000..e7b6977c7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ClassOf.scala @@ -0,0 +1,30 @@ +package dotty.tools.dotc +package transform + +import ast.tpd +import core.Constants.Constant +import core.Contexts.Context +import core.StdNames.nme +import core.Symbols.{defn,TermSymbol} +import core.TypeErasure +import TreeTransforms.{MiniPhaseTransform, TransformerInfo, TreeTransform} + +/** Rewrite `classOf` calls as follow: + * + * For every primitive class C whose boxed class is called B: + * classOf[C] -> B.TYPE + * For every non-primitive class D: + * classOf[D] -> Literal(Constant(erasure(D))) + */ +class ClassOf extends MiniPhaseTransform { + import tpd._ + + override def phaseName: String = "classOf" + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = + if (tree.symbol eq defn.Predef_classOf) { + val targ = tree.args.head.tpe + clsOf(targ).ensureConforms(tree.tpe).withPos(tree.pos) + } + else tree +} diff --git a/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala b/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala new file mode 100644 index 000000000..714255962 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala @@ -0,0 +1,116 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer, MiniPhaseTransform} +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import scala.collection.mutable.ListBuffer +import dotty.tools.dotc.core.{Scopes, Flags} +import dotty.tools.dotc.core.Symbols.NoSymbol +import scala.annotation.tailrec +import dotty.tools.dotc.core._ +import Symbols._ +import scala.Some +import dotty.tools.dotc.transform.TreeTransforms.{NXTransformations, TransformerInfo, TreeTransform, TreeTransformer} +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import scala.collection.mutable +import dotty.tools.dotc.core.Names.Name +import NameOps._ +import Types._ +import scala.collection.SortedSet +import Decorators._ +import StdNames._ +import dotty.tools.dotc.util.Positions.Position +import dotty.tools.dotc.config.JavaPlatform + +class CollectEntryPoints extends MiniPhaseTransform { + + /** perform context-dependant initialization */ + override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context) = { + entryPoints = collection.immutable.TreeSet.empty[Symbol](new SymbolOrdering()) + assert(ctx.platform.isInstanceOf[JavaPlatform], "Java platform specific phase") + this + } + + private var entryPoints: Set[Symbol] = _ + + def getEntryPoints = entryPoints.toList + + override def phaseName: String = "collectEntryPoints" + override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (tree.symbol.owner.isClass && isJavaEntryPoint(tree.symbol)) { + // collecting symbols for entry points here (as opposed to GenBCode where they are used) + // has the advantage of saving an additional pass over all ClassDefs. + entryPoints += tree.symbol + } + tree + } + + def isJavaEntryPoint(sym: Symbol)(implicit ctx: Context): Boolean = { + def fail(msg: String, pos: Position = sym.pos) = { + ctx.warning(sym.name + + s" has a main method with parameter type Array[String], but ${sym.fullName} will not be a runnable program.\n Reason: $msg", + sourcePos(sym.pos) + // TODO: make this next claim true, if possible + // by generating valid main methods as static in module classes + // not sure what the jvm allows here + // + " You can still run the program by calling it as " + javaName(sym) + " instead." + ) + false + } + def failNoForwarder(msg: String) = { + fail(s"$msg, which means no static forwarder can be generated.\n") + } + val possibles = if (sym.flags is Flags.Module) (sym.info nonPrivateMember nme.main).alternatives else Nil + val hasApproximate = possibles exists { + m => + m.info match { + case MethodType(_, p :: Nil) => + p.typeSymbol == defn.ArrayClass + case _ => false + } + } + def precise(implicit ctx: Context) = { + val companion = sym.companionClass //sym.asClass.linkedClassOfClass + val javaPlatform = ctx.platform.asInstanceOf[JavaPlatform] + if (javaPlatform.hasJavaMainMethod(companion)) + failNoForwarder("companion contains its own main method") + else if (companion.exists && companion.info.member(nme.main).exists) + // this is only because forwarders aren't smart enough yet + failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") + else if (companion.flags is Flags.Trait) + failNoForwarder("companion is a trait") + // Now either succeed, or issue some additional warnings for things which look like + // attempts to be java main methods. + else (possibles exists (x => javaPlatform.isJavaMainMethod(x.symbol))) || { + possibles exists { + m => + m.symbol.info match { + case t: PolyType => + fail("main methods cannot be generic.") + case t@MethodType(paramNames, paramTypes) => + if (t.resultType :: paramTypes exists (_.typeSymbol.isAbstractType)) + fail("main methods cannot refer to type parameters or abstract types.", m.symbol.pos) + else + javaPlatform.isJavaMainMethod(m.symbol) || fail("main method must have exact signature (Array[String])Unit", m.symbol.pos) + case tp => + fail(s"don't know what this is: $tp", m.symbol.pos) + } + } + } + } + + // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. + hasApproximate && precise(ctx.withPhase(ctx.erasurePhase)) + // Before erasure so we can identify generic mains. + + +} + +} + +class SymbolOrdering(implicit ctx: Context) extends Ordering[Symbol] { + override def compare(x: Symbol, y: Symbol): Int = { + x.fullName.toString.compareTo(y.fullName.toString) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala new file mode 100644 index 000000000..db850e944 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -0,0 +1,261 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.StdNames._ +import Phases._ +import ast._ +import Trees._ +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import util.Positions._ +import Constants.Constant +import collection.mutable + +/** This transform + * - moves initializers from body to constructor. + * - makes all supercalls explicit + * - also moves private fields that are accessed only from constructor + * into the constructor if possible. + */ +class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import tpd._ + + override def phaseName: String = "constructors" + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize]) + + + // Collect all private parameter accessors and value definitions that need + // to be retained. There are several reasons why a parameter accessor or + // definition might need to be retained: + // 1. It is accessed after the constructor has finished + // 2. It is accessed before it is defined + // 3. It is accessed on an object other than `this` + // 4. It is a mutable parameter accessor + // 5. It is has a wildcard initializer `_` + private val retainedPrivateVals = mutable.Set[Symbol]() + private val seenPrivateVals = mutable.Set[Symbol]() + + private def markUsedPrivateSymbols(tree: RefTree)(implicit ctx: Context): Unit = { + + val sym = tree.symbol + def retain() = + retainedPrivateVals.add(sym) + + if (sym.exists && sym.owner.isClass && mightBeDropped(sym)) { + val owner = sym.owner.asClass + + tree match { + case Ident(_) | Select(This(_), _) => + def inConstructor = { + val method = ctx.owner.enclosingMethod + method.isPrimaryConstructor && ctx.owner.enclosingClass == owner + } + if (inConstructor && (sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) { + // used inside constructor, accessed on this, + // could use constructor argument instead, no need to retain field + } + else retain() + case _ => retain() + } + } + } + + override def transformIdent(tree: tpd.Ident)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + markUsedPrivateSymbols(tree) + tree + } + + override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + markUsedPrivateSymbols(tree) + tree + } + + override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (mightBeDropped(tree.symbol)) + (if (isWildcardStarArg(tree.rhs)) retainedPrivateVals else seenPrivateVals) += tree.symbol + tree + } + + /** All initializers for non-lazy fields should be moved into constructor. + * All non-abstract methods should be implemented (this is assured for constructors + * in this phase and for other methods in memoize). + */ + override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = { + tree match { + case tree: ValDef if tree.symbol.exists && tree.symbol.owner.isClass && !tree.symbol.is(Lazy) && !tree.symbol.hasAnnotation(defn.ScalaStaticAnnot) => + assert(tree.rhs.isEmpty, i"$tree: initializer should be moved to constructors") + case tree: DefDef if !tree.symbol.is(LazyOrDeferred) => + assert(!tree.rhs.isEmpty, i"unimplemented: $tree") + case _ => + } + } + + /** @return true if after ExplicitOuter, all references from this tree go via an + * outer link, so no parameter accessors need to be rewired to parameters + */ + private def noDirectRefsFrom(tree: Tree)(implicit ctx: Context) = + tree.isDef && tree.symbol.isClass && !tree.symbol.is(InSuperCall) + + /** Class members that can be eliminated if referenced only from their own + * constructor. + */ + private def mightBeDropped(sym: Symbol)(implicit ctx: Context) = + sym.is(Private, butNot = MethodOrLazy) && !sym.is(MutableParamAccessor) + + private final val MutableParamAccessor = allOf(Mutable, ParamAccessor) + + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { + val cls = ctx.owner.asClass + + val constr @ DefDef(nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr + + // Produce aligned accessors and constructor parameters. We have to adjust + // for any outer parameters, which are last in the sequence of original + // parameter accessors but come first in the constructor parameter list. + val accessors = cls.paramAccessors.filterNot(_.isSetter) + val vparamsWithOuterLast = vparams match { + case vparam :: rest if vparam.name == nme.OUTER => rest ::: vparam :: Nil + case _ => vparams + } + val paramSyms = vparamsWithOuterLast map (_.symbol) + + // Adjustments performed when moving code into the constructor: + // (1) Replace references to param accessors by constructor parameters + // except possibly references to mutable variables, if `excluded = Mutable`. + // (Mutable parameters should be replaced only during the super call) + // (2) If the parameter accessor reference was to an alias getter, + // drop the () when replacing by the parameter. + object intoConstr extends TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case Ident(_) | Select(This(_), _) => + var sym = tree.symbol + if (sym is (ParamAccessor, butNot = Mutable)) sym = sym.subst(accessors, paramSyms) + if (sym.owner.isConstructor) ref(sym).withPos(tree.pos) else tree + case Apply(fn, Nil) => + val fn1 = transform(fn) + if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor) + fn1 // in this case, fn1.symbol was an alias for a parameter in a superclass + else cpy.Apply(tree)(fn1, Nil) + case _ => + if (noDirectRefsFrom(tree)) tree else super.transform(tree) + } + + def apply(tree: Tree, prevOwner: Symbol)(implicit ctx: Context): Tree = { + transform(tree).changeOwnerAfter(prevOwner, constr.symbol, thisTransform) + } + } + + def isRetained(acc: Symbol) = { + !mightBeDropped(acc) || retainedPrivateVals(acc) + } + + val constrStats, clsStats = new mutable.ListBuffer[Tree] + + /** Map outer getters $outer and outer accessors $A$B$$$outer to the given outer parameter. */ + def mapOuter(outerParam: Symbol) = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree match { + case Apply(fn, Nil) + if (fn.symbol.is(OuterAccessor) + || fn.symbol.isGetter && fn.symbol.name == nme.OUTER + ) && + fn.symbol.info.resultType.classSymbol == outerParam.info.classSymbol => + ref(outerParam) + case _ => + super.transform(tree) + } + } + + val dropped = mutable.Set[Symbol]() + + // Split class body into statements that go into constructor and + // definitions that are kept as members of the class. + def splitStats(stats: List[Tree]): Unit = stats match { + case stat :: stats1 => + stat match { + case stat @ ValDef(name, tpt, _) if !stat.symbol.is(Lazy) && !stat.symbol.hasAnnotation(defn.ScalaStaticAnnot) => + val sym = stat.symbol + if (isRetained(sym)) { + if (!stat.rhs.isEmpty && !isWildcardArg(stat.rhs)) + constrStats += Assign(ref(sym), intoConstr(stat.rhs, sym)).withPos(stat.pos) + clsStats += cpy.ValDef(stat)(rhs = EmptyTree) + } + else if (!stat.rhs.isEmpty) { + dropped += sym + sym.copySymDenotation( + initFlags = sym.flags &~ Private, + owner = constr.symbol).installAfter(thisTransform) + constrStats += intoConstr(stat, sym) + } + case DefDef(nme.CONSTRUCTOR, _, ((outerParam @ ValDef(nme.OUTER, _, _)) :: _) :: Nil, _, _) => + clsStats += mapOuter(outerParam.symbol).transform(stat) + case _: DefTree => + clsStats += stat + case _ => + constrStats += intoConstr(stat, tree.symbol) + } + splitStats(stats1) + case Nil => + (Nil, Nil) + } + splitStats(tree.body) + + // The initializers for the retained accessors */ + val copyParams = accessors flatMap { acc => + if (!isRetained(acc)) { + dropped += acc + Nil + } else { + val target = if (acc.is(Method)) acc.field else acc + if (!target.exists) Nil // this case arises when the parameter accessor is an alias + else { + val param = acc.subst(accessors, paramSyms) + val assigns = Assign(ref(target), ref(param)).withPos(tree.pos) :: Nil + if (acc.name != nme.OUTER) assigns + else { + // insert test: if ($outer eq null) throw new NullPointerException + val nullTest = + If(ref(param).select(defn.Object_eq).appliedTo(Literal(Constant(null))), + Throw(New(defn.NullPointerExceptionClass.typeRef, Nil)), + unitLiteral) + nullTest :: assigns + } + } + } + } + + // Drop accessors that are not retained from class scope + if (dropped.nonEmpty) { + val clsInfo = cls.classInfo + cls.copy( + info = clsInfo.derivedClassInfo( + decls = clsInfo.decls.filteredScope(!dropped.contains(_)))) + + // TODO: this happens to work only because Constructors is the last phase in group + } + + val (superCalls, followConstrStats) = constrStats.toList match { + case (sc: Apply) :: rest if sc.symbol.isConstructor => (sc :: Nil, rest) + case stats => (Nil, stats) + } + + val mappedSuperCalls = vparams match { + case (outerParam @ ValDef(nme.OUTER, _, _)) :: _ => + superCalls.map(mapOuter(outerParam.symbol).transform) + case _ => superCalls + } + + cpy.Template(tree)( + constr = cpy.DefDef(constr)( + rhs = Block(copyParams ::: mappedSuperCalls ::: followConstrStats, unitLiteral)), + body = clsStats.toList) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/CrossCastAnd.scala b/compiler/src/dotty/tools/dotc/transform/CrossCastAnd.scala new file mode 100644 index 000000000..4fc4ef10b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CrossCastAnd.scala @@ -0,0 +1,30 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Types.{NoType, Type, AndType} +import dotty.tools.dotc.transform.TreeTransforms._ +import tpd._ + +import scala.collection.mutable.ListBuffer + + +/** + * This transform makes sure that all private member selections from + * AndTypes are performed from the first component of AndType. + * This is needed for correctness of erasure. See `tests/run/PrivateAnd.scala` + */ +class CrossCastAnd extends MiniPhaseTransform { thisTransform => + + override def phaseName: String = "crossCast" + + override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + + lazy val qtype = tree.qualifier.tpe.widen + val sym = tree.symbol + if (sym.is(Flags.Private) && qtype.typeSymbol != sym.owner) + cpy.Select(tree)(tree.qualifier.asInstance(AndType(qtype.baseTypeWithArgs(sym.owner), tree.qualifier.tpe)), tree.name) + else tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/CtxLazy.scala b/compiler/src/dotty/tools/dotc/transform/CtxLazy.scala new file mode 100644 index 000000000..7b317abef --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CtxLazy.scala @@ -0,0 +1,23 @@ +package dotty.tools.dotc +package transform +import core.Contexts.Context + +/** Utility class for lazy values whose evaluation depends on a context. + * This should be used whenever the evaluation of a lazy expression + * depends on some context, but the value can be re-used afterwards + * with a different context. + * + * A typical use case is a lazy val in a phase object which exists once per root context where + * the expression intiializing the lazy val depends only on the root context, but not any changes afterwards. + */ +class CtxLazy[T](expr: Context => T) { + private var myValue: T = _ + private var forced = false + def apply()(implicit ctx: Context): T = { + if (!forced) { + myValue = expr(ctx) + forced = true + } + myValue + } +}
\ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled b/compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled new file mode 100644 index 000000000..7b37c5881 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled @@ -0,0 +1,98 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers.SymTransformer +import Phases.Phase +import Contexts.Context +import Flags._ +import Symbols._ +import SymDenotations.SymDenotation +import ast.Trees._ +import collection.mutable +import Decorators._ +import NameOps._ +import TreeTransforms.MiniPhaseTransform +import dotty.tools.dotc.transform.TreeTransforms.TransformerInfo + +/** Remove companion objects that are empty + * Lots of constraints here: + * 1. It's impractical to place DropEmptyCompanions before lambda lift because dropped + * modules can be anywhere and have hard to trace references. + * 2. DropEmptyCompanions cannot be interleaved with LambdaLift or Flatten because + * they put things in liftedDefs sets which cause them to surface later. So + * removed modules resurface. + * 3. DropEmptyCompanions has to be before RestoreScopes. + * The solution to the constraints is to put DropEmptyCompanions between Flatten + * and RestoreScopes and to only start working once we are back on PackageDef + * level, so we know that all objects moved by LambdaLift and Flatten have arrived + * at their destination. + */ +class DropEmptyCompanions extends MiniPhaseTransform { thisTransform => + import ast.tpd._ + override def phaseName = "dropEmptyCompanions" + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Flatten]) + + override def transformPackageDef(pdef: PackageDef)(implicit ctx: Context, info: TransformerInfo) = { + + /** Is `tree` an empty companion object? */ + def isEmptyCompanion(tree: Tree) = tree match { + case TypeDef(_, impl: Template) if tree.symbol.is(SyntheticModule) && + tree.symbol.companionClass.exists && + impl.body.forall(_.symbol.isPrimaryConstructor) => + ctx.log(i"removing ${tree.symbol}") + true + case _ => + false + } + + val dropped = pdef.stats.filter(isEmptyCompanion).map(_.symbol).toSet + + /** Symbol is a $lzy field representing a module */ + def isLazyModuleVar(sym: Symbol) = + sym.name.isLazyLocal && + sym.owner.info.decl(sym.name.asTermName.nonLazyName).symbol.is(Module) + + /** Symbol should be dropped together with a dropped companion object. + * Such symbols are: + * - lzy fields pointing to modules, + * - vals and getters representing modules. + */ + def symIsDropped(sym: Symbol): Boolean = + (sym.is(Module) || isLazyModuleVar(sym)) && + dropped.contains(sym.info.resultType.typeSymbol) + + /** Tree should be dropped because it (is associated with) an empty + * companion object. Such trees are + * - module classes of empty companion objects + * - definitions of lazy module variables or assignments to them. + * - vals and getters for empty companion objects + */ + def toDrop(stat: Tree): Boolean = stat match { + case stat: TypeDef => dropped.contains(stat.symbol) + case stat: ValOrDefDef => symIsDropped(stat.symbol) + case stat: Assign => symIsDropped(stat.lhs.symbol) + case _ => false + } + + def prune(tree: Tree): Tree = tree match { + case tree @ TypeDef(name, impl @ Template(constr, _, _, _)) => + cpy.TypeDef(tree)( + rhs = cpy.Template(impl)( + constr = cpy.DefDef(constr)(rhs = pruneLocals(constr.rhs)), + body = pruneStats(impl.body))) + case _ => + tree + } + + def pruneStats(stats: List[Tree]) = + stats.filterConserve(!toDrop(_)).mapConserve(prune) + + def pruneLocals(expr: Tree) = expr match { + case Block(stats, expr) => cpy.Block(expr)(pruneStats(stats), expr) + case _ => expr + } + + cpy.PackageDef(pdef)(pdef.pid, pruneStats(pdef.stats)) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/DropInlined.scala b/compiler/src/dotty/tools/dotc/transform/DropInlined.scala new file mode 100644 index 000000000..775663b5c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DropInlined.scala @@ -0,0 +1,15 @@ +package dotty.tools.dotc +package transform + +import typer.Inliner +import core.Contexts.Context +import TreeTransforms.{MiniPhaseTransform, TransformerInfo} + +/** Drop Inlined nodes */ +class DropInlined extends MiniPhaseTransform { + import ast.tpd._ + override def phaseName = "dropInlined" + + override def transformInlined(tree: Inlined)(implicit ctx: Context, info: TransformerInfo): Tree = + Inliner.dropInlined(tree) +} diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala new file mode 100644 index 000000000..192227261 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -0,0 +1,129 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core._ +import DenotTransformers._ +import Symbols._ +import SymDenotations._ +import Contexts._ +import Types._ +import Flags._ +import Decorators._ +import SymUtils._ +import util.Attachment +import core.StdNames.nme +import ast.Trees._ + +/** This phase eliminates ExprTypes `=> T` as types of function parameters, and replaces them by + * nullary function types. More precisely: + * + * For the types of parameter symbols: + * + * => T ==> () => T + * + * Note that `=> T` types are not eliminated in MethodTypes. This is done later at erasure. + * Terms are rewritten as follows: + * + * x ==> x.apply() if x is a parameter that had type => T + * + * Arguments to call-by-name parameters are translated as follows. First, the argument is + * rewritten by the rules + * + * e.apply() ==> e if e.apply() is an argument to a call-by-name parameter + * expr ==> () => expr if other expr is an argument to a call-by-name parameter + * + * This makes the argument compatible with a parameter type of () => T, which will be the + * formal parameter type at erasure. But to be -Ycheckable until then, any argument + * ARG rewritten by the rules above is again wrapped in an application DummyApply(ARG) + * where + * + * DummyApply: [T](() => T): T + * + * is a synthetic method defined in Definitions. Erasure will later strip these DummyApply wrappers. + * + * Note: This scheme to have inconsistent types between method types (whose formal types are still + * ExprTypes and parameter valdefs (which are now FunctionTypes) is not pretty. There are two + * other options which have been abandoned or not yet pursued. + * + * Option 1: Transform => T to () => T also in method and function types. The problem with this is + * that is that it requires to look at every type, and this forces too much, causing + * Cyclic Reference errors. Abandoned for this reason. + * + * Option 2: Merge ElimByName with erasure, or have it run immediately before. This has not been + * tried yet. + */ +class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransformer => + import ast.tpd._ + + override def phaseName: String = "elimByName" + + override def runsAfterGroupsOf = Set(classOf[Splitter]) + // assumes idents and selects have symbols; interferes with splitter distribution + // that's why it's "after group". + + /** The info of the tree's symbol at phase Nullarify (i.e. before transformation) */ + private def originalDenotation(tree: Tree)(implicit ctx: Context) = + tree.symbol.denot(ctx.withPhase(thisTransformer)) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = + ctx.traceIndented(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { + + def transformArg(arg: Tree, formal: Type): Tree = formal.dealias match { + case formalExpr: ExprType => + val argType = arg.tpe.widen + val argFun = arg match { + case Apply(Select(qual, nme.apply), Nil) + if qual.tpe.derivesFrom(defn.FunctionClass(0)) && isPureExpr(qual) => + qual + case _ => + val inSuper = if (ctx.mode.is(Mode.InSuperCall)) InSuperCall else EmptyFlags + val meth = ctx.newSymbol( + ctx.owner, nme.ANON_FUN, Synthetic | Method | inSuper, MethodType(Nil, Nil, argType)) + Closure(meth, _ => arg.changeOwner(ctx.owner, meth)) + } + ref(defn.dummyApply).appliedToType(argType).appliedTo(argFun) + case _ => + arg + } + + val MethodType(_, formals) = tree.fun.tpe.widen + val args1 = tree.args.zipWithConserve(formals)(transformArg) + cpy.Apply(tree)(tree.fun, args1) + } + + /** If denotation had an ExprType before, it now gets a function type */ + private def exprBecomesFunction(symd: SymDenotation)(implicit ctx: Context) = + (symd is Param) || (symd is (ParamAccessor, butNot = Method)) + + /** Map `tree` to `tree.apply()` is `ftree` was of ExprType and becomes now a function */ + private def applyIfFunction(tree: Tree, ftree: Tree)(implicit ctx: Context) = { + val origDenot = originalDenotation(ftree) + if (exprBecomesFunction(origDenot) && (origDenot.info.isInstanceOf[ExprType])) + tree.select(defn.Function0_apply).appliedToNone + else tree + } + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + applyIfFunction(tree, tree) + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { + case TypeApply(Select(_, nme.asInstanceOf_), arg :: Nil) => + // tree might be of form e.asInstanceOf[x.type] where x becomes a function. + // See pos/t296.scala + applyIfFunction(tree, arg) + case _ => tree + } + + override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = + if (exprBecomesFunction(tree.symbol)) + cpy.ValDef(tree)(tpt = tree.tpt.withType(tree.symbol.info)) + else tree + + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + case ExprType(rt) if exprBecomesFunction(sym) => defn.FunctionOf(Nil, rt) + case _ => tp + } + + override def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isTerm +} diff --git a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala new file mode 100644 index 000000000..24c8cdc8d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -0,0 +1,84 @@ +package dotty.tools.dotc +package transform + +import ast.{Trees, tpd} +import core._, core.Decorators._ +import TreeTransforms._, Phases.Phase +import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import TypeErasure.ErasedValueType, ValueClasses._ + +/** This phase erases ErasedValueType to their underlying type. + * It also removes the synthetic cast methods u2evt$ and evt2u$ which are + * no longer needed afterwards. + */ +class ElimErasedValueType extends MiniPhaseTransform with InfoTransformer { + + import tpd._ + + override def phaseName: String = "elimErasedValueType" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) + + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = sym match { + case sym: ClassSymbol if sym is ModuleClass => + sym.companionClass match { + case origClass: ClassSymbol if isDerivedValueClass(origClass) => + val cinfo = tp.asInstanceOf[ClassInfo] + val decls1 = cinfo.decls.cloneScope + ctx.atPhase(this.next) { implicit ctx => + // Remove synthetic cast methods introduced by ExtensionMethods, + // they are no longer needed after this phase. + decls1.unlink(cinfo.decl(nme.U2EVT).symbol) + decls1.unlink(cinfo.decl(nme.EVT2U).symbol) + } + cinfo.derivedClassInfo(decls = decls1) + case _ => + tp + } + case _ => + elimEVT(tp) + } + + def elimEVT(tp: Type)(implicit ctx: Context): Type = tp match { + case ErasedValueType(_, underlying) => + elimEVT(underlying) + case tp: MethodType => + val paramTypes = tp.paramTypes.mapConserve(elimEVT) + val retType = elimEVT(tp.resultType) + tp.derivedMethodType(tp.paramNames, paramTypes, retType) + case _ => + tp + } + + def transformTypeOfTree(tree: Tree)(implicit ctx: Context): Tree = + tree.withType(elimEVT(tree.tpe)) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = { + val Apply(fun, args) = tree + + // The casts to and from ErasedValueType are no longer needed once ErasedValueType + // has been eliminated. + val t = + if (fun.symbol.isValueClassConvertMethod) + args.head + else + tree + transformTypeOfTree(t) + } + + override def transformInlined(tree: Inlined)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + + // FIXME: transformIf and transformBlock won't be required anymore once #444 is fixed. + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformIf(tree: If)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformTypeTree(tree: TypeTree)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) +} diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala new file mode 100644 index 000000000..258b7f234 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -0,0 +1,135 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import StdNames.nme +import Types._ +import dotty.tools.dotc.transform.TreeTransforms.{AnnotationTransformer, TransformerInfo, MiniPhaseTransform, TreeTransformer} +import ast.Trees._ +import Flags._ +import Contexts.Context +import Symbols._ +import Constants._ +import Denotations._, SymDenotations._ +import Decorators.StringInterpolators +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Annotations.ConcreteAnnotation +import scala.collection.mutable +import DenotTransformers._ +import Names.Name +import NameOps._ +import TypeUtils._ + +/** A transformer that removes repeated parameters (T*) from all types, replacing + * them with Seq types. + */ +class ElimRepeated extends MiniPhaseTransform with InfoTransformer with AnnotationTransformer { thisTransformer => + import ast.tpd._ + + override def phaseName = "elimRepeated" + + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = + elimRepeated(tp) + + override def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym is Method + + private def elimRepeated(tp: Type)(implicit ctx: Context): Type = tp.stripTypeVar match { + case tp @ MethodType(paramNames, paramTypes) => + val resultType1 = elimRepeated(tp.resultType) + val paramTypes1 = + if (paramTypes.nonEmpty && paramTypes.last.isRepeatedParam) { + val last = paramTypes.last.underlyingIfRepeated(tp.isJava) + paramTypes.init :+ last + } else paramTypes + tp.derivedMethodType(paramNames, paramTypes1, resultType1) + case tp: PolyType => + tp.derivedPolyType(tp.paramNames, tp.paramBounds, elimRepeated(tp.resultType)) + case tp => + tp + } + + def transformTypeOfTree(tree: Tree)(implicit ctx: Context): Tree = + tree.withType(elimRepeated(tree.tpe)) + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = { + val args1 = tree.args.map { + case arg: Typed if isWildcardStarArg(arg) => + if (tree.fun.symbol.is(JavaDefined) && arg.expr.tpe.derivesFrom(defn.SeqClass)) + seqToArray(arg.expr) + else arg.expr + case arg => arg + } + transformTypeOfTree(cpy.Apply(tree)(tree.fun, args1)) + } + + /** Convert sequence argument to Java array */ + private def seqToArray(tree: Tree)(implicit ctx: Context): Tree = tree match { + case SeqLiteral(elems, elemtpt) => + JavaSeqLiteral(elems, elemtpt) + case _ => + val elemType = tree.tpe.elemType + var elemClass = elemType.classSymbol + if (defn.PhantomClasses contains elemClass) elemClass = defn.ObjectClass + ref(defn.DottyArraysModule) + .select(nme.seqToArray) + .appliedToType(elemType) + .appliedTo(tree, Literal(Constant(elemClass.typeRef))) + .ensureConforms(defn.ArrayOf(elemType)) + // Because of phantomclasses, the Java array's type might not conform to the return type + } + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + + /** If method overrides a Java varargs method, add a varargs bridge. + * Also transform trees inside method annotation + */ + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + assert(ctx.phase == thisTransformer) + def overridesJava = tree.symbol.allOverriddenSymbols.exists(_ is JavaDefined) + if (tree.symbol.info.isVarArgsMethod && overridesJava) + addVarArgsBridge(tree)(ctx.withPhase(thisTransformer.next)) + else + tree + } + + /** Add a Java varargs bridge + * @param ddef the original method definition which is assumed to override + * a Java varargs method JM up to this phase. + * @return a thicket consisting of `ddef` and a varargs bridge method + * which overrides the Java varargs method JM from this phase on + * and forwards to `ddef`. + */ + private def addVarArgsBridge(ddef: DefDef)(implicit ctx: Context): Tree = { + val original = ddef.symbol.asTerm + val bridge = original.copy( + flags = ddef.symbol.flags &~ Private | Artifact, + info = toJavaVarArgs(ddef.symbol.info)).enteredAfter(thisTransformer).asTerm + val bridgeDef = polyDefDef(bridge, trefs => vrefss => { + val (vrefs :+ varArgRef) :: vrefss1 = vrefss + val elemtp = varArgRef.tpe.widen.argTypes.head + ref(original.termRef) + .appliedToTypes(trefs) + .appliedToArgs(vrefs :+ TreeGen.wrapArray(varArgRef, elemtp)) + .appliedToArgss(vrefss1) + }) + Thicket(ddef, bridgeDef) + } + + /** Convert type from Scala to Java varargs method */ + private def toJavaVarArgs(tp: Type)(implicit ctx: Context): Type = tp match { + case tp: PolyType => + tp.derivedPolyType(tp.paramNames, tp.paramBounds, toJavaVarArgs(tp.resultType)) + case tp: MethodType => + val inits :+ last = tp.paramTypes + val last1 = last.underlyingIfRepeated(isJava = true) + tp.derivedMethodType(tp.paramNames, inits :+ last1, tp.resultType) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ElimStaticThis.scala b/compiler/src/dotty/tools/dotc/transform/ElimStaticThis.scala new file mode 100644 index 000000000..0601e0122 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ElimStaticThis.scala @@ -0,0 +1,40 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts.Context +import Flags._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import TreeTransforms.{MiniPhaseTransform, TransformerInfo} +import dotty.tools.dotc.core.Types.{ThisType, TermRef} + +/** Replace This references to module classes in static methods by global identifiers to the + * corresponding modules. + */ +class ElimStaticThis extends MiniPhaseTransform { + import ast.tpd._ + def phaseName: String = "elimStaticThis" + + override def transformThis(tree: This)(implicit ctx: Context, info: TransformerInfo): Tree = + if (!tree.symbol.is(Package) && ctx.owner.enclosingMethod.is(JavaStatic)) { + assert(tree.symbol.is(ModuleClass)) + ref(tree.symbol.sourceModule) + } + else tree + + override def transformIdent(tree: tpd.Ident)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (ctx.owner.enclosingMethod.is(JavaStatic)) { + tree.tpe match { + case TermRef(thiz: ThisType, _) if thiz.cls.is(ModuleClass) => + ref(thiz.cls.sourceModule).select(tree.symbol) + case TermRef(thiz: ThisType, _) => + assert(tree.symbol.is(Flags.JavaStatic)) + tree + case _ => tree + } + } + else tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala new file mode 100644 index 000000000..069176111 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -0,0 +1,664 @@ +package dotty.tools.dotc +package transform + +import core.Phases._ +import core.DenotTransformers._ +import core.Denotations._ +import core.SymDenotations._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Names._ +import core.StdNames._ +import core.NameOps._ +import core.Decorators._ +import core.Constants._ +import typer.NoChecking +import typer.ProtoTypes._ +import typer.ErrorReporting._ +import core.TypeErasure._ +import core.Decorators._ +import dotty.tools.dotc.ast.{Trees, tpd, untpd} +import ast.Trees._ +import scala.collection.mutable.ListBuffer +import dotty.tools.dotc.core.{Constants, Flags} +import ValueClasses._ +import TypeUtils._ +import ExplicitOuter._ +import core.Mode + +class Erasure extends Phase with DenotTransformer { thisTransformer => + + override def phaseName: String = "erasure" + + /** List of names of phases that should precede this phase */ + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[InterceptedMethods], classOf[Splitter], classOf[ElimRepeated]) + + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { + case ref: SymDenotation => + assert(ctx.phase == this, s"transforming $ref at ${ctx.phase}") + if (ref.symbol eq defn.ObjectClass) { + // Aftre erasure, all former Any members are now Object members + val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info + val extendedScope = decls.cloneScope + for (decl <- defn.AnyClass.classInfo.decls) + if (!decl.isConstructor) extendedScope.enter(decl) + ref.copySymDenotation( + info = transformInfo(ref.symbol, + ClassInfo(pre, defn.ObjectClass, ps, extendedScope, selfInfo)) + ) + } + else { + val oldSymbol = ref.symbol + val newSymbol = + if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor) + defn.ObjectClass.primaryConstructor + else oldSymbol + val oldOwner = ref.owner + val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner + val oldInfo = ref.info + val newInfo = transformInfo(ref.symbol, oldInfo) + val oldFlags = ref.flags + val newFlags = ref.flags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading + // TODO: define derivedSymDenotation? + if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldInfo eq newInfo) && (oldFlags == newFlags)) ref + else { + assert(!ref.is(Flags.PackageClass), s"trans $ref @ ${ctx.phase} oldOwner = $oldOwner, newOwner = $newOwner, oldInfo = $oldInfo, newInfo = $newInfo ${oldOwner eq newOwner} ${oldInfo eq newInfo}") + ref.copySymDenotation(symbol = newSymbol, owner = newOwner, initFlags = newFlags, info = newInfo) + } + } + case ref => + ref.derivedSingleDenotation(ref.symbol, transformInfo(ref.symbol, ref.info)) + } + + val eraser = new Erasure.Typer + + def run(implicit ctx: Context): Unit = { + val unit = ctx.compilationUnit + unit.tpdTree = eraser.typedExpr(unit.tpdTree)(ctx.fresh.setPhase(this.next)) + } + + override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context) = { + assertErased(tree) + tree match { + case res: tpd.This => + assert(!ExplicitOuter.referencesOuter(ctx.owner.enclosingClass, res), + i"Reference to $res from ${ctx.owner.showLocated}") + case ret: tpd.Return => + // checked only after erasure, as checking before erasure is complicated + // due presence of type params in returned types + val from = if (ret.from.isEmpty) ctx.owner.enclosingMethod else ret.from.symbol + val rType = from.info.finalResultType + assert(ret.expr.tpe <:< rType, + i"Returned value:${ret.expr} does not conform to result type(${ret.expr.tpe.widen} of method $from") + case _ => + } + } + + /** Assert that tree type and its widened underlying type are erased. + * Also assert that term refs have fixed symbols (so we are sure + * they need not be reloaded using member; this would likely fail as signatures + * may change after erasure). + */ + def assertErased(tree: tpd.Tree)(implicit ctx: Context): Unit = { + assertErased(tree.typeOpt, tree) + if (!defn.isPolymorphicAfterErasure(tree.symbol)) + assertErased(tree.typeOpt.widen, tree) + if (ctx.mode.isExpr) + tree.tpe match { + case ref: TermRef => + assert(ref.denot.isInstanceOf[SymDenotation] || + ref.denot.isInstanceOf[UniqueRefDenotation], + i"non-sym type $ref of class ${ref.getClass} with denot of class ${ref.denot.getClass} of $tree") + case _ => + } + } + + def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(implicit ctx: Context): Unit = + if (tp.typeSymbol == defn.ArrayClass && + ctx.compilationUnit.source.file.name == "Array.scala") {} // ok + else + assert(isErasedType(tp), + i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}") +} + +object Erasure extends TypeTestsCasts{ + + import tpd._ + + object Boxing { + + def isUnbox(sym: Symbol)(implicit ctx: Context) = + sym.name == nme.unbox && sym.owner.linkedClass.isPrimitiveValueClass + + def isBox(sym: Symbol)(implicit ctx: Context) = + sym.name == nme.box && sym.owner.linkedClass.isPrimitiveValueClass + + def boxMethod(cls: ClassSymbol)(implicit ctx: Context) = + cls.linkedClass.info.member(nme.box).symbol + def unboxMethod(cls: ClassSymbol)(implicit ctx: Context) = + cls.linkedClass.info.member(nme.unbox).symbol + + /** Isf this tree is an unbox operation which can be safely removed + * when enclosed in a box, the unboxed argument, otherwise EmptyTree. + * Note that one can't always remove a Box(Unbox(x)) combination because the + * process of unboxing x may lead to throwing an exception. + * This is important for specialization: calls to the super constructor should not box/unbox specialized + * fields (see TupleX). (ID) + */ + private def safelyRemovableUnboxArg(tree: Tree)(implicit ctx: Context): Tree = tree match { + case Apply(fn, arg :: Nil) + if isUnbox(fn.symbol) && defn.ScalaBoxedClasses().contains(arg.tpe.widen.typeSymbol) => + arg + case _ => + EmptyTree + } + + def constant(tree: Tree, const: Tree)(implicit ctx: Context) = + if (isPureExpr(tree)) const else Block(tree :: Nil, const) + + final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { + tree.tpe.widen match { + case ErasedValueType(tycon, _) => + New(tycon, cast(tree, underlyingOfValueClass(tycon.symbol.asClass)) :: Nil) // todo: use adaptToType? + case tp => + val cls = tp.classSymbol + if (cls eq defn.UnitClass) constant(tree, ref(defn.BoxedUnit_UNIT)) + else if (cls eq defn.NothingClass) tree // a non-terminating expression doesn't need boxing + else { + assert(cls ne defn.ArrayClass) + val arg = safelyRemovableUnboxArg(tree) + if (arg.isEmpty) ref(boxMethod(cls.asClass)).appliedTo(tree) + else { + ctx.log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") + arg + } + } + } + } + + def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") { + pt match { + case ErasedValueType(tycon, underlying) => + def unboxedTree(t: Tree) = + adaptToType(t, tycon) + .select(valueClassUnbox(tycon.symbol.asClass)) + .appliedToNone + + // Null unboxing needs to be treated separately since we cannot call a method on null. + // "Unboxing" null to underlying is equivalent to doing null.asInstanceOf[underlying] + // See tests/pos/valueclasses/nullAsInstanceOfVC.scala for cases where this might happen. + val tree1 = + if (tree.tpe isRef defn.NullClass) + adaptToType(tree, underlying) + else if (!(tree.tpe <:< tycon)) { + assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass)) + val nullTree = Literal(Constant(null)) + val unboxedNull = adaptToType(nullTree, underlying) + + evalOnce(tree) { t => + If(t.select(defn.Object_eq).appliedTo(nullTree), + unboxedNull, + unboxedTree(t)) + } + } else unboxedTree(tree) + + cast(tree1, pt) + case _ => + val cls = pt.widen.classSymbol + if (cls eq defn.UnitClass) constant(tree, Literal(Constant(()))) + else { + assert(cls ne defn.ArrayClass) + ref(unboxMethod(cls.asClass)).appliedTo(tree) + } + } + } + + /** Generate a synthetic cast operation from tree.tpe to pt. + * Does not do any boxing/unboxing (this is handled upstream). + * Casts from and to ErasedValueType are special, see the explanation + * in ExtensionMethods#transform. + */ + def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + // TODO: The commented out assertion fails for tailcall/t6574.scala + // Fix the problem and enable the assertion. + // assert(!pt.isInstanceOf[SingletonType], pt) + if (pt isRef defn.UnitClass) unbox(tree, pt) + else (tree.tpe, pt) match { + case (JavaArrayType(treeElem), JavaArrayType(ptElem)) + if treeElem.widen.isPrimitiveValueType && !ptElem.isPrimitiveValueType => + // See SI-2386 for one example of when this might be necessary. + cast(ref(defn.runtimeMethodRef(nme.toObjectArray)).appliedTo(tree), pt) + case (_, ErasedValueType(tycon, _)) => + ref(u2evt(tycon.symbol.asClass)).appliedTo(tree) + case _ => + tree.tpe.widen match { + case ErasedValueType(tycon, _) => + ref(evt2u(tycon.symbol.asClass)).appliedTo(tree) + case _ => + if (pt.isPrimitiveValueType) + primitiveConversion(tree, pt.classSymbol) + else + tree.asInstance(pt) + } + } + } + + /** Adaptation of an expression `e` to an expected type `PT`, applying the following + * rewritings exhaustively as long as the type of `e` is not a subtype of `PT`. + * + * e -> e() if `e` appears not as the function part of an application + * e -> box(e) if `e` is of erased value type + * e -> unbox(e, PT) otherwise, if `PT` is an erased value type + * e -> box(e) if `e` is of primitive type and `PT` is not a primitive type + * e -> unbox(e, PT) if `PT` is a primitive type and `e` is not of primitive type + * e -> cast(e, PT) otherwise + */ + def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree = + if (pt.isInstanceOf[FunProto]) tree + else tree.tpe.widen match { + case MethodType(Nil, _) if tree.isTerm => + adaptToType(tree.appliedToNone, pt) + case tpw => + if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt) + tree + else if (tpw.isErasedValueType) + adaptToType(box(tree), pt) + else if (pt.isErasedValueType) + adaptToType(unbox(tree, pt), pt) + else if (tpw.isPrimitiveValueType && !pt.isPrimitiveValueType) + adaptToType(box(tree), pt) + else if (pt.isPrimitiveValueType && !tpw.isPrimitiveValueType) + adaptToType(unbox(tree, pt), pt) + else + cast(tree, pt) + } + } + + class Typer extends typer.ReTyper with NoChecking { + import Boxing._ + + def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = { + val tp = tree.typeOpt + if (tree.isTerm) erasedRef(tp) else valueErasure(tp) + } + + override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = { + assert(tree.hasType) + val erased = erasedType(tree) + ctx.log(s"promoting ${tree.show}: ${erased.showWithUnderlying()}") + tree.withType(erased) + } + + /** When erasing most TypeTrees we should not semi-erase value types. + * This is not the case for [[DefDef#tpt]], [[ValDef#tpt]] and [[Typed#tpt]], they + * are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]]. + */ + override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = + tree.withType(erasure(tree.tpe)) + + /** This override is only needed to semi-erase type ascriptions */ + override def typedTyped(tree: untpd.Typed, pt: Type)(implicit ctx: Context): Tree = { + val Typed(expr, tpt) = tree + val tpt1 = promote(tpt) + val expr1 = typed(expr, tpt1.tpe) + assignType(untpd.cpy.Typed(tree)(expr1, tpt1), tpt1) + } + + override def typedLiteral(tree: untpd.Literal)(implicit ctx: Context): Literal = + if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt) + else if (tree.const.tag == Constants.ClazzTag) Literal(Constant(erasure(tree.const.typeValue))) + else super.typedLiteral(tree) + + /** Type check select nodes, applying the following rewritings exhaustively + * on selections `e.m`, where `OT` is the type of the owner of `m` and `ET` + * is the erased type of the selection's original qualifier expression. + * + * e.m1 -> e.m2 if `m1` is a member of Any or AnyVal and `m2` is + * the same-named member in Object. + * e.m -> box(e).m if `e` is primitive and `m` is a member or a reference class + * or `e` has an erased value class type. + * e.m -> unbox(e).m if `e` is not primitive and `m` is a member of a primtive type. + * e.m -> cast(e, OT).m if the type of `e` does not conform to OT and `m` + * is not an array operation. + * + * If `m` is an array operation, i.e. one of the members apply, update, length, clone, and + * <init> of class Array, we additionally try the following rewritings: + * + * e.m -> runtime.array_m(e) if ET is Object + * e.m -> cast(e, ET).m if the type of `e` does not conform to ET + * e.clone -> e.clone' where clone' is Object's clone method + * e.m -> e.[]m if `m` is an array operation other than `clone`. + */ + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + val sym = tree.symbol + assert(sym.exists, tree.show) + + def select(qual: Tree, sym: Symbol): Tree = { + val name = tree.typeOpt match { + case tp: NamedType if tp.name.isShadowedName => sym.name.shadowedName + case _ => sym.name + } + untpd.cpy.Select(tree)(qual, sym.name) + .withType(NamedType.withFixedSym(qual.tpe, sym)) + } + + def selectArrayMember(qual: Tree, erasedPre: Type): Tree = + if (erasedPre isRef defn.ObjectClass) + runtimeCallWithProtoArgs(tree.name.genericArrayOp, pt, qual) + else if (!(qual.tpe <:< erasedPre)) + selectArrayMember(cast(qual, erasedPre), erasedPre) + else + assignType(untpd.cpy.Select(tree)(qual, tree.name.primitiveArrayOp), qual) + + def adaptIfSuper(qual: Tree): Tree = qual match { + case Super(thisQual, untpd.EmptyTypeIdent) => + val SuperType(thisType, supType) = qual.tpe + if (sym.owner is Flags.Trait) + cpy.Super(qual)(thisQual, untpd.Ident(sym.owner.asClass.name)) + .withType(SuperType(thisType, sym.owner.typeRef)) + else + qual.withType(SuperType(thisType, thisType.firstParent)) + case _ => + qual + } + + def recur(qual: Tree): Tree = { + val qualIsPrimitive = qual.tpe.widen.isPrimitiveValueType + val symIsPrimitive = sym.owner.isPrimitiveValueClass + if ((sym.owner eq defn.AnyClass) || (sym.owner eq defn.AnyValClass)) { + assert(sym.isConstructor, s"${sym.showLocated}") + select(qual, defn.ObjectClass.info.decl(sym.name).symbol) + } + else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType) + recur(box(qual)) + else if (!qualIsPrimitive && symIsPrimitive) + recur(unbox(qual, sym.owner.typeRef)) + else if (sym.owner eq defn.ArrayClass) + selectArrayMember(qual, erasure(tree.qualifier.typeOpt.widen.finalResultType)) + else { + val qual1 = adaptIfSuper(qual) + if (qual1.tpe.derivesFrom(sym.owner) || qual1.isInstanceOf[Super]) + select(qual1, sym) + else + recur(cast(qual1, sym.owner.typeRef)) + } + } + + recur(typed(tree.qualifier, AnySelectionProto)) + } + + override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = + if (tree.symbol == ctx.owner.enclosingClass || tree.symbol.isStaticOwner) promote(tree) + else { + ctx.log(i"computing outer path from ${ctx.owner.ownersIterator.toList}%, % to ${tree.symbol}, encl class = ${ctx.owner.enclosingClass}") + outer.path(tree.symbol) + } + + private def runtimeCallWithProtoArgs(name: Name, pt: Type, args: Tree*)(implicit ctx: Context): Tree = { + val meth = defn.runtimeMethodRef(name) + val followingParams = meth.symbol.info.firstParamTypes.drop(args.length) + val followingArgs = protoArgs(pt).zipWithConserve(followingParams)(typedExpr).asInstanceOf[List[tpd.Tree]] + ref(meth).appliedToArgs(args.toList ++ followingArgs) + } + + private def protoArgs(pt: Type): List[untpd.Tree] = pt match { + case pt: FunProto => pt.args ++ protoArgs(pt.resType) + case _ => Nil + } + + override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context) = { + val ntree = interceptTypeApply(tree.asInstanceOf[TypeApply])(ctx.withPhase(ctx.erasurePhase)) + + ntree match { + case TypeApply(fun, args) => + val fun1 = typedExpr(fun, WildcardType) + fun1.tpe.widen match { + case funTpe: PolyType => + val args1 = args.mapconserve(typedType(_)) + untpd.cpy.TypeApply(tree)(fun1, args1).withType(funTpe.instantiate(args1.tpes)) + case _ => fun1 + } + case _ => typedExpr(ntree, pt) + } + } + + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { + val Apply(fun, args) = tree + if (fun.symbol == defn.dummyApply) + typedUnadapted(args.head, pt) + else typedExpr(fun, FunProto(args, pt, this)) match { + case fun1: Apply => // arguments passed in prototype were already passed + fun1 + case fun1 => + fun1.tpe.widen match { + case mt: MethodType => + val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased + val args1 = (outers ::: args ++ protoArgs(pt)).zipWithConserve(mt.paramTypes)(typedExpr) + untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType + case _ => + throw new MatchError(i"tree $tree has unexpected type of function ${fun1.tpe.widen}, was ${fun.typeOpt.widen}") + } + } + } + + // The following four methods take as the proto-type the erasure of the pre-existing type, + // if the original proto-type is not a value type. + // This makes all branches be adapted to the correct type. + override def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(implicit ctx: Context) = + super.typedSeqLiteral(tree, erasure(tree.typeOpt)) + // proto type of typed seq literal is original type; + + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = + super.typedIf(tree, adaptProto(tree, pt)) + + override def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context) = + super.typedMatch(tree, adaptProto(tree, pt)) + + override def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context) = + super.typedTry(tree, adaptProto(tree, pt)) + + private def adaptProto(tree: untpd.Tree, pt: Type)(implicit ctx: Context) = { + if (pt.isValueType) pt else { + if (tree.typeOpt.derivesFrom(ctx.definitions.UnitClass)) + tree.typeOpt + else valueErasure(tree.typeOpt) + } + } + + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): ValDef = + super.typedValDef(untpd.cpy.ValDef(vdef)( + tpt = untpd.TypedSplice(TypeTree(sym.info).withPos(vdef.tpt.pos))), sym) + + override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = { + val restpe = + if (sym.isConstructor) defn.UnitType + else sym.info.resultType + val ddef1 = untpd.cpy.DefDef(ddef)( + tparams = Nil, + vparamss = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil, + tpt = untpd.TypedSplice(TypeTree(restpe).withPos(ddef.tpt.pos)), + rhs = ddef.rhs match { + case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe)) + case _ => ddef.rhs + }) + super.typedDefDef(ddef1, sym) + } + + /** After erasure, we may have to replace the closure method by a bridge. + * LambdaMetaFactory handles this automatically for most types, but we have + * to deal with boxing and unboxing of value classes ourselves. + */ + override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = { + val implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt) + implClosure.tpe match { + case SAMType(sam) => + val implType = meth.tpe.widen + + val List(implParamTypes) = implType.paramTypess + val List(samParamTypes) = sam.info.paramTypess + val implResultType = implType.resultType + val samResultType = sam.info.resultType + + // Given a value class V with an underlying type U, the following code: + // val f: Function1[V, V] = x => ... + // results in the creation of a closure and a method: + // def $anonfun(v1: V): V = ... + // val f: Function1[V, V] = closure($anonfun) + // After [[Erasure]] this method will look like: + // def $anonfun(v1: ErasedValueType(V, U)): ErasedValueType(V, U) = ... + // And after [[ElimErasedValueType]] it will look like: + // def $anonfun(v1: U): U = ... + // This method does not implement the SAM of Function1[V, V] anymore and + // needs to be replaced by a bridge: + // def $anonfun$2(v1: V): V = new V($anonfun(v1.underlying)) + // val f: Function1 = closure($anonfun$2) + // In general, a bridge is needed when the signature of the closure method after + // Erasure contains an ErasedValueType but the corresponding type in the functional + // interface is not an ErasedValueType. + val bridgeNeeded = + (implResultType :: implParamTypes, samResultType :: samParamTypes).zipped.exists( + (implType, samType) => implType.isErasedValueType && !samType.isErasedValueType + ) + + if (bridgeNeeded) { + val bridge = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Flags.Synthetic | Flags.Method, sam.info) + val bridgeCtx = ctx.withOwner(bridge) + Closure(bridge, bridgeParamss => { + implicit val ctx: Context = bridgeCtx + + val List(bridgeParams) = bridgeParamss + val rhs = Apply(meth, (bridgeParams, implParamTypes).zipped.map(adapt(_, _))) + adapt(rhs, sam.info.resultType) + }) + } else implClosure + case _ => + implClosure + } + } + + override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context) = + EmptyTree + + override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + val stats1 = Trees.flatten(super.typedStats(stats, exprOwner)) + if (ctx.owner.isClass) stats1 ::: addBridges(stats, stats1)(ctx) else stats1 + } + + // this implementation doesn't check for bridge clashes with value types! + def addBridges(oldStats: List[untpd.Tree], newStats: List[tpd.Tree])(implicit ctx: Context): List[tpd.Tree] = { + val beforeCtx = ctx.withPhase(ctx.erasurePhase) + def traverse(after: List[Tree], before: List[untpd.Tree], + emittedBridges: ListBuffer[tpd.DefDef] = ListBuffer[tpd.DefDef]()): List[tpd.DefDef] = { + after match { + case Nil => emittedBridges.toList + case (member: DefDef) :: newTail => + before match { + case Nil => emittedBridges.toList + case (oldMember: untpd.DefDef) :: oldTail => + try { + val oldSymbol = oldMember.symbol(beforeCtx) + val newSymbol = member.symbol(ctx) + assert(oldSymbol.name(beforeCtx) == newSymbol.name, + s"${oldSymbol.name(beforeCtx)} bridging with ${newSymbol.name}") + val newOverridden = oldSymbol.denot.allOverriddenSymbols.toSet // TODO: clarify new <-> old in a comment; symbols are swapped here + val oldOverridden = newSymbol.allOverriddenSymbols(beforeCtx).toSet // TODO: can we find a more efficient impl? newOverridden does not have to be a set! + def stillInBaseClass(sym: Symbol) = ctx.owner derivesFrom sym.owner + val neededBridges = (oldOverridden -- newOverridden).filter(stillInBaseClass) + + var minimalSet = Set[Symbol]() + // compute minimal set of bridges that are needed: + for (bridge <- neededBridges) { + val isRequired = minimalSet.forall(nxtBridge => !(bridge.info =:= nxtBridge.info)) + + if (isRequired) { + // check for clashes + val clash: Option[Symbol] = oldSymbol.owner.info.decls.lookupAll(bridge.name).find { + sym => + (sym.name eq bridge.name) && sym.info.widen =:= bridge.info.widen + }.orElse( + emittedBridges.find(stat => (stat.name == bridge.name) && stat.tpe.widen =:= bridge.info.widen) + .map(_.symbol)) + clash match { + case Some(cl) => + ctx.error(i"bridge for method ${newSymbol.showLocated(beforeCtx)} of type ${newSymbol.info(beforeCtx)}\n" + + i"clashes with ${cl.symbol.showLocated(beforeCtx)} of type ${cl.symbol.info(beforeCtx)}\n" + + i"both have same type after erasure: ${bridge.symbol.info}") + case None => minimalSet += bridge + } + } + } + + val bridgeImplementations = minimalSet.map { + sym => makeBridgeDef(member, sym)(ctx) + } + emittedBridges ++= bridgeImplementations + } catch { + case ex: MergeError => ctx.error(ex.getMessage, member.pos) + } + + traverse(newTail, oldTail, emittedBridges) + case notADefDef :: oldTail => + traverse(after, oldTail, emittedBridges) + } + case notADefDef :: newTail => + traverse(newTail, before, emittedBridges) + } + } + + traverse(newStats, oldStats) + } + + private final val NoBridgeFlags = Flags.Accessor | Flags.Deferred | Flags.Lazy | Flags.ParamAccessor + + /** Create a bridge DefDef which overrides a parent method. + * + * @param newDef The DefDef which needs bridging because its signature + * does not match the parent method signature + * @param parentSym A symbol corresponding to the parent method to override + * @return A new DefDef whose signature matches the parent method + * and whose body only contains a call to newDef + */ + def makeBridgeDef(newDef: tpd.DefDef, parentSym: Symbol)(implicit ctx: Context): tpd.DefDef = { + val newDefSym = newDef.symbol + val currentClass = newDefSym.owner.asClass + + def error(reason: String) = { + assert(false, s"failure creating bridge from ${newDefSym} to ${parentSym}, reason: $reason") + ??? + } + var excluded = NoBridgeFlags + if (!newDefSym.is(Flags.Protected)) excluded |= Flags.Protected // needed to avoid "weaker access" assertion failures in expandPrivate + val bridge = ctx.newSymbol(currentClass, + parentSym.name, parentSym.flags &~ excluded | Flags.Bridge, parentSym.info, coord = newDefSym.owner.coord).asTerm + bridge.enteredAfter(ctx.phase.prev.asInstanceOf[DenotTransformer]) // this should be safe, as we're executing in context of next phase + ctx.debuglog(s"generating bridge from ${newDefSym} to $bridge") + + val sel: Tree = This(currentClass).select(newDefSym.termRef) + + val resultType = parentSym.info.widen.resultType + + val bridgeCtx = ctx.withOwner(bridge) + + tpd.DefDef(bridge, { paramss: List[List[tpd.Tree]] => + implicit val ctx: Context = bridgeCtx + + val rhs = paramss.foldLeft(sel)((fun, vparams) => + fun.tpe.widen match { + case MethodType(names, types) => Apply(fun, (vparams, types).zipped.map(adapt(_, _, untpd.EmptyTree))) + case a => error(s"can not resolve apply type $a") + + }) + adapt(rhs, resultType) + }) + } + + override def adapt(tree: Tree, pt: Type, original: untpd.Tree)(implicit ctx: Context): Tree = + ctx.traceIndented(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { + assert(ctx.phase == ctx.erasurePhase.next, ctx.phase) + if (tree.isEmpty) tree + else if (ctx.mode is Mode.Pattern) tree // TODO: replace with assertion once pattern matcher is active + else adaptToType(tree, pt) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala new file mode 100644 index 000000000..83cd395ff --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -0,0 +1,111 @@ +package dotty.tools.dotc +package transform + +import core._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.DenotTransformers.{SymTransformer, IdentityDenotTransformer} +import Contexts.Context +import Symbols._ +import Scopes._ +import Flags._ +import StdNames._ +import SymDenotations._ +import Types._ +import collection.mutable +import TreeTransforms._ +import Decorators._ +import ast.Trees._ +import TreeTransforms._ +import java.io.File.separatorChar +import ValueClasses._ + +/** Make private term members that are accessed from another class + * non-private by resetting the Private flag and expanding their name. + * + * Make private accessor in value class not-private. Ihis is necessary to unbox + * the value class when accessing it from separate compilation units + * + * Also, make non-private any private parameter forwarders that forward to an inherited + * public or protected parameter accessor with the same name as the forwarder. + * This is necessary since private methods are not allowed to have the same name + * as inherited public ones. + * + * See discussion in https://github.com/lampepfl/dotty/pull/784 + * and https://github.com/lampepfl/dotty/issues/783 + */ +class ExpandPrivate extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + override def phaseName: String = "expandPrivate" + + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case t: DefDef => + val sym = t.symbol + def hasWeakerAccess(other: Symbol) = { + // public > protected > /* default */ > private + if (sym.is(Private)) other.is(Private) + else if (sym.is(Protected)) other.is(Protected | Private) + else true // sym is public + } + val fail = sym.allOverriddenSymbols.findSymbol(x => !hasWeakerAccess(x)) + if (fail.exists) { + assert(false, i"${sym.showFullName}: ${sym.info} has weaker access than superclass method ${fail.showFullName}: ${fail.info}") + } + case _ => + } + } + + private def isVCPrivateParamAccessor(d: SymDenotation)(implicit ctx: Context) = + d.isTerm && d.is(PrivateParamAccessor) && isDerivedValueClass(d.owner) + + /** Make private terms accessed from different classes non-private. + * Note: this happens also for accesses between class and linked module class. + * If we change the scheme at one point to make static module class computations + * static members of the companion class, we should tighten the condition below. + */ + private def ensurePrivateAccessible(d: SymDenotation)(implicit ctx: Context) = + if (isVCPrivateParamAccessor(d)) + d.ensureNotPrivate.installAfter(thisTransform) + else if (d.is(PrivateTerm) && d.owner != ctx.owner.enclosingClass) { + // Paths `p1` and `p2` are similar if they have a common suffix that follows + // possibly different directory paths. That is, their common suffix extends + // in both cases either to the start of the path or to a file separator character. + def isSimilar(p1: String, p2: String): Boolean = { + var i = p1.length - 1 + var j = p2.length - 1 + while (i >= 0 && j >= 0 && p1(i) == p2(j) && p1(i) != separatorChar) { + i -= 1 + j -= 1 + } + (i < 0 || p1(i) == separatorChar) && + (j < 0 || p1(j) == separatorChar) + } + assert(isSimilar(d.symbol.sourceFile.path, ctx.source.file.path), + i"private ${d.symbol.showLocated} in ${d.symbol.sourceFile} accessed from ${ctx.owner.showLocated} in ${ctx.source.file}") + d.ensureNotPrivate.installAfter(thisTransform) + } + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = { + ensurePrivateAccessible(tree.symbol) + tree + } + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = { + ensurePrivateAccessible(tree.symbol) + tree + } + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo) = { + val sym = tree.symbol + tree.rhs match { + case Apply(sel @ Select(_: Super, _), _) + if sym.is(PrivateParamAccessor) && sel.symbol.is(ParamAccessor) && sym.name == sel.symbol.name => + sym.ensureNotPrivate.installAfter(thisTransform) + case _ => + if (isVCPrivateParamAccessor(sym)) + sym.ensureNotPrivate.installAfter(thisTransform) + } + tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala new file mode 100644 index 000000000..91399f91a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -0,0 +1,86 @@ +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._ + + /** Is the SAMType `cls` also a SAM under the rules of the platform? */ + def isPlatformSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = + ctx.platform.isSam(cls) + + 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 tpe.isRef(defn.PartialFunctionClass) => + toPartialFunction(tree) + case tpe @ SAMType(_) if isPlatformSam(tpe.classSymbol.asClass) => + tree + case tpe => + val Seq(samDenot) = tpe.abstractTermMembers.filter(!_.symbol.is(SuperAccessor)) + cpy.Block(tree)(stats, + AnonClass(tpe :: Nil, fn.symbol.asTerm :: Nil, samDenot.symbol.asTerm.name :: 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.isDefinedAt, + 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, Underscore(selector.tpe.widen)), + EmptyTree, + Literal(Constant(false))) + val annotated = Annotated(paramRef, New(ref(defn.UncheckedAnnotType))) + cpy.Match(applyRhs)(annotated, 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) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala new file mode 100644 index 000000000..3fec47e9f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -0,0 +1,362 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import ast.Trees._ +import SymUtils._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Phases.Phase +import util.Property +import collection.mutable + +/** This phase adds outer accessors to classes and traits that need them. + * Compared to Scala 2.x, it tries to minimize the set of classes + * that take outer accessors by scanning class implementations for + * outer references. + * + * The following things are delayed until erasure and are performed + * by class OuterOps: + * + * - add outer parameters to constructors + * - pass outer arguments in constructor calls + * + * replacement of outer this by outer paths is done in Erasure. + * needs to run after pattern matcher as it can add outer checks and force creation of $outer + */ +class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransformer => + import ExplicitOuter._ + import ast.tpd._ + + val Outer = new Property.Key[Tree] + + override def phaseName: String = "explicitOuter" + + /** List of names of phases that should have finished their processing of all compilation units + * before this phase starts + */ + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[PatternMatcher]) + + /** Add outer accessors if a class always needs an outer pointer */ + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + case tp @ ClassInfo(_, cls, _, decls, _) if needsOuterAlways(cls) && !sym.is(JavaDefined) => + val newDecls = decls.cloneScope + newOuterAccessors(cls).foreach(newDecls.enter) + tp.derivedClassInfo(decls = newDecls) + case _ => + tp + } + + override def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass + + /** Convert a selection of the form `qual.C_<OUTER>` to an outer path from `qual` to `C` */ + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = + if (tree.name.isOuterSelect) + outer.path(tree.tpe.widen.classSymbol, tree.qualifier).ensureConforms(tree.tpe) + else tree + + /** First, add outer accessors if a class does not have them yet and it references an outer this. + * If the class has outer accessors, implement them. + * Furthermore, if a parent trait might have an outer accessor, + * provide an implementation for the outer accessor by computing the parent's + * outer from the parent type prefix. If the trait ends up not having an outer accessor + * after all, the implementation is redundant, but does not harm. + * The same logic is not done for non-trait parent classes because for them the outer + * pointer is passed in the super constructor, which will be implemented later in + * a separate phase which needs to run after erasure. However, we make sure here + * that the super class constructor is indeed a New, and not just a type. + */ + override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { + val cls = ctx.owner.asClass + val isTrait = cls.is(Trait) + if (needsOuterIfReferenced(cls) && + !needsOuterAlways(cls) && + impl.existsSubTree(referencesOuter(cls, _))) + ensureOuterAccessors(cls) + if (hasOuter(cls)) { + val newDefs = new mutable.ListBuffer[Tree] + if (isTrait) + newDefs += DefDef(outerAccessor(cls).asTerm, EmptyTree) + else { + val outerParamAcc = outerParamAccessor(cls) + newDefs += ValDef(outerParamAcc, EmptyTree) + newDefs += DefDef(outerAccessor(cls).asTerm, ref(outerParamAcc)) + } + + for (parentTrait <- cls.mixins) { + if (needsOuterIfReferenced(parentTrait)) { + val parentTp = cls.denot.thisType.baseTypeRef(parentTrait) + val outerAccImpl = newOuterAccessor(cls, parentTrait).enteredAfter(thisTransformer) + newDefs += DefDef(outerAccImpl, singleton(outerPrefix(parentTp))) + } + } + + val parents1 = + for (parent <- impl.parents) yield { + val parentCls = parent.tpe.classSymbol.asClass + if (parentCls.is(Trait)) { + parent + } + else parent match { // ensure class parent is a constructor + case parent: TypeTree => New(parent.tpe, Nil).withPos(impl.pos) + case _ => parent + } + } + cpy.Template(impl)(parents = parents1, body = impl.body ++ newDefs) + } + else impl + } + + override def transformClosure(tree: Closure)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (tree.tpt ne EmptyTree) { + val cls = tree.tpt.asInstanceOf[TypeTree].tpe.classSymbol + if (cls.exists && hasOuter(cls.asClass)) + ctx.error("Not a single abstract method type, requires an outer pointer", tree.pos) + } + tree + } +} + +object ExplicitOuter { + import ast.tpd._ + + /** Ensure that class `cls` has outer accessors */ + def ensureOuterAccessors(cls: ClassSymbol)(implicit ctx: Context): Unit = { + //todo: implementing #165 would simplify this logic + val prevPhase = ctx.phase.prev + assert(prevPhase.id <= ctx.explicitOuterPhase.id, "can add $outer symbols only before ExplicitOuter") + assert(prevPhase.isInstanceOf[DenotTransformer], "adding outerAccessors requires being DenotTransformer") + if (!hasOuter(cls)) { + newOuterAccessors(cls).foreach(_.enteredAfter(prevPhase.asInstanceOf[DenotTransformer])) + } + } + + /** The outer accessor and potentially outer param accessor needed for class `cls` */ + private def newOuterAccessors(cls: ClassSymbol)(implicit ctx: Context) = + newOuterAccessor(cls, cls) :: (if (cls is Trait) Nil else newOuterParamAccessor(cls) :: Nil) + + /** A new outer accessor or param accessor */ + private def newOuterSym(owner: ClassSymbol, cls: ClassSymbol, name: TermName, flags: FlagSet)(implicit ctx: Context) = { + val target = cls.owner.enclosingClass.typeRef + val info = if (flags.is(Method)) ExprType(target) else target + ctx.newSymbol(owner, name, Synthetic | flags, info, coord = cls.coord) + } + + /** A new param accessor for the outer field in class `cls` */ + private def newOuterParamAccessor(cls: ClassSymbol)(implicit ctx: Context) = + newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor) + + /** A new outer accessor for class `cls` which is a member of `owner` */ + private def newOuterAccessor(owner: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context) = { + val deferredIfTrait = if (owner.is(Trait)) Deferred else EmptyFlags + val outerAccIfOwn = if (owner == cls) OuterAccessor else EmptyFlags + newOuterSym(owner, cls, outerAccName(cls), + Final | Method | Stable | outerAccIfOwn | deferredIfTrait) + } + + private def outerAccName(cls: ClassSymbol)(implicit ctx: Context): TermName = + nme.OUTER.expandedName(cls) + + /** Class needs an outer pointer, provided there is a reference to an outer this in it. */ + def needsOuterIfReferenced(cls: ClassSymbol)(implicit ctx: Context): Boolean = + !(cls.isStatic || + cls.owner.enclosingClass.isStaticOwner || + cls.is(PureInterface) + ) + + /** Class unconditionally needs an outer pointer. This is the case if + * the class needs an outer pointer if referenced and one of the following holds: + * - we might not know at all instantiation sites whether outer is referenced or not + * - we need to potentially pass along outer to a parent class or trait + */ + private def needsOuterAlways(cls: ClassSymbol)(implicit ctx: Context): Boolean = + needsOuterIfReferenced(cls) && + (!hasLocalInstantiation(cls) || // needs outer because we might not know whether outer is referenced or not + cls.classInfo.parents.exists(parent => // needs outer to potentially pass along to parent + needsOuterIfReferenced(parent.classSymbol.asClass))) + + /** Class is always instantiated in the compilation unit where it is defined */ + private def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean = + // scala2x modules always take an outer pointer(as of 2.11) + // dotty modules are always locally instantiated + cls.owner.isTerm || cls.is(Private) || cls.is(Module, butNot = Scala2x) + + /** The outer parameter accessor of cass `cls` */ + private def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context): TermSymbol = + cls.info.decl(nme.OUTER).symbol.asTerm + + /** The outer accessor of class `cls`. To find it is a bit tricky. The + * class might have been moved with new owners between ExplicitOuter and Erasure, + * where the method is also called. For instance, it might have been part + * of a by-name argument, and therefore be moved under a closure method + * by ElimByName. In that case looking up the method again at Erasure with the + * fully qualified name `outerAccName` will fail, because the `outerAccName`'s + * result is phase dependent. In that case we use a backup strategy where we search all + * definitions in the class to find the one with the OuterAccessor flag. + */ + def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol = + if (cls.isStatic) NoSymbol // fast return to avoid scanning package decls + else cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse + cls.info.decls.find(_ is OuterAccessor).getOrElse(NoSymbol) + + /** Class has an outer accessor. Can be called only after phase ExplicitOuter. */ + private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean = + needsOuterIfReferenced(cls) && outerAccessor(cls).exists + + /** Class constructor takes an outer argument. Can be called only after phase ExplicitOuter. */ + private def hasOuterParam(cls: ClassSymbol)(implicit ctx: Context): Boolean = + !cls.is(Trait) && needsOuterIfReferenced(cls) && outerAccessor(cls).exists + + /** Tree references an outer class of `cls` which is not a static owner. + */ + def referencesOuter(cls: Symbol, tree: Tree)(implicit ctx: Context): Boolean = { + def isOuterSym(sym: Symbol) = + !sym.isStaticOwner && cls.isProperlyContainedIn(sym) + def isOuterRef(ref: Type): Boolean = ref match { + case ref: ThisType => + isOuterSym(ref.cls) + case ref: TermRef => + if (ref.prefix ne NoPrefix) + !ref.symbol.isStatic && isOuterRef(ref.prefix) + else ( + (ref.symbol is Hoistable) && + // ref.symbol will be placed in enclosing class scope by LambdaLift, so it might need + // an outer path then. + isOuterSym(ref.symbol.owner.enclosingClass) + || + // If not hoistable, ref.symbol will get a proxy in immediately enclosing class. If this properly + // contains the current class, it needs an outer path. + // If the symbol is hoistable, it might have free variables for which the same + // reasoning applies. See pos/i1664.scala + ctx.owner.enclosingClass.owner.enclosingClass.isContainedIn(ref.symbol.owner) + ) + case _ => false + } + def hasOuterPrefix(tp: Type) = tp match { + case TypeRef(prefix, _) => isOuterRef(prefix) + case _ => false + } + tree match { + case _: This | _: Ident => isOuterRef(tree.tpe) + case nw: New => + val newCls = nw.tpe.classSymbol + isOuterSym(newCls.owner.enclosingClass) || + hasOuterPrefix(nw.tpe) || + newCls.owner.isTerm && cls.isProperlyContainedIn(newCls) + // newCls might get proxies for free variables. If current class is + // properly contained in newCls, it needs an outer path to newCls access the + // proxies and forward them to the new instance. + case _ => + false + } + } + + private final val Hoistable = Method | Lazy | Module + + /** The outer prefix implied by type `tpe` */ + private def outerPrefix(tpe: Type)(implicit ctx: Context): Type = tpe match { + case tpe: TypeRef => + tpe.symbol match { + case cls: ClassSymbol => + if (tpe.prefix eq NoPrefix) cls.owner.enclosingClass.thisType + else tpe.prefix + case _ => + outerPrefix(tpe.underlying) + } + case tpe: TypeProxy => + outerPrefix(tpe.underlying) + } + + def outer(implicit ctx: Context): OuterOps = new OuterOps(ctx) + + /** The operations in this class + * - add outer parameters + * - pass outer arguments to these parameters + * - replace outer this references by outer paths. + * They are called from erasure. There are two constraints which + * suggest these operations should be done in erasure. + * - Replacing this references with outer paths loses aliasing information, + * so programs will not typecheck with unerased types unless a lot of type + * refinements are added. Therefore, outer paths should be computed no + * earlier than erasure. + * - outer parameters should not show up in signatures, so again + * they cannot be added before erasure. + * - outer arguments need access to outer parameters as well as to the + * original type prefixes of types in New expressions. These prefixes + * get erased during erasure. Therefore, outer arguments have to be passed + * no later than erasure. + */ + class OuterOps(val ictx: Context) extends AnyVal { + private implicit def ctx: Context = ictx + + /** If `cls` has an outer parameter add one to the method type `tp`. */ + def addParam(cls: ClassSymbol, tp: Type): Type = + if (hasOuterParam(cls)) { + val mt @ MethodType(pnames, ptypes) = tp + mt.derivedMethodType( + nme.OUTER :: pnames, cls.owner.enclosingClass.typeRef :: ptypes, mt.resultType) + } else tp + + /** If function in an apply node is a constructor that needs to be passed an + * outer argument, the singleton list with the argument, otherwise Nil. + */ + def args(fun: Tree): List[Tree] = { + if (fun.symbol.isConstructor) { + val cls = fun.symbol.owner.asClass + def outerArg(receiver: Tree): Tree = receiver match { + case New(_) | Super(_, _) => + singleton(outerPrefix(receiver.tpe)) + case This(_) => + ref(outerParamAccessor(cls)) // will be rewired to outer argument of secondary constructor in phase Constructors + case TypeApply(Select(r, nme.asInstanceOf_), args) => + outerArg(r) // cast was inserted, skip + } + if (hasOuterParam(cls)) + methPart(fun) match { + case Select(receiver, _) => outerArg(receiver).withPos(fun.pos) :: Nil + } + else Nil + } else Nil + } + + /** The path of outer accessors that references `toCls.this` starting from + * the context owner's this node. + */ + def path(toCls: Symbol, start: Tree = This(ctx.owner.enclosingClass.asClass)): Tree = try { + def loop(tree: Tree): Tree = { + val treeCls = tree.tpe.widen.classSymbol + val outerAccessorCtx = ctx.withPhaseNoLater(ctx.lambdaLiftPhase) // lambdalift mangles local class names, which means we cannot reliably find outer acessors anymore + ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)(outerAccessorCtx)} in $treeCls") + if (treeCls == toCls) tree + else { + val acc = outerAccessor(treeCls.asClass)(outerAccessorCtx) + assert(acc.exists, + i"failure to construct path from ${ctx.owner.ownersIterator.toList}%/% to `this` of ${toCls.showLocated};\n${treeCls.showLocated} does not have an outer accessor") + loop(tree.select(acc).ensureApplied) + } + } + ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}") + loop(start) + } catch { + case ex: ClassCastException => + throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls") + } + + /** The outer parameter definition of a constructor if it needs one */ + def paramDefs(constr: Symbol): List[ValDef] = + if (constr.isConstructor && hasOuterParam(constr.owner.asClass)) { + val MethodType(outerName :: _, outerType :: _) = constr.info + val outerSym = ctx.newSymbol(constr, outerName, Param, outerType) + ValDef(outerSym) :: Nil + } + else Nil + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitSelf.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitSelf.scala new file mode 100644 index 000000000..7bb65e575 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitSelf.scala @@ -0,0 +1,47 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts.Context +import Types._ +import TreeTransforms._ +import Decorators._ +import ast.Trees._ +import Flags._ + +/** Transform references of the form + * + * C.this.m + * + * where `C` is a class with explicit self type and `C` is not a + * subclass of the owner of `m` to + * + * C.this.asInstanceOf[S & C.this.type].m + * + * where `S` is the self type of `C`. + * See run/i789.scala for a test case why this is needed. + * + * Also replaces idents referring to the self type with ThisTypes. + */ +class ExplicitSelf extends MiniPhaseTransform { thisTransform => + import ast.tpd._ + + override def phaseName = "explicitSelf" + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match { + case tp: ThisType => + ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}") + This(tp.cls) withPos tree.pos + case _ => tree + } + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { + case Select(thiz: This, name) if name.isTermName => + val cls = thiz.symbol.asClass + val cinfo = cls.classInfo + if (cinfo.givenSelfType.exists && !cls.derivesFrom(tree.symbol.owner)) + cpy.Select(tree)(thiz.asInstance(AndType(cinfo.selfType, thiz.tpe)), name) + else tree + case _ => tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala new file mode 100644 index 000000000..5ae4e8a54 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -0,0 +1,243 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Martin Odersky + */ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.transform.TreeTransforms._ +import ValueClasses._ +import dotty.tools.dotc.ast.{Trees, tpd} +import scala.collection.{ mutable, immutable } +import mutable.ListBuffer +import core._ +import dotty.tools.dotc.core.Phases.{NeedsCompanions, Phase} +import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import TypeErasure.{ valueErasure, ErasedValueType } +import TypeUtils._ +import util.Positions._ +import Decorators._ +import SymUtils._ + +/** + * Perform Step 1 in the inline classes SIP: Creates extension methods for all + * methods in a value class, except parameter or super accessors, or constructors. + * + * Additionally, for a value class V, let U be the underlying type after erasure. We add + * to the companion module of V two cast methods: + * def u2evt$(x0: U): ErasedValueType(V, U) + * def evt2u$(x0: ErasedValueType(V, U)): U + * The casts are used in [[Erasure]] to make it typecheck, they are then removed + * in [[ElimErasedValueType]]. + * This is different from the implementation of value classes in Scala 2 + * (see SIP-15) which uses `asInstanceOf` which does not typecheck. + * + * Finally, if the constructor of a value class is private pr protected + * it is widened to public. + */ +class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with FullParameterization { thisTransformer => + + import tpd._ + import ExtensionMethods._ + + /** the following two members override abstract members in Transform */ + override def phaseName: String = "extmethods" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimRepeated]) + + override def runsAfterGroupsOf = Set(classOf[FirstTransform]) // need companion objects to exist + + override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { + case moduleClassSym: ClassDenotation if moduleClassSym is ModuleClass => + moduleClassSym.linkedClass match { + case valueClass: ClassSymbol if isDerivedValueClass(valueClass) => + val cinfo = moduleClassSym.classInfo + val decls1 = cinfo.decls.cloneScope + val moduleSym = moduleClassSym.symbol.asClass + + var newSuperClass: Type = null + + ctx.atPhase(thisTransformer.next) { implicit ctx => + // In Scala 2, extension methods are added before pickling so we should + // not generate them again. + if (!(valueClass is Scala2x)) ctx.atPhase(thisTransformer) { implicit ctx => + for (decl <- valueClass.classInfo.decls) { + if (isMethodWithExtension(decl)) + decls1.enter(createExtensionMethod(decl, moduleClassSym.symbol)) + } + } + + val underlying = valueErasure(underlyingOfValueClass(valueClass)) + val evt = ErasedValueType(valueClass.typeRef, underlying) + val u2evtSym = ctx.newSymbol(moduleSym, nme.U2EVT, Synthetic | Method, + MethodType(List(nme.x_0), List(underlying), evt)) + val evt2uSym = ctx.newSymbol(moduleSym, nme.EVT2U, Synthetic | Method, + MethodType(List(nme.x_0), List(evt), underlying)) + + val defn = ctx.definitions + + val underlyingCls = underlying.classSymbol + val underlyingClsName = + if (underlyingCls.isNumericValueClass || underlyingCls == defn.BooleanClass) underlyingCls.name + else nme.Object + + val syp = ctx.requiredClass(s"dotty.runtime.vc.VC${underlyingClsName}Companion").asClass + + newSuperClass = tpd.ref(syp).select(nme.CONSTRUCTOR).appliedToType(valueClass.typeRef).tpe.resultType + + decls1.enter(u2evtSym) + decls1.enter(evt2uSym) + } + + // Add the extension methods, the cast methods u2evt$ and evt2u$, and a VC*Companion superclass + moduleClassSym.copySymDenotation(info = + cinfo.derivedClassInfo( + // FIXME: use of VC*Companion superclasses is disabled until the conflicts with SyntheticMethods are solved. + //classParents = ctx.normalizeToClassRefs(List(newSuperClass), moduleSym, decls1), + decls = decls1)) + case _ => + moduleClassSym + } + case ref: SymDenotation => + if (isMethodWithExtension(ref) && ref.hasAnnotation(defn.TailrecAnnot)) { + val ref1 = ref.copySymDenotation() + ref1.removeAnnotation(defn.TailrecAnnot) + ref1 + } + else if (ref.isConstructor && isDerivedValueClass(ref.owner) && ref.is(AccessFlags)) { + val ref1 = ref.copySymDenotation() + ref1.resetFlag(AccessFlags) + ref1 + } + else ref + case _ => + ref + } + + protected def rewiredTarget(target: Symbol, derived: Symbol)(implicit ctx: Context): Symbol = + if (isMethodWithExtension(target) && + target.owner.linkedClass == derived.owner) extensionMethod(target) + else NoSymbol + + private def createExtensionMethod(imeth: Symbol, staticClass: Symbol)(implicit ctx: Context): TermSymbol = { + val extensionName = extensionNames(imeth).head.toTermName + val extensionMeth = ctx.newSymbol(staticClass, extensionName, + imeth.flags | Final &~ (Override | Protected | AbsOverride), + fullyParameterizedType(imeth.info, imeth.owner.asClass), + privateWithin = imeth.privateWithin, coord = imeth.coord) + extensionMeth.addAnnotations(imeth.annotations)(ctx.withPhase(thisTransformer)) + // need to change phase to add tailrec annotation which gets removed from original method in the same phase. + extensionMeth + } + + private val extensionDefs = mutable.Map[Symbol, mutable.ListBuffer[Tree]]() + // TODO: this is state and should be per-run + // todo: check that when transformation finished map is empty + + private def checkNonCyclic(pos: Position, seen: Set[Symbol], clazz: ClassSymbol)(implicit ctx: Context): Unit = + if (seen contains clazz) + ctx.error("value class may not unbox to itself", pos) + else { + val unboxed = underlyingOfValueClass(clazz).typeSymbol + if (isDerivedValueClass(unboxed)) checkNonCyclic(pos, seen + clazz, unboxed.asClass) + } + + override def transformTemplate(tree: tpd.Template)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (isDerivedValueClass(ctx.owner)) { + /* This is currently redundant since value classes may not + wrap over other value classes anyway. + checkNonCyclic(ctx.owner.pos, Set(), ctx.owner) */ + tree + } else if (ctx.owner.isStaticOwner) { + extensionDefs remove tree.symbol.owner match { + case Some(defns) if defns.nonEmpty => + cpy.Template(tree)(body = tree.body ++ + defns.map(transformFollowing(_))) + case _ => + tree + } + } else tree + } + + override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (isMethodWithExtension(tree.symbol)) { + val origMeth = tree.symbol + val origClass = ctx.owner.asClass + val staticClass = origClass.linkedClass + assert(staticClass.exists, s"$origClass lacks companion, ${origClass.owner.definedPeriodsString} ${origClass.owner.info.decls} ${origClass.owner.info.decls}") + val extensionMeth = extensionMethod(origMeth) + ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}") + val store: ListBuffer[Tree] = extensionDefs.get(staticClass) match { + case Some(x) => x + case None => + val newC = new ListBuffer[Tree]() + extensionDefs(staticClass) = newC + newC + } + store += atGroupEnd(fullyParameterizedDef(extensionMeth, tree)(_)) + cpy.DefDef(tree)(rhs = atGroupEnd(forwarder(extensionMeth, tree)(_))) + } else tree + } +} + +object ExtensionMethods { + /** Generate stream of possible names for the extension version of given instance method `imeth`. + * If the method is not overloaded, this stream consists of just "imeth$extension". + * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the + * index of imeth in the sequence of overloaded alternatives with the same name. This choice will + * always be picked as the name of the generated extension method. + * After this first choice, all other possible indices in the range of 0 until the number + * of overloaded alternatives are returned. The secondary choices are used to find a matching method + * in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity + * of how overloaded types are ordered between phases and picklings. + */ + private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = { + val decl = imeth.owner.info.decl(imeth.name) + + /** No longer needed for Dotty, as we are more disciplined with scopes now. + // Bridge generation is done at phase `erasure`, but new scopes are only generated + // for the phase after that. So bridges are visible in earlier phases. + // + // `info.member(imeth.name)` filters these out, but we need to use `decl` + // to restrict ourselves to members defined in the current class, so we + // must do the filtering here. + val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe + */ + decl match { + case decl: MultiDenotation => + val alts = decl.alternatives + val index = alts indexOf imeth.denot + assert(index >= 0, alts + " does not contain " + imeth) + def altName(index: Int) = (imeth.name + "$extension" + index).toTermName + altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName) + case decl => + assert(decl.exists, imeth.name + " not found in " + imeth.owner + "'s decls: " + imeth.owner.info.decls) + Stream((imeth.name + "$extension").toTermName) + } + } + + /** Return the extension method that corresponds to given instance method `meth`. */ + def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol = + ctx.atPhase(ctx.extensionMethodsPhase.next) { implicit ctx => + // FIXME use toStatic instead? + val companionInfo = imeth.owner.companionModule.info + val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists) + val matching = candidates filter (c => FullParameterization.memberSignature(c.info) == imeth.signature) + assert(matching.nonEmpty, + i"""no extension method found for: + | + | $imeth:${imeth.info.show} with signature ${imeth.signature} + | + | Candidates: + | + | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")} + | + | Candidates (signatures normalized): + | + | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + FullParameterization.memberSignature(c.info)).mkString("\n")} + | + | Eligible Names: ${extensionNames(imeth).mkString(",")}""") + matching.head.asTerm + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala new file mode 100644 index 000000000..597146514 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -0,0 +1,193 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Phases.NeedsCompanions +import dotty.tools.dotc.transform.TreeTransforms._ +import ast.Trees._ +import Flags._ +import Types._ +import Constants.Constant +import Contexts.Context +import Symbols._ +import SymDenotations._ +import Decorators._ +import dotty.tools.dotc.core.Annotations.ConcreteAnnotation +import dotty.tools.dotc.core.Denotations.SingleDenotation +import scala.collection.mutable +import DenotTransformers._ +import typer.Checking +import Names.Name +import NameOps._ +import StdNames._ + + +/** The first tree transform + * - ensures there are companion objects for all classes except module classes + * - eliminates some kinds of trees: Imports, NamedArgs + * - stubs out native methods + * - eliminates self tree in Template and self symbol in ClassInfo + * - collapsess all type trees to trees of class TypeTree + * - converts idempotent expressions with constant types + */ +class FirstTransform extends MiniPhaseTransform with InfoTransformer with AnnotationTransformer { thisTransformer => + import ast.tpd._ + + override def phaseName = "firstTransform" + + private var addCompanionPhases: List[NeedsCompanions] = _ + + def needsCompanion(cls: ClassSymbol)(implicit ctx: Context) = + addCompanionPhases.exists(_.isCompanionNeeded(cls)) + + override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context): TreeTransform = { + addCompanionPhases = ctx.phasePlan.flatMap(_ collect { case p: NeedsCompanions => p }) + this + } + + /** eliminate self symbol in ClassInfo */ + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + case tp @ ClassInfo(_, _, _, _, self: Symbol) => + tp.derivedClassInfo(selfInfo = self.info) + case _ => + tp + } + + /* + tp match { + //create companions for value classes that are not from currently compiled source file + case tp@ClassInfo(_, cls, _, decls, _) + if (ValueClasses.isDerivedValueClass(cls)) && + !sym.isDefinedInCurrentRun && sym.scalacLinkedClass == NoSymbol => + val newDecls = decls.cloneScope + val (modul, mcMethod, symMethod) = newCompanion(sym.name.toTermName, sym) + modul.entered + mcMethod.entered + newDecls.enter(symMethod) + tp.derivedClassInfo(decls = newDecls) + case _ => tp + } + } + */ + + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case Select(qual, name) if !name.isOuterSelect && tree.symbol.exists => + assert(qual.tpe derivesFrom tree.symbol.owner, i"non member selection of ${tree.symbol.showLocated} from ${qual.tpe} in $tree") + case _: TypeTree => + case _: Import | _: NamedArg | _: TypTree => + assert(false, i"illegal tree: $tree") + case _ => + } + } + + /** Reorder statements so that module classes always come after their companion classes, add missing companion classes */ + private def reorderAndComplete(stats: List[Tree])(implicit ctx: Context): List[Tree] = { + val moduleClassDefs, singleClassDefs = mutable.Map[Name, Tree]() + + def reorder(stats: List[Tree]): List[Tree] = stats match { + case (stat: TypeDef) :: stats1 if stat.symbol.isClass => + if (stat.symbol is Flags.Module) { + moduleClassDefs += (stat.name -> stat) + singleClassDefs -= stat.name.stripModuleClassSuffix + val stats1r = reorder(stats1) + if (moduleClassDefs contains stat.name) stat :: stats1r else stats1r + } else { + def stats1r = reorder(stats1) + val normalized = moduleClassDefs remove stat.name.moduleClassName match { + case Some(mcdef) => + mcdef :: stats1r + case None => + singleClassDefs += (stat.name -> stat) + stats1r + } + stat :: normalized + } + case stat :: stats1 => stat :: reorder(stats1) + case Nil => Nil + } + + def registerCompanion(name: TermName, forClass: Symbol): TermSymbol = { + val (modul, mcCompanion, classCompanion) = newCompanion(name, forClass) + if (ctx.owner.isClass) modul.enteredAfter(thisTransformer) + mcCompanion.enteredAfter(thisTransformer) + classCompanion.enteredAfter(thisTransformer) + modul + } + + def addMissingCompanions(stats: List[Tree]): List[Tree] = stats map { + case stat: TypeDef if (singleClassDefs contains stat.name) && needsCompanion(stat.symbol.asClass) => + val objName = stat.name.toTermName + val nameClash = stats.exists { + case other: MemberDef => + other.name == objName && other.symbol.info.isParameterless + case _ => + false + } + val uniqueName = if (nameClash) objName.avoidClashName else objName + Thicket(stat :: ModuleDef(registerCompanion(uniqueName, stat.symbol), Nil).trees) + case stat => stat + } + + addMissingCompanions(reorder(stats)) + } + + private def newCompanion(name: TermName, forClass: Symbol)(implicit ctx: Context) = { + val modul = ctx.newCompleteModuleSymbol(forClass.owner, name, Synthetic, Synthetic, + defn.ObjectType :: Nil, Scopes.newScope, assocFile = forClass.asClass.assocFile) + val mc = modul.moduleClass + + val mcComp = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, forClass, mc) + val classComp = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, mc, forClass) + (modul, mcComp, classComp) + } + + /** elimiate self in Template */ + override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { + cpy.Template(impl)(self = EmptyValDef) + } + + override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo) = { + if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) { + ddef.symbol.resetFlag(Deferred) + DefDef(ddef.symbol.asTerm, + _ => ref(defn.Sys_errorR).withPos(ddef.pos) + .appliedTo(Literal(Constant("native method stub")))) + } else ddef + } + + override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = + ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisTransformer.next))) + + override def transformOther(tree: Tree)(implicit ctx: Context, info: TransformerInfo) = tree match { + case tree: Import => EmptyTree + case tree: NamedArg => transform(tree.arg) + case tree => if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) else tree + } + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = + if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) + else constToLiteral(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = + if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) + else constToLiteral(tree) + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) + + override def transformTyped(tree: Typed)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) + + override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) + + // invariants: all modules have companion objects + // all types are TypeTrees + // all this types are explicit +} diff --git a/compiler/src/dotty/tools/dotc/transform/Flatten.scala b/compiler/src/dotty/tools/dotc/transform/Flatten.scala new file mode 100644 index 000000000..f0104e715 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Flatten.scala @@ -0,0 +1,47 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers.SymTransformer +import Phases.Phase +import Contexts.Context +import Flags._ +import SymDenotations.SymDenotation +import collection.mutable +import TreeTransforms.MiniPhaseTransform +import dotty.tools.dotc.transform.TreeTransforms.TransformerInfo + +/** Lift nested classes to toplevel */ +class Flatten extends MiniPhaseTransform with SymTransformer { thisTransform => + import ast.tpd._ + override def phaseName = "flatten" + + def transformSym(ref: SymDenotation)(implicit ctx: Context) = { + if (ref.isClass && !ref.is(Package) && !ref.owner.is(Package)) { + ref.copySymDenotation( + name = ref.flatName, + owner = ref.enclosingPackageClass) + } + else ref + } + + private val liftedDefs = new mutable.ListBuffer[Tree] + + private def liftIfNested(tree: Tree)(implicit ctx: Context, info: TransformerInfo) = + if (ctx.owner is Package) tree + else { + transformFollowing(tree).foreachInThicket(liftedDefs += _) + EmptyTree + } + + override def transformStats(stats: List[Tree])(implicit ctx: Context, info: TransformerInfo) = + if (ctx.owner is Package) { + val liftedStats = stats ++ liftedDefs + liftedDefs.clear + liftedStats + } + else stats + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context, info: TransformerInfo) = + liftIfNested(tree) +} diff --git a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala new file mode 100644 index 000000000..6c69c735b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -0,0 +1,263 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import Contexts._ +import Symbols._ +import Decorators._ +import TypeUtils._ +import StdNames.nme +import NameOps._ +import ast._ +import ast.Trees._ + +import scala.reflect.internal.util.Collections + +/** Provides methods to produce fully parameterized versions of instance methods, + * where the `this` of the enclosing class is abstracted out in an extra leading + * `$this` parameter and type parameters of the class become additional type + * parameters of the fully parameterized method. + * + * Example usage scenarios are: + * + * - extension methods of value classes + * - implementations of trait methods + * - static protected accessors + * - local methods produced by tailrec transform + * + * Note that the methods lift out type parameters of the class containing + * the instance method, but not type parameters of enclosing classes. The + * fully instantiated method therefore needs to be put in a scope "close" + * to the original method, i.e. they need to share the same outer pointer. + * Examples of legal positions are: in the companion object, or as a local + * method inside the original method. + * + * Note: The scheme does not handle yet methods where type parameter bounds + * depend on value parameters of the enclosing class, as in: + * + * class C(val a: String) extends AnyVal { + * def foo[U <: a.type]: Unit = ... + * } + * + * The expansion of method `foo` would lead to + * + * def foo$extension[U <: $this.a.type]($this: C): Unit = ... + * + * which is not typable. Not clear yet what to do. Maybe allow PolyTypes + * to follow method parameters and translate to the following: + * + * def foo$extension($this: C)[U <: $this.a.type]: Unit = ... + * + * @see class-dependent-extension-method.scala in pending/pos. + */ +trait FullParameterization { + + import tpd._ + import FullParameterization._ + + /** If references to original symbol `referenced` from within fully parameterized method + * `derived` should be rewired to some fully parameterized method, the rewiring target symbol, + * otherwise NoSymbol. + */ + protected def rewiredTarget(referenced: Symbol, derived: Symbol)(implicit ctx: Context): Symbol + + /** If references to some original symbol from given tree node within fully parameterized method + * `derived` should be rewired to some fully parameterized method, the rewiring target symbol, + * otherwise NoSymbol. By default implemented as + * + * rewiredTarget(tree.symbol, derived) + * + * but can be overridden. + */ + protected def rewiredTarget(tree: Tree, derived: Symbol)(implicit ctx: Context): Symbol = + rewiredTarget(tree.symbol, derived) + + /** Converts the type `info` of a member of class `clazz` to a method type that + * takes the `this` of the class and any type parameters of the class + * as additional parameters. Example: + * + * class Foo[+A <: AnyRef](val xs: List[A]) extends AnyVal { + * def baz[B >: A](x: B): List[B] = ... + * } + * + * leads to: + * + * object Foo { + * def extension$baz[B >: A <: Any, A >: Nothing <: AnyRef]($this: Foo[A])(x: B): List[B] + * } + * + * If a self type is present, $this has this self type as its type. + * + * @param abstractOverClass if true, include the type parameters of the class in the method's list of type parameters. + * @param liftThisType if true, require created $this to be $this: (Foo[A] & Foo,this). + * This is needed if created member stays inside scope of Foo(as in tailrec) + */ + def fullyParameterizedType(info: Type, clazz: ClassSymbol, abstractOverClass: Boolean = true, liftThisType: Boolean = false)(implicit ctx: Context): Type = { + val (mtparamCount, origResult) = info match { + case info: PolyType => (info.paramNames.length, info.resultType) + case info: ExprType => (0, info.resultType) + case _ => (0, info) + } + val ctparams = if (abstractOverClass) clazz.typeParams else Nil + val ctnames = ctparams.map(_.name.unexpandedName) + + /** The method result type */ + def resultType(mapClassParams: Type => Type) = { + val thisParamType = mapClassParams(clazz.classInfo.selfType) + val firstArgType = if (liftThisType) thisParamType & clazz.thisType else thisParamType + MethodType(nme.SELF :: Nil, firstArgType :: Nil)(mt => + mapClassParams(origResult).substThisUnlessStatic(clazz, MethodParam(mt, 0))) + } + + /** Replace class type parameters by the added type parameters of the polytype `pt` */ + def mapClassParams(tp: Type, pt: PolyType): Type = { + val classParamsRange = (mtparamCount until mtparamCount + ctparams.length).toList + tp.substDealias(ctparams, classParamsRange map (PolyParam(pt, _))) + } + + /** The bounds for the added type parameters of the polytype `pt` */ + def mappedClassBounds(pt: PolyType): List[TypeBounds] = + ctparams.map(tparam => mapClassParams(tparam.info, pt).bounds) + + info match { + case info: PolyType => + PolyType(info.paramNames ++ ctnames)( + pt => + (info.paramBounds.map(mapClassParams(_, pt).bounds) ++ + mappedClassBounds(pt)).mapConserve(_.subst(info, pt).bounds), + pt => resultType(mapClassParams(_, pt)).subst(info, pt)) + case _ => + if (ctparams.isEmpty) resultType(identity) + else PolyType(ctnames)(mappedClassBounds, pt => resultType(mapClassParams(_, pt))) + } + } + + /** The type parameters (skolems) of the method definition `originalDef`, + * followed by the class parameters of its enclosing class. + */ + private def allInstanceTypeParams(originalDef: DefDef, abstractOverClass: Boolean)(implicit ctx: Context): List[Symbol] = + if (abstractOverClass) + originalDef.tparams.map(_.symbol) ::: originalDef.symbol.enclosingClass.typeParams + else originalDef.tparams.map(_.symbol) + + /** Given an instance method definition `originalDef`, return a + * fully parameterized method definition derived from `originalDef`, which + * has `derived` as symbol and `fullyParameterizedType(originalDef.symbol.info)` + * as info. + * `abstractOverClass` defines weather the DefDef should abstract over type parameters + * of class that contained original defDef + */ + def fullyParameterizedDef(derived: TermSymbol, originalDef: DefDef, abstractOverClass: Boolean = true)(implicit ctx: Context): Tree = + polyDefDef(derived, trefs => vrefss => { + val origMeth = originalDef.symbol + val origClass = origMeth.enclosingClass.asClass + val origTParams = allInstanceTypeParams(originalDef, abstractOverClass) + val origVParams = originalDef.vparamss.flatten map (_.symbol) + val thisRef :: argRefs = vrefss.flatten + + /** If tree should be rewired, the rewired tree, otherwise EmptyTree. + * @param targs Any type arguments passed to the rewired tree. + */ + def rewireTree(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { + def rewireCall(thisArg: Tree): Tree = { + val rewired = rewiredTarget(tree, derived) + if (rewired.exists) { + val base = thisArg.tpe.baseTypeWithArgs(origClass) + assert(base.exists) + ref(rewired.termRef) + .appliedToTypeTrees(targs ++ base.argInfos.map(TypeTree(_))) + .appliedTo(thisArg) + } else EmptyTree + } + tree match { + case Return(expr, from) if !from.isEmpty => + val rewired = rewiredTarget(from, derived) + if (rewired.exists) + tpd.cpy.Return(tree)(expr, Ident(rewired.termRef)) + else + EmptyTree + case Ident(_) => rewireCall(thisRef) + case Select(qual, _) => rewireCall(qual) + case tree @ TypeApply(fn, targs1) => + assert(targs.isEmpty) + rewireTree(fn, targs1) + case _ => EmptyTree + } + } + + /** Type rewiring is needed because a previous reference to an instance + * method might still persist in the types of enclosing nodes. Example: + * + * if (true) this.imeth else this.imeth + * + * is rewritten to + * + * if (true) xmeth($this) else xmeth($this) + * + * but the type `this.imeth` still persists as the result type of the `if`, + * because it is kept by the `cpy` operation of the tree transformer. + * It needs to be rewritten to the common result type of `imeth` and `xmeth`. + */ + def rewireType(tpe: Type) = tpe match { + case tpe: TermRef if rewiredTarget(tpe.symbol, derived).exists => tpe.widen + case _ => tpe + } + + new TreeTypeMap( + typeMap = rewireType(_) + .substDealias(origTParams, trefs) + .subst(origVParams, argRefs.map(_.tpe)) + .substThisUnlessStatic(origClass, thisRef.tpe), + treeMap = { + case tree: This if tree.symbol == origClass => thisRef + case tree => rewireTree(tree, Nil) orElse tree + }, + oldOwners = origMeth :: Nil, + newOwners = derived :: Nil + ).transform(originalDef.rhs) + }) + + /** A forwarder expression which calls `derived`, passing along + * - if `abstractOverClass` the type parameters and enclosing class parameters of originalDef`, + * - the `this` of the enclosing class, + * - the value parameters of the original method `originalDef`. + */ + def forwarder(derived: TermSymbol, originalDef: DefDef, abstractOverClass: Boolean = true, liftThisType: Boolean = false)(implicit ctx: Context): Tree = { + val fun = + ref(derived.termRef) + .appliedToTypes(allInstanceTypeParams(originalDef, abstractOverClass).map(_.typeRef)) + .appliedTo(This(originalDef.symbol.enclosingClass.asClass)) + + (if (!liftThisType) + fun.appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol))) + else { + // this type could have changed on forwarding. Need to insert a cast. + val args = Collections.map2(originalDef.vparamss, fun.tpe.paramTypess)((vparams, paramTypes) => + Collections.map2(vparams, paramTypes)((vparam, paramType) => { + assert(vparam.tpe <:< paramType.widen) // type should still conform to widened type + ref(vparam.symbol).ensureConforms(paramType) + }) + ) + fun.appliedToArgss(args) + + }).withPos(originalDef.rhs.pos) + } +} + +object FullParameterization { + + /** Assuming `info` is a result of a `fullyParameterizedType` call, the signature of the + * original method type `X` such that `info = fullyParameterizedType(X, ...)`. + */ + def memberSignature(info: Type)(implicit ctx: Context): Signature = info match { + case info: PolyType => + memberSignature(info.resultType) + case info @ MethodType(nme.SELF :: Nil, _) => + info.resultType.ensureMethodic.signature + case info @ MethodType(nme.SELF :: otherNames, thisType :: otherTypes) => + info.derivedMethodType(otherNames, otherTypes, info.resultType).signature + case _ => + Signature.NotAMethod + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala b/compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala new file mode 100644 index 000000000..5fd89314a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala @@ -0,0 +1,83 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.SymDenotations._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import ast.Trees._ +import SymUtils._ +import dotty.tools.dotc.ast.tpd +import collection.{ mutable, immutable } +import collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet } + +/** + * Rewires closures to implement more specific types of Functions. + */ +class FunctionalInterfaces extends MiniPhaseTransform { + import tpd._ + + def phaseName: String = "functionalInterfaces" + + private var allowedReturnTypes: Set[Symbol] = _ // moved here to make it explicit what specializations are generated + private var allowedArgumentTypes: Set[Symbol] = _ + val maxArgsCount = 2 + + def shouldSpecialize(m: MethodType)(implicit ctx: Context) = + (m.paramTypes.size <= maxArgsCount) && + m.paramTypes.forall(x => allowedArgumentTypes.contains(x.typeSymbol)) && + allowedReturnTypes.contains(m.resultType.typeSymbol) + + val functionName = "JFunction".toTermName + val functionPackage = "scala.compat.java8.".toTermName + + override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context): TreeTransform = { + allowedReturnTypes = Set(defn.UnitClass, + defn.BooleanClass, + defn.IntClass, + defn.FloatClass, + defn.LongClass, + defn.DoubleClass, + /* only for Function0: */ defn.ByteClass, + defn.ShortClass, + defn.CharClass) + + allowedArgumentTypes = Set(defn.IntClass, + defn.LongClass, + defn.DoubleClass, + /* only for Function1: */ defn.FloatClass) + + this + } + + override def transformClosure(tree: Closure)(implicit ctx: Context, info: TransformerInfo): Tree = { + tree.tpt match { + case EmptyTree => + val m = tree.meth.tpe.widen.asInstanceOf[MethodType] + + if (shouldSpecialize(m)) { + val functionSymbol = tree.tpe.widenDealias.classSymbol + val names = ctx.atPhase(ctx.erasurePhase) { + implicit ctx => functionSymbol.typeParams.map(_.name) + } + val interfaceName = (functionName ++ m.paramTypes.length.toString).specializedFor(m.paramTypes ::: m.resultType :: Nil, names, Nil, Nil) + + // symbols loaded from classpath aren't defined in periods earlier than when they where loaded + val interface = ctx.withPhase(ctx.typerPhase).getClassIfDefined(functionPackage ++ interfaceName) + if (interface.exists) { + val tpt = tpd.TypeTree(interface.asType.typeRef) + tpd.Closure(tree.env, tree.meth, tpt) + } else tree + } else tree + case _ => + tree + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/GetClass.scala b/compiler/src/dotty/tools/dotc/transform/GetClass.scala new file mode 100644 index 000000000..6a9a5fda2 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/GetClass.scala @@ -0,0 +1,34 @@ +package dotty.tools.dotc +package transform + +import ast.tpd +import core.Contexts.Context +import core.StdNames.nme +import core.Phases.Phase +import TypeUtils._ +import TreeTransforms.{MiniPhaseTransform, TransformerInfo} + +/** Rewrite `getClass` calls as follow: + * + * For every instance of primitive class C whose boxed class is called B: + * instanceC.getClass -> B.TYPE + * For every instance of non-primitive class D: + * instanceD.getClass -> instanceD.getClass + */ +class GetClass extends MiniPhaseTransform { + import tpd._ + + override def phaseName: String = "getClass" + + // getClass transformation should be applied to specialized methods + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure], classOf[FunctionalInterfaces]) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = { + import ast.Trees._ + tree match { + case Apply(Select(qual, nme.getClass_), Nil) if qual.tpe.widen.isPrimitiveValueType => + clsOf(qual.tpe.widen).withPos(tree.pos) + case _ => tree + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/Getters.scala b/compiler/src/dotty/tools/dotc/transform/Getters.scala new file mode 100644 index 000000000..31171dfab --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Getters.scala @@ -0,0 +1,76 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers.SymTransformer +import Contexts.Context +import SymDenotations.SymDenotation +import Types._ +import Symbols._ +import SymUtils._ +import Constants._ +import TreeTransforms._ +import Flags._ +import Decorators._ +import ValueClasses._ + +/** Performs the following rewritings for fields of a class: + * + * <mods> val x: T = e + * --> <mods> <stable> <accessor> def x: T = e + * <mods> var x: T = e + * --> <mods> <accessor> def x: T = e + * + * <mods> val x: T + * --> <mods> <stable> <accessor> def x: T + * + * <mods> lazy val x: T = e + * --> <mods> <accessor> lazy def x: T =e + * + * <mods> var x: T + * --> <mods> <accessor> def x: T + * + * <mods> non-static <module> val x$ = e + * --> <mods> <module> <accessor> def x$ = e + * + * Omitted from the rewritings are + * + * - private[this] fields in classes (excluding traits, value classes) + * - fields generated for static modules (TODO: needed?) + * - parameters, static fields, and fields coming from Java + * + * Furthermore, assignments to mutable vars are replaced by setter calls + * + * p.x = e + * --> p.x_=(e) + * + * No fields are generated yet. This is done later in phase Memoize. + */ +class Getters extends MiniPhaseTransform with SymTransformer { thisTransform => + import ast.tpd._ + + override def phaseName = "getters" + + override def transformSym(d: SymDenotation)(implicit ctx: Context): SymDenotation = { + def noGetterNeeded = + d.is(NoGetterNeeded) || + d.initial.asInstanceOf[SymDenotation].is(PrivateLocal) && !d.owner.is(Trait) && !isDerivedValueClass(d.owner) && !d.is(Flags.Lazy) || + d.is(Module) && d.isStatic || + d.hasAnnotation(defn.ScalaStaticAnnot) || + d.isSelfSym + if (d.isTerm && (d.is(Lazy) || d.owner.isClass) && d.info.isValueType && !noGetterNeeded) { + val maybeStable = if (d.isStable) Stable else EmptyFlags + d.copySymDenotation( + initFlags = d.flags | maybeStable | AccessorCreationFlags, + info = ExprType(d.info)) + } + else d + } + private val NoGetterNeeded = Method | Param | JavaDefined | JavaStatic + + override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = + if (tree.symbol is Method) DefDef(tree.symbol.asTerm, tree.rhs).withPos(tree.pos) else tree + + override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = + if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs).withPos(tree.pos) else tree +} diff --git a/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala new file mode 100644 index 000000000..7c60e8d72 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -0,0 +1,131 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.Denotations._ +import core.SymDenotations._ +import core.Contexts._ +import core.Types._ +import ast.Trees._ +import ast.tpd.{Apply, Tree, cpy} +import dotty.tools.dotc.ast.tpd +import scala.collection.mutable +import dotty.tools.dotc._ +import core._ +import Contexts._ +import Symbols._ +import Decorators._ +import NameOps._ +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, TreeTransform} +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{untpd, tpd} +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Types.MethodType +import dotty.tools.dotc.core.Names.Name +import scala.collection.mutable.ListBuffer +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import StdNames._ +import Phases.Phase + +/** Replace member references as follows: + * + * - `x != y` for != in class Any becomes `!(x == y)` with == in class Any. + * - `x.##` for ## in NullClass becomes `0` + * - `x.##` for ## in Any becomes calls to ScalaRunTime.hash, + * using the most precise overload available + * - `x.getClass` for getClass in primitives becomes `x.getClass` with getClass in class Object. + */ +class InterceptedMethods extends MiniPhaseTransform { + thisTransform => + + import tpd._ + + override def phaseName: String = "intercepted" + + private var primitiveGetClassMethods: Set[Symbol] = _ + + var Any_## : Symbol = _ // cached for performance reason + + /** perform context-dependant initialization */ + override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { + this.Any_## = defn.Any_## + primitiveGetClassMethods = Set[Symbol]() ++ defn.ScalaValueClasses().map(x => x.requiredMethod(nme.getClass_)) + this + } + + // this should be removed if we have guarantee that ## will get Apply node + override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (tree.symbol.isTerm && (Any_## eq tree.symbol.asTerm)) { + val rewrite = poundPoundValue(tree.qualifier) + ctx.log(s"$phaseName rewrote $tree to $rewrite") + rewrite + } + else tree + } + + private def poundPoundValue(tree: Tree)(implicit ctx: Context) = { + val s = tree.tpe.widen.typeSymbol + if (s == defn.NullClass) Literal(Constant(0)) + else { + // Since we are past typer, we need to avoid creating trees carrying + // overloaded types. This logic is custom (and technically incomplete, + // although serviceable) for def hash. What is really needed is for + // the overloading logic presently hidden away in a few different + // places to be properly exposed so we can just call "resolveOverload" + // after typer. Until then: + + def alts = defn.ScalaRuntimeModule.info.member(nme.hash_) + + // if tpe is a primitive value type, alt1 will match on the exact value, + // taking in account that null.asInstanceOf[Int] == 0 + def alt1 = alts.suchThat(_.info.firstParamTypes.head =:= tree.tpe.widen) + + // otherwise alt2 will match. alt2 also knows how to handle 'null' runtime value + def alt2 = defn.ScalaRuntimeModule.info.member(nme.hash_) + .suchThat(_.info.firstParamTypes.head.typeSymbol == defn.AnyClass) + + Ident((if (s.isNumericValueClass) alt1 else alt2).termRef) + .appliedTo(tree) + } + } + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = { + def unknown = { + assert(false, s"The symbol '${tree.fun.symbol.showLocated}' was intercepted but didn't match any cases, " + + s"that means the intercepted methods set doesn't match the code") + tree + } + lazy val Select(qual, _) = tree.fun + val Any_## = this.Any_## + val Any_!= = defn.Any_!= + val rewrite: Tree = tree.fun.symbol match { + case Any_## => + poundPoundValue(qual) + case Any_!= => + qual.select(defn.Any_==).appliedToArgs(tree.args).select(defn.Boolean_!) + /* + /* else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) { + // todo: this is needed to support value classes + // Rewrite 5.getClass to ScalaRunTime.anyValClass(5) + global.typer.typed(gen.mkRuntimeCall(nme.anyValClass, + List(qual, typer.resolveClassTag(tree.pos, qual.tpe.widen)))) + }*/ + */ + case t if primitiveGetClassMethods.contains(t) => + // if we got here then we're trying to send a primitive getClass method to either + // a) an Any, in which cage Object_getClass works because Any erases to object. Or + // + // b) a non-primitive, e.g. because the qualifier's type is a refinement type where one parent + // of the refinement is a primitive and another is AnyRef. In that case + // we get a primitive form of _getClass trying to target a boxed value + // so we need replace that method name with Object_getClass to get correct behavior. + // See SI-5568. + qual.selectWithSig(defn.Any_getClass).appliedToNone + case _ => + tree + } + ctx.log(s"$phaseName rewrote $tree to $rewrite") + rewrite + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala b/compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala new file mode 100644 index 000000000..8bc4a2aa9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala @@ -0,0 +1,168 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.util.Positions._ +import TreeTransforms.{MiniPhaseTransform, TransformerInfo} +import core._ +import Contexts.Context, Types._, Constants._, Decorators._, Symbols._ +import TypeUtils._, TypeErasure._, Flags._ + + +/** Implements partial evaluation of `sc.isInstanceOf[Sel]` according to: + * + * +-------------+----------------------------+----------------------------+------------------+ + * | Sel\sc | trait | class | final class | + * +-------------+----------------------------+----------------------------+------------------+ + * | trait | ? | ? | statically known | + * | class | ? | false if classes unrelated | statically known | + * | final class | false if classes unrelated | false if classes unrelated | statically known | + * +-------------+----------------------------+----------------------------+------------------+ + * + * This is a generalized solution to raising an error on unreachable match + * cases and warnings on other statically known results of `isInstanceOf`. + * + * Steps taken: + * + * 1. evalTypeApply will establish the matrix and choose the appropriate + * handling for the case: + * 2. a) Sel/sc is a value class or scrutinee is `Any` + * b) handleStaticallyKnown + * c) falseIfUnrelated with `scrutinee <:< selector` + * d) handleFalseUnrelated + * e) leave as is (aka `happens`) + * 3. Rewrite according to step taken in `2` + */ +class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer => + + import dotty.tools.dotc.ast.tpd._ + + def phaseName = "isInstanceOfEvaluator" + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = { + val defn = ctx.definitions + + /** Handles the four cases of statically known `isInstanceOf`s and gives + * the correct warnings, or an error if statically known to be false in + * match + */ + def handleStaticallyKnown(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = { + val scrutineeSubSelector = scrutinee <:< selector + if (!scrutineeSubSelector && inMatch) { + ctx.error( + s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`", + Position(pos.start - 5, pos.end - 5) + ) + rewrite(select, to = false) + } else if (!scrutineeSubSelector && !inMatch) { + ctx.warning( + s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)", + pos + ) + rewrite(select, to = false) + } else if (scrutineeSubSelector && !inMatch) { + ctx.warning( + s"this will always yield true if the scrutinee is non-null, since `${scrutinee.show}` is a subclass of `${selector.show}` (will be optimized away)", + pos + ) + rewrite(select, to = true) + } else /* if (scrutineeSubSelector && inMatch) */ rewrite(select, to = true) + } + + /** Rewrites cases with unrelated types */ + def handleFalseUnrelated(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean) = + if (inMatch) { + ctx.error( + s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`", + Position(select.pos.start - 5, select.pos.end - 5) + ) + rewrite(select, to = false) + } else { + ctx.warning( + s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`", + select.pos + ) + rewrite(select, to = false) + } + + /** Rewrites the select to a boolean if `to` is false or if the qualifier + * is a value class. + * + * If `to` is set to true and the qualifier is not a primitive, the + * instanceOf is replaced by a null check, since: + * + * `scrutinee.isInstanceOf[Selector]` if `scrutinee eq null` + */ + def rewrite(tree: Select, to: Boolean): Tree = + if (!to || !tree.qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) { + val literal = Literal(Constant(to)) + if (!isPureExpr(tree.qualifier)) Block(List(tree.qualifier), literal) + else literal + } else + Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null)))) + + /** Attempts to rewrite TypeApply to either `scrutinee ne null` or a + * constant + */ + def evalTypeApply(tree: TypeApply): Tree = + if (tree.symbol != defn.Any_isInstanceOf) tree + else tree.fun match { + case s: Select => { + val scrutinee = erasure(s.qualifier.tpe.widen) + val selector = erasure(tree.args.head.tpe.widen) + + val scTrait = scrutinee.typeSymbol is Trait + val scClass = + scrutinee.typeSymbol.isClass && + !(scrutinee.typeSymbol is Trait) && + !(scrutinee.typeSymbol is Module) + + val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final) + val scFinalClass = scClass && (scrutinee.typeSymbol is Final) + + val selTrait = selector.typeSymbol is Trait + val selClass = + selector.typeSymbol.isClass && + !(selector.typeSymbol is Trait) && + !(selector.typeSymbol is Module) + + val selClassNonFinal = selClass && !(selector.typeSymbol is Final) + val selFinalClass = selClass && (selector.typeSymbol is Final) + + // Cases --------------------------------- + val valueClassesOrAny = + ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) || + ValueClasses.isDerivedValueClass(selector.typeSymbol) || + scrutinee == defn.ObjectType + + val knownStatically = scFinalClass + + val falseIfUnrelated = + (scClassNonFinal && selClassNonFinal) || + (scClassNonFinal && selFinalClass) || + (scTrait && selFinalClass) + + val happens = + (scClassNonFinal && selClassNonFinal) || + (scTrait && selClassNonFinal) || + (scTrait && selTrait) + + val inMatch = s.qualifier.symbol is Case + + if (valueClassesOrAny) tree + else if (knownStatically) + handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos) + else if (falseIfUnrelated && scrutinee <:< selector) + // scrutinee is a subtype of the selector, safe to rewrite + rewrite(s, to = true) + else if (falseIfUnrelated && !(selector <:< scrutinee)) + // selector and scrutinee are unrelated + handleFalseUnrelated(s, scrutinee, selector, inMatch) + else if (happens) tree + else tree + } + + case _ => tree + } + + evalTypeApply(tree) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala new file mode 100644 index 000000000..19fb3dd0c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -0,0 +1,548 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import core.Phases._ +import ast.Trees._ +import SymUtils._ +import ExplicitOuter.outer +import util.Attachment +import util.NameTransformer +import util.Positions._ +import collection.{ mutable, immutable } +import collection.mutable.{ HashMap, HashSet, LinkedHashMap, LinkedHashSet, TreeSet } + +object LambdaLift { + private val NJ = NameTransformer.NAME_JOIN_STRING + private class NoPath extends Exception +} + +/** This phase performs the necessary rewritings to eliminate classes and methods + * nested in other methods. In detail: + * 1. It adds all free variables of local functions as additional parameters (proxies). + * 2. It rebinds references to free variables to the corresponding proxies, + * 3. It lifts all local functions and classes out as far as possible, but at least + * to the enclosing class. + * 4. It stores free variables of non-trait classes as additional fields of the class. + * The fields serve as proxies for methods in the class, which avoids the need + * of passing additional parameters to these methods. + * + * A particularly tricky case are local traits. These cannot store free variables + * as field proxies, because LambdaLift runs after Mixin, so the fields cannot be + * expanded anymore. Instead, methods of local traits get free variables of + * the trait as additional proxy parameters. The difference between local classes + * and local traits is illustrated by the two rewritings below. + * + * def f(x: Int) = { def f(x: Int) = new C(x).f2 + * class C { ==> class C(x$1: Int) { + * def f2 = x def f2 = x$1 + * } } + * new C().f2 + * } + * + * def f(x: Int) = { def f(x: Int) = new C().f2(x) + * trait T { ==> trait T + * def f2 = x def f2(x$1: Int) = x$1 + * } } + * class C extends T class C extends T + * new C().f2 + * } + */ +class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform => + import LambdaLift._ + import ast.tpd._ + + /** the following two members override abstract members in Transform */ + val phaseName: String = "lambdaLift" + val treeTransform = new LambdaLifter + + override def relaxedTyping = true + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Constructors]) + // Constructors has to happen before LambdaLift because the lambda lift logic + // becomes simpler if it can assume that parameter accessors have already been + // converted to parameters in super calls. Without this it is very hard to get + // lambda lift for super calls right. Witness the implementation restrictions to + // this effect in scalac. + + class LambdaLifter extends TreeTransform { + override def phase = thisTransform + + private type SymSet = TreeSet[Symbol] + + /** A map storing free variables of functions and classes */ + private val free = new LinkedHashMap[Symbol, SymSet] + + /** A map storing the free variable proxies of functions and classes. + * For every function and class, this is a map from the free variables + * of that function or class to the proxy symbols accessing them. + */ + private val proxyMap = new LinkedHashMap[Symbol, Map[Symbol, Symbol]] + + /** A hashtable storing calls between functions */ + private val called = new LinkedHashMap[Symbol, SymSet] + + /** Symbols that are called from an inner class. */ + private val calledFromInner = new HashSet[Symbol] + + /** A map from local methods and classes to the owners to which they will be lifted as members. + * For methods and classes that do not have any dependencies this will be the enclosing package. + * symbols with packages as lifted owners will subsequently represented as static + * members of their toplevel class, unless their enclosing class was already static. + * Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner + * is also used to decide whether a method had a term owner before. + */ + private val liftedOwner = new HashMap[Symbol, Symbol] + + /** The outer parameter of a constructor */ + private val outerParam = new HashMap[Symbol, Symbol] + + /** Buffers for lifted out classes and methods, indexed by owner */ + private val liftedDefs = new HashMap[Symbol, mutable.ListBuffer[Tree]] + + /** A flag to indicate whether new free variables have been found */ + private var changedFreeVars: Boolean = _ + + /** A flag to indicate whether lifted owners have changed */ + private var changedLiftedOwner: Boolean = _ + + private val ord: Ordering[Symbol] = Ordering.by((_: Symbol).id) // Dotty deviation: Type annotation needed. TODO: figure out why + private def newSymSet = TreeSet.empty[Symbol](ord) + + private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = + f.getOrElseUpdate(sym, newSymSet) + + def freeVars(sym: Symbol): List[Symbol] = free get sym match { + case Some(set) => set.toList + case None => Nil + } + + def proxyOf(sym: Symbol, fv: Symbol) = proxyMap.getOrElse(sym, Map.empty)(fv) + + def proxies(sym: Symbol): List[Symbol] = freeVars(sym).map(proxyOf(sym, _)) + + /** A symbol is local if it is owned by a term or a local trait, + * or if it is a constructor of a local symbol. + */ + def isLocal(sym: Symbol)(implicit ctx: Context): Boolean = { + val owner = sym.maybeOwner + owner.isTerm || + owner.is(Trait) && isLocal(owner) || + sym.isConstructor && isLocal(owner) + } + + /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested + * than the previous value of `liftedowner(sym)`. + */ + def narrowLiftedOwner(sym: Symbol, owner: Symbol)(implicit ctx: Context) = + if (sym.maybeOwner.isTerm && + owner.isProperlyContainedIn(liftedOwner(sym)) && + owner != sym) { + ctx.log(i"narrow lifted $sym to $owner") + changedLiftedOwner = true + liftedOwner(sym) = owner + } + + /** Mark symbol `sym` as being free in `enclosure`, unless `sym` is defined + * in `enclosure` or there is an intermediate class properly containing `enclosure` + * in which `sym` is also free. Also, update `liftedOwner` of `enclosure` so + * that `enclosure` can access `sym`, or its proxy in an intermediate class. + * This means: + * + * 1. If there is an intermediate class in which `sym` is free, `enclosure` + * must be contained in that class (in order to access the `sym proxy stored + * in the class). + * + * 2. If there is no intermediate class, `enclosure` must be contained + * in the class enclosing `sym`. + * + * @return If there is a non-trait class between `enclosure` and + * the owner of `sym`, the largest such class. + * Otherwise, if there is a trait between `enclosure` and + * the owner of `sym`, the largest such trait. + * Otherwise, NoSymbol. + * + * @pre sym.owner.isTerm, (enclosure.isMethod || enclosure.isClass) + * + * The idea of `markFree` is illustrated with an example: + * + * def f(x: int) = { + * class C { + * class D { + * val y = x + * } + * } + * } + * + * In this case `x` is free in the primary constructor of class `C`. + * but it is not free in `D`, because after lambda lift the code would be transformed + * as follows: + * + * def f(x$0: int) { + * class C(x$0: int) { + * val x$1 = x$0 + * class D { + * val y = outer.x$1 + * } + * } + * } + */ + private def markFree(sym: Symbol, enclosure: Symbol)(implicit ctx: Context): Symbol = try { + if (!enclosure.exists) throw new NoPath + if (enclosure == sym.enclosure) NoSymbol + else { + ctx.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") + val intermediate = + if (enclosure.is(PackageClass)) enclosure + else markFree(sym, enclosure.enclosure) + narrowLiftedOwner(enclosure, intermediate orElse sym.enclosingClass) + if (!intermediate.isRealClass || enclosure.isConstructor) { + // Constructors and methods nested inside traits get the free variables + // of the enclosing trait or class. + // Conversely, local traits do not get free variables. + if (!enclosure.is(Trait)) + if (symSet(free, enclosure).add(sym)) { + changedFreeVars = true + ctx.log(i"$sym is free in $enclosure") + } + } + if (intermediate.isRealClass) intermediate + else if (enclosure.isRealClass) enclosure + else if (intermediate.isClass) intermediate + else if (enclosure.isClass) enclosure + else NoSymbol + } + } catch { + case ex: NoPath => + println(i"error lambda lifting ${ctx.compilationUnit}: $sym is not visible from $enclosure") + throw ex + } + + private def markCalled(callee: Symbol, caller: Symbol)(implicit ctx: Context): Unit = { + ctx.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller in ${caller.owner}") + assert(isLocal(callee)) + symSet(called, caller) += callee + if (callee.enclosingClass != caller.enclosingClass) calledFromInner += callee + } + + private class CollectDependencies extends EnclosingMethodTraverser { + def traverse(enclosure: Symbol, tree: Tree)(implicit ctx: Context) = try { //debug + val sym = tree.symbol + def narrowTo(thisClass: ClassSymbol) = { + val enclClass = enclosure.enclosingClass + narrowLiftedOwner(enclosure, + if (enclClass.isContainedIn(thisClass)) thisClass + else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner + } + tree match { + case tree: Ident => + if (isLocal(sym)) { + if (sym is Label) + assert(enclosure == sym.enclosure, + i"attempt to refer to label $sym from nested $enclosure") + else if (sym is Method) markCalled(sym, enclosure) + else if (sym.isTerm) markFree(sym, enclosure) + } + def captureImplicitThis(x: Type): Unit = { + x match { + case tr@TermRef(x, _) if (!tr.termSymbol.isStatic) => captureImplicitThis(x) + case x: ThisType if (!x.tref.typeSymbol.isStaticOwner) => narrowTo(x.tref.typeSymbol.asClass) + case _ => + } + } + captureImplicitThis(tree.tpe) + case tree: Select => + if (sym.is(Method) && isLocal(sym)) markCalled(sym, enclosure) + case tree: This => + narrowTo(tree.symbol.asClass) + case tree: DefDef => + if (sym.owner.isTerm && !sym.is(Label)) + liftedOwner(sym) = sym.enclosingPackageClass + // this will make methods in supercall constructors of top-level classes owned + // by the enclosing package, which means they will be static. + // On the other hand, all other methods will be indirectly owned by their + // top-level class. This avoids possible deadlocks when a static method + // has to access its enclosing object from the outside. + else if (sym.isConstructor) { + if (sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait)) + // add a call edge from the constructor of a local non-trait class to + // the class itself. This is done so that the constructor inherits + // the free variables of the class. + symSet(called, sym) += sym.owner + + tree.vparamss.head.find(_.name == nme.OUTER) match { + case Some(vdef) => outerParam(sym) = vdef.symbol + case _ => + } + } + case tree: TypeDef => + if (sym.owner.isTerm) liftedOwner(sym) = sym.topLevelClass.owner + case tree: Template => + liftedDefs(tree.symbol.owner) = new mutable.ListBuffer + case _ => + } + foldOver(enclosure, tree) + } catch { //debug + case ex: Exception => + println(i"$ex while traversing $tree") + throw ex + } + } + + /** Compute final free variables map `fvs by closing over caller dependencies. */ + private def computeFreeVars()(implicit ctx: Context): Unit = + do { + changedFreeVars = false + for { + caller <- called.keys + callee <- called(caller) + fvs <- free get callee + fv <- fvs + } markFree(fv, caller) + } while (changedFreeVars) + + /** Compute final liftedOwner map by closing over caller dependencies */ + private def computeLiftedOwners()(implicit ctx: Context): Unit = + do { + changedLiftedOwner = false + for { + caller <- called.keys + callee <- called(caller) + } { + val normalizedCallee = callee.skipConstructor + val calleeOwner = normalizedCallee.owner + if (calleeOwner.isTerm) narrowLiftedOwner(caller, liftedOwner(normalizedCallee)) + else { + assert(calleeOwner.is(Trait)) + // methods nested inside local trait methods cannot be lifted out + // beyond the trait. Note that we can also call a trait method through + // a qualifier; in that case no restriction to lifted owner arises. + if (caller.isContainedIn(calleeOwner)) + narrowLiftedOwner(caller, calleeOwner) + } + } + } while (changedLiftedOwner) + + private def newName(sym: Symbol)(implicit ctx: Context): Name = + if (sym.isAnonymousFunction && sym.owner.is(Method, butNot = Label)) + (sym.name ++ NJ ++ sym.owner.name).freshened + else sym.name.freshened + + private def generateProxies()(implicit ctx: Context): Unit = + for ((owner, freeValues) <- free.toIterator) { + val newFlags = Synthetic | (if (owner.isClass) ParamAccessor | Private else Param) + ctx.debuglog(i"free var proxy: ${owner.showLocated}, ${freeValues.toList}%, %") + proxyMap(owner) = { + for (fv <- freeValues.toList) yield { + val proxyName = newName(fv) + val proxy = ctx.newSymbol(owner, proxyName.asTermName, newFlags, fv.info, coord = fv.coord) + if (owner.isClass) proxy.enteredAfter(thisTransform) + (fv, proxy) + } + }.toMap + } + + private def liftedInfo(local: Symbol)(implicit ctx: Context): Type = local.info match { + case mt @ MethodType(pnames, ptypes) => + val ps = proxies(local) + MethodType( + ps.map(_.name.asTermName) ++ pnames, + ps.map(_.info) ++ ptypes, + mt.resultType) + case info => info + } + + private def liftLocals()(implicit ctx: Context): Unit = { + for ((local, lOwner) <- liftedOwner) { + val (newOwner, maybeStatic) = + if (lOwner is Package) { + val encClass = local.enclosingClass + val topClass = local.topLevelClass + val preferEncClass = + encClass.isStatic && + // non-static classes can capture owners, so should be avoided + (encClass.isProperlyContainedIn(topClass) || + // can be false for symbols which are defined in some weird combination of supercalls. + encClass.is(ModuleClass, butNot = Package) + // needed to not cause deadlocks in classloader. see t5375.scala + ) + if (preferEncClass) (encClass, EmptyFlags) + else (topClass, JavaStatic) + } + else (lOwner, EmptyFlags) + local.copySymDenotation( + owner = newOwner, + name = newName(local), + initFlags = local.flags &~ (InSuperCall | Module) | Private | maybeStatic, + // drop Module because class is no longer a singleton in the lifted context. + info = liftedInfo(local)).installAfter(thisTransform) + } + for (local <- free.keys) + if (!liftedOwner.contains(local)) + local.copySymDenotation(info = liftedInfo(local)).installAfter(thisTransform) + } + + private def init(implicit ctx: Context) = { + (new CollectDependencies).traverse(NoSymbol, ctx.compilationUnit.tpdTree) + computeFreeVars() + computeLiftedOwners() + generateProxies()(ctx.withPhase(thisTransform.next)) + liftLocals()(ctx.withPhase(thisTransform.next)) + } + + override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { + val lifter = new LambdaLifter + lifter.init(ctx.withPhase(thisTransform)) + lifter + } + + private def currentEnclosure(implicit ctx: Context) = + ctx.owner.enclosingMethodOrClass + + private def inCurrentOwner(sym: Symbol)(implicit ctx: Context) = + sym.enclosure == currentEnclosure + + private def proxy(sym: Symbol)(implicit ctx: Context): Symbol = { + def liftedEnclosure(sym: Symbol) = liftedOwner.getOrElse(sym, sym.enclosure) + def searchIn(enclosure: Symbol): Symbol = { + if (!enclosure.exists) { + def enclosures(encl: Symbol): List[Symbol] = + if (encl.exists) encl :: enclosures(liftedEnclosure(encl)) else Nil + throw new IllegalArgumentException(i"Could not find proxy for ${sym.showDcl} in ${sym.ownersIterator.toList}, encl = $currentEnclosure, owners = ${currentEnclosure.ownersIterator.toList}%, %; enclosures = ${enclosures(currentEnclosure)}%, %") + } + ctx.debuglog(i"searching for $sym(${sym.owner}) in $enclosure") + proxyMap get enclosure match { + case Some(pmap) => + pmap get sym match { + case Some(proxy) => return proxy + case none => + } + case none => + } + searchIn(liftedEnclosure(enclosure)) + } + if (inCurrentOwner(sym)) sym else searchIn(currentEnclosure) + } + + private def memberRef(sym: Symbol)(implicit ctx: Context, info: TransformerInfo): Tree = { + val clazz = sym.enclosingClass + val qual = + if (clazz.isStaticOwner || ctx.owner.enclosingClass == clazz) + singleton(clazz.thisType) + else if (ctx.owner.isConstructor) + outerParam.get(ctx.owner) match { + case Some(param) => outer.path(clazz, Ident(param.termRef)) + case _ => outer.path(clazz) + } + else outer.path(clazz) + transformFollowingDeep(qual.select(sym)) + } + + private def proxyRef(sym: Symbol)(implicit ctx: Context, info: TransformerInfo): Tree = { + val psym = proxy(sym)(ctx.withPhase(thisTransform)) + transformFollowingDeep(if (psym.owner.isTerm) ref(psym) else memberRef(psym)) + } + + private def addFreeArgs(sym: Symbol, args: List[Tree])(implicit ctx: Context, info: TransformerInfo) = + free get sym match { + case Some(fvs) => fvs.toList.map(proxyRef(_)) ++ args + case _ => args + } + + private def addFreeParams(tree: Tree, proxies: List[Symbol])(implicit ctx: Context, info: TransformerInfo): Tree = proxies match { + case Nil => tree + case proxies => + val sym = tree.symbol + val freeParamDefs = proxies.map(proxy => + transformFollowingDeep(ValDef(proxy.asTerm).withPos(tree.pos)).asInstanceOf[ValDef]) + def proxyInit(field: Symbol, param: Symbol) = + transformFollowingDeep(memberRef(field).becomes(ref(param))) + + /** Initialize proxy fields from proxy parameters and map `rhs` from fields to parameters */ + def copyParams(rhs: Tree) = { + val fvs = freeVars(sym.owner) + val classProxies = fvs.map(proxyOf(sym.owner, _)) + val constrProxies = fvs.map(proxyOf(sym, _)) + ctx.debuglog(i"copy params ${constrProxies.map(_.showLocated)}%, % to ${classProxies.map(_.showLocated)}%, %}") + seq((classProxies, constrProxies).zipped.map(proxyInit), rhs) + } + + tree match { + case tree: DefDef => + cpy.DefDef(tree)( + vparamss = tree.vparamss.map(freeParamDefs ++ _), + rhs = + if (sym.isPrimaryConstructor && !sym.owner.is(Trait)) copyParams(tree.rhs) + else tree.rhs) + case tree: Template => + cpy.Template(tree)(body = freeParamDefs ++ tree.body) + } + } + + private def liftDef(tree: MemberDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val buf = liftedDefs(tree.symbol.owner) + transformFollowing(rename(tree, tree.symbol.name)).foreachInThicket(buf += _) + EmptyTree + } + + private def needsLifting(sym: Symbol) = liftedOwner contains sym + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = { + val sym = tree.symbol + tree.tpe match { + case tpe @ TermRef(prefix, _) => + if (prefix eq NoPrefix) + if (sym.enclosure != currentEnclosure && !sym.isStatic) + (if (sym is Method) memberRef(sym) else proxyRef(sym)).withPos(tree.pos) + else if (sym.owner.isClass) // sym was lifted out + ref(sym).withPos(tree.pos) + else + tree + else if (!prefixIsElidable(tpe)) ref(tpe) + else tree + case _ => + tree + } + } + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + cpy.Apply(tree)(tree.fun, addFreeArgs(tree.symbol, tree.args)).withPos(tree.pos) + + override def transformClosure(tree: Closure)(implicit ctx: Context, info: TransformerInfo) = + cpy.Closure(tree)(env = addFreeArgs(tree.meth.symbol, tree.env)) + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo) = { + val sym = tree.symbol + val paramsAdded = + if (free.contains(sym)) addFreeParams(tree, proxies(sym)).asInstanceOf[DefDef] + else tree + if (needsLifting(sym)) liftDef(paramsAdded) + else paramsAdded + } + + override def transformReturn(tree: Return)(implicit ctx: Context, info: TransformerInfo) = tree.expr match { + case Block(stats, value) => + Block(stats, Return(value, tree.from)).withPos(tree.pos) + case _ => + tree + } + + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = { + val cls = ctx.owner + val impl = addFreeParams(tree, proxies(cls)).asInstanceOf[Template] + cpy.Template(impl)(body = impl.body ++ liftedDefs.remove(cls).get) + } + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context, info: TransformerInfo) = + if (needsLifting(tree.symbol)) liftDef(tree) else tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala new file mode 100644 index 000000000..e63a7c3a7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -0,0 +1,418 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.core.Annotations.Annotation +import dotty.tools.dotc.core.Phases.NeedsCompanions + +import scala.collection.mutable +import core._ +import Contexts._ +import Symbols._ +import Decorators._ +import NameOps._ +import StdNames.nme +import rewrite.Rewrites.patch +import util.Positions.Position +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, MiniPhaseTransform} +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{untpd, tpd} +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Types.{ExprType, NoType, MethodType} +import dotty.tools.dotc.core.Names.Name +import SymUtils._ +import scala.collection.mutable.ListBuffer +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.core.DenotTransformers.{SymTransformer, IdentityDenotTransformer, DenotTransformer} +import Erasure.Boxing.adaptToType + +class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer { + import LazyVals._ + + import tpd._ + + def transformer = new LazyVals + + val containerFlags = Flags.Synthetic | Flags.Mutable | Flags.Lazy + val initFlags = Flags.Synthetic | Flags.Method + + val containerFlagsMask = Flags.Method | Flags.Lazy | Flags.Accessor | Flags.Module + + /** this map contains mutable state of transformation: OffsetDefs to be appended to companion object definitions, + * and number of bits currently used */ + class OffsetInfo(var defs: List[Tree], var ord:Int) + val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] + + override def phaseName: String = "LazyVals" + + /** List of names of phases that should have finished processing of tree + * before this phase starts processing same tree */ + override def runsAfter = Set(classOf[Mixin]) + + override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = + transformLazyVal(tree) + + + override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + transformLazyVal(tree) + } + + def transformLazyVal(tree: ValOrDefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val sym = tree.symbol + if (!(sym is Flags.Lazy) || sym.owner.is(Flags.Trait) || (sym.isStatic && sym.is(Flags.Module))) tree + else { + val isField = sym.owner.isClass + if (isField) { + if (sym.isVolatile || + (sym.is(Flags.Module)/* || ctx.scala2Mode*/) && + // TODO assume @volatile once LazyVals uses static helper constructs instead of + // ones in the companion object. + !sym.is(Flags.Synthetic)) + // module class is user-defined. + // Should be threadsafe, to mimic safety guaranteed by global object + transformMemberDefVolatile(tree) + else if (sym.is(Flags.Module)) // synthetic module + transformSyntheticModule(tree) + else + transformMemberDefNonVolatile(tree) + } + else transformLocalDef(tree) + } + } + + + /** Append offset fields to companion objects + */ + override def transformTemplate(template: tpd.Template)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val cls = ctx.owner.asClass + + appendOffsetDefs.get(cls) match { + case None => template + case Some(data) => + data.defs.foreach(_.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))) + cpy.Template(template)(body = addInFront(data.defs, template.body)) + } + + } + + private def addInFront(prefix: List[Tree], stats: List[Tree]) = stats match { + case first :: rest if isSuperConstrCall(first) => first :: prefix ::: rest + case _ => prefix ::: stats + } + + /** Make an eager val that would implement synthetic module. + * Eager val ensures thread safety and has less code generated. + * + */ + def transformSyntheticModule(tree: ValOrDefDef)(implicit ctx: Context) = { + val sym = tree.symbol + val holderSymbol = ctx.newSymbol(sym.owner, sym.asTerm.name.lazyLocalName, + Flags.Synthetic, sym.info.widen.resultType).enteredAfter(this) + val field = ValDef(holderSymbol, tree.rhs.changeOwnerAfter(sym, holderSymbol, this)) + val getter = DefDef(sym.asTerm, ref(holderSymbol)) + Thicket(field, getter) + } + + /** Replace a local lazy val inside a method, + * with a LazyHolder from + * dotty.runtime(eg dotty.runtime.LazyInt) + */ + def transformLocalDef(x: ValOrDefDef)(implicit ctx: Context) = { + val valueInitter = x.rhs + val holderName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName + val initName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL_INIT).toTermName + val tpe = x.tpe.widen.resultType.widen + + val holderType = + if (tpe isRef defn.IntClass) "LazyInt" + else if (tpe isRef defn.LongClass) "LazyLong" + else if (tpe isRef defn.BooleanClass) "LazyBoolean" + else if (tpe isRef defn.FloatClass) "LazyFloat" + else if (tpe isRef defn.DoubleClass) "LazyDouble" + else if (tpe isRef defn.ByteClass) "LazyByte" + else if (tpe isRef defn.CharClass) "LazyChar" + else if (tpe isRef defn.ShortClass) "LazyShort" + else "LazyRef" + + + val holderImpl = ctx.requiredClass("dotty.runtime." + holderType) + + val holderSymbol = ctx.newSymbol(x.symbol.owner, holderName, containerFlags, holderImpl.typeRef, coord = x.pos) + val initSymbol = ctx.newSymbol(x.symbol.owner, initName, initFlags, MethodType(Nil, tpe), coord = x.pos) + val result = ref(holderSymbol).select(lazyNme.value) + val flag = ref(holderSymbol).select(lazyNme.initialized) + val initer = valueInitter.changeOwner(x.symbol, initSymbol) + val initBody = + adaptToType( + ref(holderSymbol).select(defn.Object_synchronized).appliedTo( + adaptToType(mkNonThreadSafeDef(result, flag, initer), defn.ObjectType)), + tpe) + val initTree = DefDef(initSymbol, initBody) + val holderTree = ValDef(holderSymbol, New(holderImpl.typeRef, List())) + val methodBody = tpd.If(flag.ensureApplied, + result.ensureApplied, + ref(initSymbol).ensureApplied).ensureConforms(tpe) + + val methodTree = DefDef(x.symbol.asTerm, methodBody) + ctx.debuglog(s"found a lazy val ${x.show},\n rewrote with ${holderTree.show}") + Thicket(holderTree, initTree, methodTree) + } + + + override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = { + // backend requires field usage to be after field definition + // need to bring containers to start of method + val (holders, stats) = + atGroupEnd { implicit ctx: Context => + trees.partition { + _.symbol.flags.&~(Flags.Touched) == containerFlags + // Filtering out Flags.Touched is not required currently, as there are no LazyTypes involved here + // but just to be more safe + } + } + holders:::stats + } + + /** Create non-threadsafe lazy accessor equivalent to such code + * def methodSymbol() = { + * if (flag) target + * else { + * target = rhs + * flag = true + * target + * } + * } + */ + + def mkNonThreadSafeDef(target: Tree, flag: Tree, rhs: Tree)(implicit ctx: Context) = { + val setFlag = flag.becomes(Literal(Constants.Constant(true))) + val setTargets = if (isWildcardArg(rhs)) Nil else target.becomes(rhs) :: Nil + val init = Block(setFlag :: setTargets, target.ensureApplied) + If(flag.ensureApplied, target.ensureApplied, init) + } + + /** Create non-threadsafe lazy accessor for not-nullable types equivalent to such code + * def methodSymbol() = { + * if (target eq null) { + * target = rhs + * target + * } else target + * } + */ + def mkDefNonThreadSafeNonNullable(target: Symbol, rhs: Tree)(implicit ctx: Context) = { + val cond = ref(target).select(nme.eq).appliedTo(Literal(Constant(null))) + val exp = ref(target) + val setTarget = exp.becomes(rhs) + val init = Block(List(setTarget), exp) + If(cond, init, exp) + } + + def transformMemberDefNonVolatile(x: ValOrDefDef)(implicit ctx: Context) = { + val claz = x.symbol.owner.asClass + val tpe = x.tpe.widen.resultType.widen + assert(!(x.symbol is Flags.Mutable)) + val containerName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName + val containerSymbol = ctx.newSymbol(claz, containerName, + x.symbol.flags &~ containerFlagsMask | containerFlags | Flags.Private, + tpe, coord = x.symbol.coord + ).enteredAfter(this) + + val containerTree = ValDef(containerSymbol, defaultValue(tpe)) + if (x.tpe.isNotNull && tpe <:< defn.ObjectType) { // can use 'null' value instead of flag + val slowPath = DefDef(x.symbol.asTerm, mkDefNonThreadSafeNonNullable(containerSymbol, x.rhs)) + Thicket(containerTree, slowPath) + } + else { + val flagName = ctx.freshName(x.name ++ StdNames.nme.BITMAP_PREFIX).toTermName + val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags | Flags.Private, defn.BooleanType).enteredAfter(this) + val flag = ValDef(flagSymbol, Literal(Constants.Constant(false))) + val slowPath = DefDef(x.symbol.asTerm, mkNonThreadSafeDef(ref(containerSymbol), ref(flagSymbol), x.rhs)) + Thicket(containerTree, flag, slowPath) + } + } + + /** Create a threadsafe lazy accessor equivalent to such code + * + * def methodSymbol(): Int = { + * val result: Int = 0 + * val retry: Boolean = true + * var flag: Long = 0L + * while retry do { + * flag = dotty.runtime.LazyVals.get(this, $claz.$OFFSET) + * dotty.runtime.LazyVals.STATE(flag, 0) match { + * case 0 => + * if dotty.runtime.LazyVals.CAS(this, $claz.$OFFSET, flag, 1, $ord) { + * try {result = rhs} catch { + * case x: Throwable => + * dotty.runtime.LazyVals.setFlag(this, $claz.$OFFSET, 0, $ord) + * throw x + * } + * $target = result + * dotty.runtime.LazyVals.setFlag(this, $claz.$OFFSET, 3, $ord) + * retry = false + * } + * case 1 => + * dotty.runtime.LazyVals.wait4Notification(this, $claz.$OFFSET, flag, $ord) + * case 2 => + * dotty.runtime.LazyVals.wait4Notification(this, $claz.$OFFSET, flag, $ord) + * case 3 => + * retry = false + * result = $target + * } + * } + * result + * } + */ + def mkThreadSafeDef(methodSymbol: TermSymbol, claz: ClassSymbol, ord: Int, target: Symbol, rhs: Tree, tp: Types.Type, offset: Tree, getFlag: Tree, stateMask: Tree, casFlag: Tree, setFlagState: Tree, waitOnLock: Tree)(implicit ctx: Context) = { + val initState = Literal(Constants.Constant(0)) + val computeState = Literal(Constants.Constant(1)) + val notifyState = Literal(Constants.Constant(2)) + val computedState = Literal(Constants.Constant(3)) + val flagSymbol = ctx.newSymbol(methodSymbol, lazyNme.flag, containerFlags, defn.LongType) + val flagDef = ValDef(flagSymbol, Literal(Constant(0L))) + + val thiz = This(claz)(ctx.fresh.setOwner(claz)) + + val resultSymbol = ctx.newSymbol(methodSymbol, lazyNme.result, containerFlags, tp) + val resultDef = ValDef(resultSymbol, defaultValue(tp)) + + val retrySymbol = ctx.newSymbol(methodSymbol, lazyNme.retry, containerFlags, defn.BooleanType) + val retryDef = ValDef(retrySymbol, Literal(Constants.Constant(true))) + + val whileCond = ref(retrySymbol) + + val compute = { + val handlerSymbol = ctx.newSymbol(methodSymbol, nme.ANON_FUN, Flags.Synthetic, + MethodType(List(nme.x_1), List(defn.ThrowableType), defn.IntType)) + val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Flags.Synthetic, defn.ThrowableType) + val triggerRetry = setFlagState.appliedTo(thiz, offset, initState, Literal(Constant(ord))) + val complete = setFlagState.appliedTo(thiz, offset, computedState, Literal(Constant(ord))) + + val handler = CaseDef(Bind(caseSymbol, ref(caseSymbol)), EmptyTree, + Block(List(triggerRetry), Throw(ref(caseSymbol)) + )) + + val compute = ref(resultSymbol).becomes(rhs) + val tr = Try(compute, List(handler), EmptyTree) + val assign = ref(target).becomes(ref(resultSymbol)) + val noRetry = ref(retrySymbol).becomes(Literal(Constants.Constant(false))) + val body = If(casFlag.appliedTo(thiz, offset, ref(flagSymbol), computeState, Literal(Constant(ord))), + Block(tr :: assign :: complete :: noRetry :: Nil, Literal(Constant(()))), + Literal(Constant(()))) + + CaseDef(initState, EmptyTree, body) + } + + val waitFirst = { + val wait = waitOnLock.appliedTo(thiz, offset, ref(flagSymbol), Literal(Constant(ord))) + CaseDef(computeState, EmptyTree, wait) + } + + val waitSecond = { + val wait = waitOnLock.appliedTo(thiz, offset, ref(flagSymbol), Literal(Constant(ord))) + CaseDef(notifyState, EmptyTree, wait) + } + + val computed = { + val noRetry = ref(retrySymbol).becomes(Literal(Constants.Constant(false))) + val result = ref(resultSymbol).becomes(ref(target)) + val body = Block(noRetry :: result :: Nil, Literal(Constant(()))) + CaseDef(computedState, EmptyTree, body) + } + + val default = CaseDef(Underscore(defn.LongType), EmptyTree, Literal(Constant(()))) + + val cases = Match(stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord))), + List(compute, waitFirst, waitSecond, computed, default)) //todo: annotate with @switch + + val whileBody = List(ref(flagSymbol).becomes(getFlag.appliedTo(thiz, offset)), cases) + val cycle = WhileDo(methodSymbol, whileCond, whileBody) + DefDef(methodSymbol, Block(resultDef :: retryDef :: flagDef :: cycle :: Nil, ref(resultSymbol))) + } + + def transformMemberDefVolatile(x: ValOrDefDef)(implicit ctx: Context) = { + assert(!(x.symbol is Flags.Mutable)) + + val tpe = x.tpe.widen.resultType.widen + val claz = x.symbol.owner.asClass + val thizClass = Literal(Constant(claz.info)) + val helperModule = ctx.requiredModule("dotty.runtime.LazyVals") + val getOffset = Select(ref(helperModule), lazyNme.RLazyVals.getOffset) + var offsetSymbol: TermSymbol = null + var flag: Tree = EmptyTree + var ord = 0 + + def offsetName(id: Int) = (StdNames.nme.LAZY_FIELD_OFFSET + (if(x.symbol.owner.is(Flags.Module)) "_m_" else "") + id.toString).toTermName + + // compute or create appropriate offsetSymol, bitmap and bits used by current ValDef + appendOffsetDefs.get(claz) match { + case Some(info) => + val flagsPerLong = (64 / dotty.runtime.LazyVals.BITS_PER_LAZY_VAL).toInt + info.ord += 1 + ord = info.ord % flagsPerLong + val id = info.ord / flagsPerLong + val offsetById = offsetName(id) + if (ord != 0) { // there are unused bits in already existing flag + offsetSymbol = claz.info.decl(offsetById) + .suchThat(sym => (sym is Flags.Synthetic) && sym.isTerm) + .symbol.asTerm + } else { // need to create a new flag + offsetSymbol = ctx.newSymbol(claz, offsetById, Flags.Synthetic, defn.LongType).enteredAfter(this) + offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot)) + val flagName = (StdNames.nme.BITMAP_PREFIX + id.toString).toTermName + val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) + flag = ValDef(flagSymbol, Literal(Constants.Constant(0L))) + val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString)))) + info.defs = offsetTree :: info.defs + } + + case None => + offsetSymbol = ctx.newSymbol(claz, offsetName(0), Flags.Synthetic, defn.LongType).enteredAfter(this) + offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot)) + val flagName = (StdNames.nme.BITMAP_PREFIX + "0").toTermName + val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) + flag = ValDef(flagSymbol, Literal(Constants.Constant(0L))) + val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString)))) + appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree), ord)) + } + + val containerName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName + val containerSymbol = ctx.newSymbol(claz, containerName, x.symbol.flags &~ containerFlagsMask | containerFlags, tpe, coord = x.symbol.coord).enteredAfter(this) + + val containerTree = ValDef(containerSymbol, defaultValue(tpe)) + + val offset = ref(offsetSymbol) + val getFlag = Select(ref(helperModule), lazyNme.RLazyVals.get) + val setFlag = Select(ref(helperModule), lazyNme.RLazyVals.setFlag) + val wait = Select(ref(helperModule), lazyNme.RLazyVals.wait4Notification) + val state = Select(ref(helperModule), lazyNme.RLazyVals.state) + val cas = Select(ref(helperModule), lazyNme.RLazyVals.cas) + + val accessor = mkThreadSafeDef(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) + if (flag eq EmptyTree) + Thicket(containerTree, accessor) + else Thicket(containerTree, flag, accessor) + } +} + +object LazyVals { + object lazyNme { + object RLazyVals { + import dotty.runtime.LazyVals._ + val get = Names.get.toTermName + val setFlag = Names.setFlag.toTermName + val wait4Notification = Names.wait4Notification.toTermName + val state = Names.state.toTermName + val cas = Names.cas.toTermName + val getOffset = Names.getOffset.toTermName + } + val flag = "flag".toTermName + val result = "result".toTermName + val value = "value".toTermName + val initialized = "initialized".toTermName + val retry = "retry".toTermName + } +} + + + diff --git a/compiler/src/dotty/tools/dotc/transform/LiftTry.scala b/compiler/src/dotty/tools/dotc/transform/LiftTry.scala new file mode 100644 index 000000000..6a273b91e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/LiftTry.scala @@ -0,0 +1,66 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import NonLocalReturns._ + +/** Lifts try's that might be executed on non-empty expression stacks + * to their own methods. I.e. + * + * try body catch handler + * + * is lifted to + * + * { def liftedTree$n() = try body catch handler; liftedTree$n() } + */ +class LiftTry extends MiniPhase with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + /** the following two members override abstract members in Transform */ + val phaseName: String = "liftTry" + + val treeTransform = new Transform(needLift = false) + val liftingTransform = new Transform(needLift = true) + + class Transform(needLift: Boolean) extends TreeTransform { + def phase = thisTransform + + override def prepareForApply(tree: Apply)(implicit ctx: Context) = + if (tree.fun.symbol.is(Label)) this + else liftingTransform + + override def prepareForValDef(tree: ValDef)(implicit ctx: Context) = + if (!tree.symbol.exists || + tree.symbol.isSelfSym || + tree.symbol.owner == ctx.owner.enclosingMethod) this + else liftingTransform + + override def prepareForAssign(tree: Assign)(implicit ctx: Context) = + if (tree.lhs.symbol.maybeOwner == ctx.owner.enclosingMethod) this + else liftingTransform + + override def prepareForReturn(tree: Return)(implicit ctx: Context) = + if (!isNonLocalReturn(tree)) this + else liftingTransform + + override def prepareForTemplate(tree: Template)(implicit ctx: Context) = + treeTransform + + override def transformTry(tree: Try)(implicit ctx: Context, info: TransformerInfo): Tree = + if (needLift) { + ctx.debuglog(i"lifting tree at ${tree.pos}, current owner = ${ctx.owner}") + val fn = ctx.newSymbol( + ctx.owner, ctx.freshName("liftedTree").toTermName, Synthetic | Method, + MethodType(Nil, tree.tpe), coord = tree.pos) + tree.changeOwnerAfter(ctx.owner, fn, thisTransform) + Block(DefDef(fn, tree) :: Nil, ref(fn).appliedToNone) + } + else tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala b/compiler/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala new file mode 100644 index 000000000..ca06938dc --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala @@ -0,0 +1,62 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import Contexts.Context +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import StdNames._ +import NameOps._ +import Phases._ +import ast.untpd +import ast.Trees._ +import collection.mutable + +/** Rewrite calls + * + * super[M].f(args) + * + * where M is a Scala2 trait implemented by the current class to + * + * M$class.f(this, args) + * + * provided the implementation class M$class defines a corresponding function `f`. + */ +class LinkScala2ImplClasses extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + override def phaseName: String = "linkScala2ImplClasses" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Mixin]) + + override def transformApply(app: Apply)(implicit ctx: Context, info: TransformerInfo) = { + def currentClass = ctx.owner.enclosingClass.asClass + app match { + case Apply(sel @ Select(Super(_, _), _), args) + if sel.symbol.owner.is(Scala2xTrait) && currentClass.mixins.contains(sel.symbol.owner) => + val impl = implMethod(sel.symbol) + if (impl.exists) Apply(ref(impl), This(currentClass) :: args).withPos(app.pos) + else app // could have been an abstract method in a trait linked to from a super constructor + case _ => + app + } + } + + private def implMethod(meth: Symbol)(implicit ctx: Context): Symbol = { + val implInfo = meth.owner.implClass.info + if (meth.isConstructor) + implInfo.decl(nme.TRAIT_CONSTRUCTOR).symbol + else + implInfo.decl(meth.name) + .suchThat(c => FullParameterization.memberSignature(c.info) == meth.signature) + .symbol + } + + private val Scala2xTrait = allOf(Scala2x, Trait) +} diff --git a/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled b/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled new file mode 100644 index 000000000..f33baa52b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled @@ -0,0 +1,95 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Constants._ + +/** This phase rewrites idempotent expressions with constant types to Literals. + * The constant types are eliminated by erasure, so we need to keep + * the info about constantness in the trees. + * + * The phase also makes sure that the constant of a literal is the same as the constant + * in the type of the literal. + */ +class Literalize extends MiniPhaseTransform { thisTransform => + import ast.tpd._ + + override def phaseName: String = "literalize" + + /** Note: Demanding idempotency instead of purity is strictly speaking too loose. + * Example + * + * object O { final val x = 42; println("43") } + * O.x + * + * Strictly speaking we can't replace `O.x` with `42`. But this would make + * most expressions non-constant. Maybe we can change the spec to accept this + * kind of eliding behavior. Or else enforce true purity in the compiler. + * The choice will be affected by what we will do with `inline` and with + * Singleton type bounds (see SIP 23). Presumably + * + * object O1 { val x: Singleton = 42; println("43") } + * object O2 { inline val x = 42; println("43") } + * + * should behave differently. + * + * O1.x should have the same effect as { println("43"; 42 } + * + * whereas + * + * O2.x = 42 + * + * Revisit this issue once we have implemented `inline`. Then we can demand + * purity of the prefix unless the selection goes to an inline val. + */ + def literalize(tree: Tree)(implicit ctx: Context): Tree = { + def recur(tp: Type): Tree = tp match { + case ConstantType(value) if isIdempotentExpr(tree) => Literal(value) + case tp: TermRef if tp.symbol.isStable => recur(tp.info.widenExpr) + case _ => tree + } + recur(tree.tpe) + } + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) + + override def transformLiteral(tree: Literal)(implicit ctx: Context, info: TransformerInfo): Tree = tree.tpe match { + case ConstantType(const) if tree.const.value != const.value || (tree.const.tag != const.tag) => Literal(const) + case _ => tree + } + + /** Check that all literals have types match underlying constants + */ + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case Literal(c @ Constant(treeValue)) => + tree.tpe match { + case ConstantType(c2 @ Constant(typeValue)) => + assert(treeValue == typeValue && c2.tag == c.tag, + i"Type of Literal $tree is inconsistent with underlying constant") + case tpe => + assert(c.tpe =:= tpe, i"Type of Literal $tree is inconsistent with underlying constant type ${c.tpe}") + } + case _ => + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala new file mode 100644 index 000000000..9634decaa --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -0,0 +1,70 @@ +package dotty.tools.dotc +package transform + +import core._ +import typer._ +import Phases._ +import ast.Trees._ +import Contexts._ +import Symbols._ +import Flags.PackageVal +import Decorators._ + +/** A base class for transforms. + * A transform contains a compiler phase which applies a tree transformer. + */ +abstract class MacroTransform extends Phase { + + import ast.tpd._ + + override def run(implicit ctx: Context): Unit = { + val unit = ctx.compilationUnit + unit.tpdTree = newTransformer.transform(unit.tpdTree)(ctx.withPhase(transformPhase)) + } + + protected def newTransformer(implicit ctx: Context): Transformer + + /** The phase in which the transformation should be run. + * By default this is the phase given by the this macro transformer, + * but it could be overridden to be the phase following that one. + */ + protected def transformPhase(implicit ctx: Context): Phase = this + + class Transformer extends TreeMap { + + protected def localCtx(tree: Tree)(implicit ctx: Context) = { + val sym = tree.symbol + val owner = if (sym is PackageVal) sym.moduleClass else sym + ctx.fresh.setTree(tree).setOwner(owner) + } + + def transformStats(trees: List[Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + def transformStat(stat: Tree): Tree = stat match { + case _: Import | _: DefTree => transform(stat) + case Thicket(stats) => cpy.Thicket(stat)(stats mapConserve transformStat) + case _ => transform(stat)(ctx.exprContext(stat, exprOwner)) + } + flatten(trees.mapconserve(transformStat(_))) + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + tree match { + case EmptyValDef => + tree + case _: PackageDef | _: MemberDef => + super.transform(tree)(localCtx(tree)) + case impl @ Template(constr, parents, self, _) => + cpy.Template(tree)( + transformSub(constr), + transform(parents)(ctx.superCallContext), + transformSelf(self), + transformStats(impl.body, tree.symbol)) + case _ => + super.transform(tree) + } + } + + def transformSelf(vd: ValDef)(implicit ctx: Context) = + cpy.ValDef(vd)(tpt = transform(vd.tpt)) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/Memoize.scala b/compiler/src/dotty/tools/dotc/transform/Memoize.scala new file mode 100644 index 000000000..01c240e3a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Memoize.scala @@ -0,0 +1,129 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers._ +import Phases.Phase +import Contexts.Context +import SymDenotations.SymDenotation +import Types._ +import Symbols._ +import SymUtils._ +import Constants._ +import ast.Trees._ +import TreeTransforms._ +import NameOps._ +import Flags._ +import Decorators._ + +/** Provides the implementations of all getters and setters, introducing + * fields to hold the value accessed by them. + * TODO: Make LazyVals a part of this phase? + * + * <accessor> <stable> <mods> def x(): T = e + * --> private val x: T = e + * <accessor> <stable> <mods> def x(): T = x + * + * <accessor> <mods> def x(): T = e + * --> private var x: T = e + * <accessor> <mods> def x(): T = x + * + * <accessor> <mods> def x_=(y: T): Unit = () + * --> <accessor> <mods> def x_=(y: T): Unit = x = y + */ + class Memoize extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + override def phaseName = "memoize" + + /* Makes sure that, after getters and constructors gen, there doesn't + * exist non-deferred definitions that are not implemented. */ + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { + def errorLackImplementation(t: Tree) = { + val firstPhaseId = t.symbol.initial.validFor.firstPhaseId + val definingPhase = ctx.withPhase(firstPhaseId).phase.prev + throw new AssertionError( + i"Non-deferred definition introduced by $definingPhase lacks implementation: $t") + } + tree match { + case ddef: DefDef + if !ddef.symbol.is(Deferred) && ddef.rhs == EmptyTree => + errorLackImplementation(ddef) + case tdef: TypeDef + if tdef.symbol.isClass && !tdef.symbol.is(Deferred) && tdef.rhs == EmptyTree => + errorLackImplementation(tdef) + case _ => + } + super.checkPostCondition(tree) + } + + /** Should run after mixin so that fields get generated in the + * class that contains the concrete getter rather than the trait + * that defines it. + */ + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Mixin]) + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val sym = tree.symbol + + def newField = { + val fieldType = + if (sym.isGetter) sym.info.resultType + else /*sym.isSetter*/ sym.info.firstParamTypes.head + + ctx.newSymbol( + owner = ctx.owner, + name = sym.name.asTermName.fieldName, + flags = Private | (if (sym is Stable) EmptyFlags else Mutable), + info = fieldType, + coord = tree.pos) + .withAnnotationsCarrying(sym, defn.FieldMetaAnnot) + .enteredAfter(thisTransform) + } + + /** Can be used to filter annotations on getters and setters; not used yet */ + def keepAnnotations(denot: SymDenotation, meta: ClassSymbol) = { + val cpy = sym.copySymDenotation() + cpy.filterAnnotations(_.symbol.derivesFrom(meta)) + if (cpy.annotations ne denot.annotations) cpy.installAfter(thisTransform) + } + + lazy val field = sym.field.orElse(newField).asTerm + + def adaptToField(tree: Tree) = + if (tree.isEmpty) tree else tree.ensureConforms(field.info.widen) + + if (sym.is(Accessor, butNot = NoFieldNeeded)) + if (sym.isGetter) { + def skipBlocks(t: Tree): Tree = t match { + case Block(_, t1) => skipBlocks(t1) + case _ => t + } + skipBlocks(tree.rhs) match { + case lit: Literal if sym.is(Final) && isIdempotentExpr(tree.rhs) => + // duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field + // and instead return the value. This seemingly minor optimization has huge effect on initialization + // order and the values that can be observed during superconstructor call + + // see remark about idempotency in PostTyper#normalizeTree + cpy.DefDef(tree)(rhs = lit) + case _ => + var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform) + if (isWildcardArg(rhs)) rhs = EmptyTree + + val fieldDef = transformFollowing(ValDef(field, adaptToField(rhs))) + val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)) + Thicket(fieldDef, getterDef) + } + } else if (sym.isSetter) { + if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion + field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits + val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol))) + cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info)) + } + else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as + // neither getters nor setters + else tree + } + private val NoFieldNeeded = Lazy | Deferred | JavaDefined +} diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala new file mode 100644 index 000000000..27cfc835a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -0,0 +1,257 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import Contexts.Context +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import StdNames._ +import NameOps._ +import Phases._ +import ast.untpd +import ast.Trees._ +import collection.mutable + +/** This phase performs the following transformations: + * + * 1. (done in `traitDefs` and `transformSym`) Map every concrete trait getter + * + * <mods> def x(): T = expr + * + * to the pair of definitions: + * + * <mods> def x(): T + * protected def initial$x(): T = { stats; expr } + * + * where `stats` comprises all statements between either the start of the trait + * or the previous field definition which are not definitions (i.e. are executed for + * their side effects). + * + * 2. (done in `traitDefs`) Make every concrete trait setter + * + * <mods> def x_=(y: T) = () + * + * deferred by mapping it to + * + * <mods> def x_=(y: T) + * + * 3. For a non-trait class C: + * + * For every trait M directly implemented by the class (see SymUtils.mixin), in + * reverse linearization order, add the following definitions to C: + * + * 3.1 (done in `traitInits`) For every parameter accessor `<mods> def x(): T` in M, + * in order of textual occurrence, add + * + * <mods> def x() = e + * + * where `e` is the constructor argument in C that corresponds to `x`. Issue + * an error if no such argument exists. + * + * 3.2 (done in `traitInits`) For every concrete trait getter `<mods> def x(): T` in M + * which is not a parameter accessor, in order of textual occurrence, produce the following: + * + * 3.2.1 If `x` is also a member of `C`, and M is a Dotty trait: + * + * <mods> def x(): T = super[M].initial$x() + * + * 3.2.2 If `x` is also a member of `C`, and M is a Scala 2.x trait: + * + * <mods> def x(): T = _ + * + * 3.2.3 If `x` is not a member of `C`, and M is a Dotty trait: + * + * super[M].initial$x() + * + * 3.2.4 If `x` is not a member of `C`, and M is a Scala2.x trait, nothing gets added. + * + * + * 3.3 (done in `superCallOpt`) The call: + * + * super[M].<init> + * + * 3.4 (done in `setters`) For every concrete setter `<mods> def x_=(y: T)` in M: + * + * <mods> def x_=(y: T) = () + * + * 4. (done in `transformTemplate` and `transformSym`) Drop all parameters from trait + * constructors. + * + * 5. (done in `transformSym`) Drop ParamAccessor flag from all parameter accessors in traits. + * + * Conceptually, this is the second half of the previous mixin phase. It needs to run + * after erasure because it copies references to possibly private inner classes and objects + * into enclosing classes where they are not visible. This can only be done if all references + * are symbolic. + */ +class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => + import ast.tpd._ + + override def phaseName: String = "mixin" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) + + override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = + if (sym.is(Accessor, butNot = Deferred) && sym.owner.is(Trait)) { + val sym1 = + if (sym is Lazy) sym + else sym.copySymDenotation(initFlags = sym.flags &~ ParamAccessor | Deferred) + sym1.ensureNotPrivate + } + else if (sym.isConstructor && sym.owner.is(Trait)) + sym.copySymDenotation( + name = nme.TRAIT_CONSTRUCTOR, + info = MethodType(Nil, sym.info.resultType)) + else + sym + + private def initializer(sym: Symbol)(implicit ctx: Context): TermSymbol = { + if (sym is Lazy) sym + else { + val initName = InitializerName(sym.name.asTermName) + sym.owner.info.decl(initName).symbol + .orElse( + ctx.newSymbol( + sym.owner, + initName, + Protected | Synthetic | Method, + sym.info, + coord = sym.symbol.coord).enteredAfter(thisTransform)) + } + }.asTerm + + override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = { + val cls = impl.symbol.owner.asClass + val ops = new MixinOps(cls, thisTransform) + import ops._ + + def traitDefs(stats: List[Tree]): List[Tree] = { + val initBuf = new mutable.ListBuffer[Tree] + stats.flatMap({ + case stat: DefDef if stat.symbol.isGetter && !stat.rhs.isEmpty && !stat.symbol.is(Flags.Lazy) => + // make initializer that has all effects of previous getter, + // replace getter rhs with empty tree. + val vsym = stat.symbol + val isym = initializer(vsym) + val rhs = Block( + initBuf.toList.map(_.changeOwnerAfter(impl.symbol, isym, thisTransform)), + stat.rhs.changeOwnerAfter(vsym, isym, thisTransform).wildcardToDefault) + initBuf.clear() + cpy.DefDef(stat)(rhs = EmptyTree) :: DefDef(isym, rhs) :: Nil + case stat: DefDef if stat.symbol.isSetter => + cpy.DefDef(stat)(rhs = EmptyTree) :: Nil + case stat: DefTree => + stat :: Nil + case stat => + initBuf += stat + Nil + }) ++ initBuf + } + + /** Map constructor call to a pair of a supercall and a list of arguments + * to be used as initializers of trait parameters if the target of the call + * is a trait. + */ + def transformConstructor(tree: Tree): (Tree, List[Tree]) = { + val Apply(sel @ Select(New(_), nme.CONSTRUCTOR), args) = tree + val (callArgs, initArgs) = if (tree.symbol.owner.is(Trait)) (Nil, args) else (args, Nil) + (superRef(tree.symbol, tree.pos).appliedToArgs(callArgs), initArgs) + } + + val superCallsAndArgs = ( + for (p <- impl.parents if p.symbol.isConstructor) + yield p.symbol.owner -> transformConstructor(p) + ).toMap + val superCalls = superCallsAndArgs.mapValues(_._1) + val initArgs = superCallsAndArgs.mapValues(_._2) + + def superCallOpt(baseCls: Symbol): List[Tree] = superCalls.get(baseCls) match { + case Some(call) => + if (defn.PhantomClasses.contains(baseCls)) Nil else call :: Nil + case None => + if (baseCls.is(NoInitsTrait) || defn.PhantomClasses.contains(baseCls)) Nil + else { + //println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}") + transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil + } + } + + def was(sym: Symbol, flags: FlagSet) = + ctx.atPhase(thisTransform) { implicit ctx => sym is flags } + + def traitInits(mixin: ClassSymbol): List[Tree] = { + var argNum = 0 + def nextArgument() = initArgs.get(mixin) match { + case Some(arguments) => + val result = arguments(argNum) + argNum += 1 + result + case None => + assert( + impl.parents.forall(_.tpe.typeSymbol != mixin), + i"missing parameters for $mixin from $impl should have been caught in typer") + ctx.error( + em"""parameterized $mixin is indirectly implemented, + |needs to be implemented directly so that arguments can be passed""", + cls.pos) + EmptyTree + } + + for (getter <- mixin.info.decls.toList if getter.isGetter && !was(getter, Deferred)) yield { + val isScala2x = mixin.is(Scala2x) + def default = Underscore(getter.info.resultType) + def initial = transformFollowing(superRef(initializer(getter)).appliedToNone) + + /** A call to the implementation of `getter` in `mixin`'s implementation class */ + def lazyGetterCall = { + def canbeImplClassGetter(sym: Symbol) = sym.info.firstParamTypes match { + case t :: Nil => t.isDirectRef(mixin) + case _ => false + } + val implClassGetter = mixin.implClass.info.nonPrivateDecl(getter.name) + .suchThat(canbeImplClassGetter).symbol + ref(mixin.implClass).select(implClassGetter).appliedTo(This(cls)) + } + + if (isCurrent(getter) || getter.is(ExpandedName)) { + val rhs = + if (was(getter, ParamAccessor)) nextArgument() + else if (isScala2x) + if (getter.is(Lazy, butNot = Module)) lazyGetterCall + else if (getter.is(Module)) + New(getter.info.resultType, List(This(cls))) + else Underscore(getter.info.resultType) + else initial + // transformFollowing call is needed to make memoize & lazy vals run + transformFollowing(DefDef(implementation(getter.asTerm), rhs)) + } + else if (isScala2x || was(getter, ParamAccessor)) EmptyTree + else initial + } + } + + def setters(mixin: ClassSymbol): List[Tree] = + for (setter <- mixin.info.decls.filter(setr => setr.isSetter && !was(setr, Deferred)).toList) + yield transformFollowing(DefDef(implementation(setter.asTerm), unitLiteral.withPos(cls.pos))) + + cpy.Template(impl)( + constr = + if (cls.is(Trait)) cpy.DefDef(impl.constr)(vparamss = Nil :: Nil) + else impl.constr, + parents = impl.parents.map(p => TypeTree(p.tpe).withPos(p.pos)), + body = + if (cls is Trait) traitDefs(impl.body) + else { + val mixInits = mixins.flatMap { mixin => + flatten(traitInits(mixin)) ::: superCallOpt(mixin) ::: setters(mixin) + } + superCallOpt(superCls) ::: mixInits ::: impl.body + }) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala new file mode 100644 index 000000000..6cebf7197 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -0,0 +1,68 @@ +package dotty.tools.dotc +package transform + +import core._ +import Symbols._, Types._, Contexts._, SymDenotations._, DenotTransformers._, Flags._ +import util.Positions._ +import SymUtils._ +import StdNames._, NameOps._ + +class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx: Context) { + import ast.tpd._ + + val superCls: Symbol = cls.superClass + val mixins: List[ClassSymbol] = cls.mixins + + def implementation(member: TermSymbol): TermSymbol = { + val res = member.copy( + owner = cls, + name = member.name.stripScala2LocalSuffix, + flags = member.flags &~ Deferred, + info = cls.thisType.memberInfo(member)).enteredAfter(thisTransform).asTerm + res.addAnnotations(member.annotations) + res + } + + def superRef(target: Symbol, pos: Position = cls.pos): Tree = { + val sup = if (target.isConstructor && !target.owner.is(Trait)) + Super(This(cls), tpnme.EMPTY, true) + else + Super(This(cls), target.owner.name.asTypeName, false, target.owner) + //println(i"super ref $target on $sup") + ast.untpd.Select(sup.withPos(pos), target.name) + .withType(NamedType.withFixedSym(sup.tpe, target)) + //sup.select(target) + } + + /** Is `sym` a member of implementing class `cls`? + * The test is performed at phase `thisTransform`. + */ + def isCurrent(sym: Symbol) = + ctx.atPhase(thisTransform) { implicit ctx => + cls.info.member(sym.name).hasAltWith(_.symbol == sym) + } + + /** Does `method` need a forwarder to in class `cls` + * Method needs a forwarder in those cases: + * - there's a class defining a method with same signature + * - there are multiple traits defining method with same signature + */ + def needsForwarder(meth: Symbol): Boolean = { + lazy val competingMethods = cls.baseClasses.iterator + .filter(_ ne meth.owner) + .map(meth.overriddenSymbol) + .filter(_.exists) + .toList + + def needsDisambiguation = competingMethods.exists(x=> !(x is Deferred)) // multiple implementations are available + def hasNonInterfaceDefinition = competingMethods.exists(!_.owner.is(Trait)) // there is a definition originating from class + meth.is(Method, butNot = PrivateOrAccessorOrDeferred) && + isCurrent(meth) && + (needsDisambiguation || hasNonInterfaceDefinition || meth.owner.is(Scala2x)) + } + + final val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred + + def forwarder(target: Symbol) = (targs: List[Type]) => (vrefss: List[List[Tree]]) => + superRef(target).appliedToTypes(targs).appliedToArgss(vrefss) +} diff --git a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala new file mode 100644 index 000000000..5c2cd3145 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala @@ -0,0 +1,77 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.{Trees, tpd} +import dotty.tools.dotc.core.Annotations.Annotation +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.DenotTransformers.{InfoTransformer, SymTransformer} +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.NameOps._ +import dotty.tools.dotc.core.{Flags, Names} +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types.MethodType +import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo} + +/** Move static methods from companion to the class itself */ +class MoveStatics extends MiniPhaseTransform with SymTransformer { thisTransformer => + + import tpd._ + override def phaseName = "moveStatic" + + + def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { + if (sym.hasAnnotation(defn.ScalaStaticAnnot) && sym.owner.is(Flags.Module) && sym.owner.companionClass.exists) { + sym.owner.asClass.delete(sym.symbol) + sym.owner.companionClass.asClass.enter(sym.symbol) + val flags = if (sym.is(Flags.Method)) sym.flags else sym.flags | Flags.Mutable + sym.copySymDenotation(owner = sym.owner.companionClass, initFlags = flags) + } + else sym + } + + override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = { + if (ctx.owner.is(Flags.Package)) { + val (classes, others) = trees.partition(x => x.isInstanceOf[TypeDef] && x.symbol.isClass) + val pairs = classes.groupBy(_.symbol.name.stripModuleClassSuffix).asInstanceOf[Map[Name, List[TypeDef]]] + + def rebuild(orig: TypeDef, newBody: List[Tree]): Tree = { + if (orig eq null) return EmptyTree + + val staticFields = newBody.filter(x => x.isInstanceOf[ValDef] && x.symbol.hasAnnotation(defn.ScalaStaticAnnot)).asInstanceOf[List[ValDef]] + val newBodyWithStaticConstr = + if (staticFields.nonEmpty) { + /* do NOT put Flags.JavaStatic here. It breaks .enclosingClass */ + val staticCostructor = ctx.newSymbol(orig.symbol, Names.STATIC_CONSTRUCTOR, Flags.Synthetic | Flags.Method | Flags.Private, MethodType(Nil, defn.UnitType)) + staticCostructor.addAnnotation(Annotation(defn.ScalaStaticAnnot)) + staticCostructor.entered + + val staticAssigns = staticFields.map(x => Assign(ref(x.symbol), x.rhs.changeOwner(x.symbol, staticCostructor))) + tpd.DefDef(staticCostructor, Block(staticAssigns, tpd.unitLiteral)) :: newBody + } else newBody + + val oldTemplate = orig.rhs.asInstanceOf[Template] + cpy.TypeDef(orig)(rhs = cpy.Template(orig.rhs)(oldTemplate.constr, oldTemplate.parents, oldTemplate.self, newBodyWithStaticConstr)) + } + + def move(module: TypeDef, companion: TypeDef): List[Tree] = { + if (!module.symbol.is(Flags.Module)) move(companion, module) + else { + val allMembers = + (if(companion ne null) {companion.rhs.asInstanceOf[Template].body} else Nil) ++ + module.rhs.asInstanceOf[Template].body + val (newModuleBody, newCompanionBody) = allMembers.partition(x => {assert(x.symbol.exists); x.symbol.owner == module.symbol}) + Trees.flatten(rebuild(companion, newCompanionBody) :: rebuild(module, newModuleBody) :: Nil) + } + } + val newPairs = + for ((name, classes) <- pairs) + yield + if (classes.tail.isEmpty) + if (classes.head.symbol.is(Flags.Module)) move(classes.head, null) + else List(rebuild(classes.head, classes.head.rhs.asInstanceOf[Template].body)) + else move(classes.head, classes.tail.head) + Trees.flatten(newPairs.toList.flatten ++ others) + } else trees + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala new file mode 100644 index 000000000..7680e283e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala @@ -0,0 +1,92 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts._, Symbols._, Types._, Flags._, Decorators._, StdNames._, Constants._, Phases._ +import TreeTransforms._ +import ast.Trees._ +import collection.mutable + +object NonLocalReturns { + import ast.tpd._ + def isNonLocalReturn(ret: Return)(implicit ctx: Context) = + ret.from.symbol != ctx.owner.enclosingMethod || ctx.owner.is(Lazy) +} + +/** Implement non-local returns using NonLocalReturnControl exceptions. + */ +class NonLocalReturns extends MiniPhaseTransform { thisTransformer => + override def phaseName = "nonLocalReturns" + + import NonLocalReturns._ + import ast.tpd._ + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimByName]) + + private def ensureConforms(tree: Tree, pt: Type)(implicit ctx: Context) = + if (tree.tpe <:< pt) tree + else Erasure.Boxing.adaptToType(tree, pt) + + /** The type of a non-local return expression with given argument type */ + private def nonLocalReturnExceptionType(argtype: Type)(implicit ctx: Context) = + defn.NonLocalReturnControlType.appliedTo(argtype) + + /** A hashmap from method symbols to non-local return keys */ + private val nonLocalReturnKeys = mutable.Map[Symbol, TermSymbol]() + + /** Return non-local return key for given method */ + private def nonLocalReturnKey(meth: Symbol)(implicit ctx: Context) = + nonLocalReturnKeys.getOrElseUpdate(meth, + ctx.newSymbol( + meth, ctx.freshName("nonLocalReturnKey").toTermName, Synthetic, defn.ObjectType, coord = meth.pos)) + + /** Generate a non-local return throw with given return expression from given method. + * I.e. for the method's non-local return key, generate: + * + * throw new NonLocalReturnControl(key, expr) + * todo: maybe clone a pre-existing exception instead? + * (but what to do about exceptions that miss their targets?) + */ + private def nonLocalReturnThrow(expr: Tree, meth: Symbol)(implicit ctx: Context) = + Throw( + New( + defn.NonLocalReturnControlType, + ref(nonLocalReturnKey(meth)) :: expr.ensureConforms(defn.ObjectType) :: Nil)) + + /** Transform (body, key) to: + * + * { + * val key = new Object() + * try { + * body + * } catch { + * case ex: NonLocalReturnControl => + * if (ex.key().eq(key)) ex.value().asInstanceOf[T] + * else throw ex + * } + * } + */ + private def nonLocalReturnTry(body: Tree, key: TermSymbol, meth: Symbol)(implicit ctx: Context) = { + val keyDef = ValDef(key, New(defn.ObjectType, Nil)) + val nonLocalReturnControl = defn.NonLocalReturnControlType + val ex = ctx.newSymbol(meth, nme.ex, EmptyFlags, nonLocalReturnControl, coord = body.pos) + val pat = BindTyped(ex, nonLocalReturnControl) + val rhs = If( + ref(ex).select(nme.key).appliedToNone.select(nme.eq).appliedTo(ref(key)), + ref(ex).select(nme.value).ensureConforms(meth.info.finalResultType), + Throw(ref(ex))) + val catches = CaseDef(pat, EmptyTree, rhs) :: Nil + val tryCatch = Try(body, catches, EmptyTree) + Block(keyDef :: Nil, tryCatch) + } + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = + nonLocalReturnKeys.remove(tree.symbol) match { + case Some(key) => cpy.DefDef(tree)(rhs = nonLocalReturnTry(tree.rhs, key, tree.symbol)) + case _ => tree + } + + override def transformReturn(tree: Return)(implicit ctx: Context, info: TransformerInfo): Tree = + if (isNonLocalReturn(tree)) nonLocalReturnThrow(tree.expr, tree.from.symbol).withPos(tree.pos) + else tree +} diff --git a/compiler/src/dotty/tools/dotc/transform/NormalizeFlags.scala b/compiler/src/dotty/tools/dotc/transform/NormalizeFlags.scala new file mode 100644 index 000000000..755846904 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/NormalizeFlags.scala @@ -0,0 +1,25 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers.SymTransformer +import Phases.Phase +import Contexts.Context +import SymDenotations.SymDenotation +import TreeTransforms.MiniPhaseTransform +import Flags._, Symbols._ + +/** 1. Widens all private[this] and protected[this] qualifiers to just private/protected + * 2. Sets PureInterface flag for traits that only have pure interface members and that + * do not have initialization code. A pure interface member is either an abstract + * or alias type definition or a deferred val or def. + */ +class NormalizeFlags extends MiniPhaseTransform with SymTransformer { thisTransformer => + override def phaseName = "normalizeFlags" + + def transformSym(ref: SymDenotation)(implicit ctx: Context) = { + var newFlags = ref.flags &~ Local + if (newFlags != ref.flags) ref.copySymDenotation(initFlags = newFlags) + else ref + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala new file mode 100644 index 000000000..650a03054 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -0,0 +1,140 @@ +package dotty.tools.dotc +package transform + +import core._ +import Flags._, Symbols._, Contexts._, Types._, Scopes._, Decorators._ +import util.HashSet +import collection.mutable +import collection.immutable.BitSet +import scala.annotation.tailrec + +/** A module that can produce a kind of iterator (`Cursor`), + * which yields all pairs of overriding/overridden symbols + * that are visible in some baseclass, unless there's a parent class + * that already contains the same pairs. + * + * Adapted from the 2.9 version of OverridingPairs. The 2.10 version is IMO + * way too unwieldy to be maintained. + */ +object OverridingPairs { + + /** The cursor class + * @param base the base class that contains the overriding pairs + */ + class Cursor(base: Symbol)(implicit ctx: Context) { + + private val self = base.thisType + + /** Symbols to exclude: Here these are constructors and private locals. + * But it may be refined in subclasses. + */ + protected def exclude(sym: Symbol): Boolean = !sym.memberCanMatchInheritedSymbols + + /** The parents of base (may also be refined). + */ + protected def parents: Array[Symbol] = base.info.parents.toArray map (_.typeSymbol) + + /** Does `sym1` match `sym2` so that it qualifies as overriding. + * Types always match. Term symbols match if their membertypes + * relative to <base>.this do + */ + protected def matches(sym1: Symbol, sym2: Symbol): Boolean = + sym1.isType || self.memberInfo(sym1).matches(self.memberInfo(sym2)) + + /** The symbols that can take part in an overriding pair */ + private val decls = { + val decls = newScope + // fill `decls` with overriding shadowing overridden */ + def fillDecls(bcs: List[Symbol], deferred: Boolean): Unit = bcs match { + case bc :: bcs1 => + fillDecls(bcs1, deferred) + var e = bc.info.decls.lastEntry + while (e != null) { + if (e.sym.is(Deferred) == deferred && !exclude(e.sym)) + decls.enter(e.sym) + e = e.prev + } + case nil => + } + // first, deferred (this will need to change if we change lookup rules! + fillDecls(base.info.baseClasses, deferred = true) + // then, concrete. + fillDecls(base.info.baseClasses, deferred = false) + decls + } + + private val subParents = { + val subParents = new mutable.HashMap[Symbol, BitSet] + for (bc <- base.info.baseClasses) + subParents(bc) = BitSet(parents.indices.filter(parents(_).derivesFrom(bc)): _*) + subParents + } + + private def hasCommonParentAsSubclass(cls1: Symbol, cls2: Symbol): Boolean = + (subParents(cls1) intersect subParents(cls2)).nonEmpty + + /** The scope entries that have already been visited as overridden + * (maybe excluded because of hasCommonParentAsSubclass). + * These will not appear as overriding + */ + private val visited = new mutable.HashSet[Symbol] + + /** The current entry candidate for overriding + */ + private var curEntry = decls.lastEntry + + /** The current entry candidate for overridden */ + private var nextEntry = curEntry + + /** The current candidate symbol for overriding */ + var overriding: Symbol = _ + + /** If not null: The symbol overridden by overriding */ + var overridden: Symbol = _ + + //@M: note that next is called once during object initialization + final def hasNext: Boolean = nextEntry ne null + + /** @post + * curEntry = the next candidate that may override something else + * nextEntry = curEntry + * overriding = curEntry.sym + */ + private def nextOverriding(): Unit = { + @tailrec def loop(): Unit = + if (curEntry ne null) { + overriding = curEntry.sym + if (visited.contains(overriding)) { + curEntry = curEntry.prev + loop() + } + } + loop() + nextEntry = curEntry + } + + /** @post + * hasNext = there is another overriding pair + * overriding = overriding member of the pair, provided hasNext is true + * overridden = overridden member of the pair, provided hasNext is true + */ + @tailrec final def next(): Unit = + if (nextEntry ne null) { + nextEntry = decls.lookupNextEntry(nextEntry) + if (nextEntry ne null) { + overridden = nextEntry.sym + if (overriding.owner != overridden.owner && matches(overriding, overridden)) { + visited += overridden + if (!hasCommonParentAsSubclass(overriding.owner, overridden.owner)) return + } + } else { + curEntry = curEntry.prev + nextOverriding() + } + next() + } + + nextOverriding() + next() + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala new file mode 100644 index 000000000..9571c387b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala @@ -0,0 +1,94 @@ +package dotty.tools.dotc +package transform + +import core._ +import ast.Trees._ +import Contexts._, Types._, Symbols._, Flags._, TypeUtils._, DenotTransformers._, StdNames._ + +/** For all parameter accessors + * + * val x: T = ... + * + * if + * (1) x is forwarded in the supercall to a parameter that's also named `x` + * (2) the superclass parameter accessor for `x` is accessible from the current class + * change the accessor to + * + * def x: T = super.x.asInstanceOf[T] + * + * Do the same also if there are intermediate inaccessible parameter accessor forwarders. + * The aim of this transformation is to avoid redundant parameter accessor fields. + */ +class ParamForwarding(thisTransformer: DenotTransformer) { + import ast.tpd._ + + def forwardParamAccessors(impl: Template)(implicit ctx: Context): Template = { + def fwd(stats: List[Tree])(implicit ctx: Context): List[Tree] = { + val (superArgs, superParamNames) = impl.parents match { + case superCall @ Apply(fn, args) :: _ => + fn.tpe.widen match { + case MethodType(paramNames, _) => (args, paramNames) + case _ => (Nil, Nil) + } + case _ => (Nil, Nil) + } + def inheritedAccessor(sym: Symbol): Symbol = { + /** + * Dmitry: having it have the same name is needed to maintain correctness in presence of subclassing + * if you would use parent param-name `a` to implement param-field `b` + * overriding field `b` will actually override field `a`, that is wrong! + * + * class A(val s: Int); + * class B(val b: Int) extends A(b) + * class C extends A(2) { + * def s = 3 + * assert(this.b == 2) + * } + */ + val candidate = sym.owner.asClass.superClass + .info.decl(sym.name).suchThat(_ is (ParamAccessor, butNot = Mutable)).symbol + if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate + else if (candidate is Method) inheritedAccessor(candidate) + else NoSymbol + } + def forwardParamAccessor(stat: Tree): Tree = { + stat match { + case stat: ValDef => + val sym = stat.symbol.asTerm + if (sym is (ParamAccessor, butNot = Mutable)) { + val idx = superArgs.indexWhere(_.symbol == sym) + if (idx >= 0 && superParamNames(idx) == stat.name) { // supercall to like-named parameter + val alias = inheritedAccessor(sym) + if (alias.exists) { + def forwarder(implicit ctx: Context) = { + sym.copySymDenotation(initFlags = sym.flags | Method | Stable, info = sym.info.ensureMethodic) + .installAfter(thisTransformer) + val superAcc = + Super(This(currentClass), tpnme.EMPTY, inConstrCall = false).select(alias) + DefDef(sym, superAcc.ensureConforms(sym.info.widen)) + } + return forwarder(ctx.withPhase(thisTransformer.next)) + } + } + } + case _ => + } + stat + } + stats map forwardParamAccessor + } + + cpy.Template(impl)(body = fwd(impl.body)(ctx.withPhase(thisTransformer))) + } + + def adaptRef[T <: RefTree](tree: T)(implicit ctx: Context): T = tree.tpe match { + case tpe: TermRefWithSignature + if tpe.sig == Signature.NotAMethod && tpe.symbol.is(Method) => + // It's a param forwarder; adapt the signature + tree.withType( + TermRef.withSig(tpe.prefix, tpe.name, tpe.prefix.memberInfo(tpe.symbol).signature)) + .asInstanceOf[T] + case _ => + tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala new file mode 100644 index 000000000..3e25cf82e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -0,0 +1,1989 @@ +package dotty.tools.dotc +package transform + +import scala.language.postfixOps + +import TreeTransforms._ +import core.Denotations._ +import core.SymDenotations._ +import core.Contexts._ +import core.Symbols._ +import core.Types._ +import core.Constants._ +import core.StdNames._ +import dotty.tools.dotc.ast.{untpd, TreeTypeMap, tpd} +import dotty.tools.dotc.core +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.{TypeApplications, Flags} +import dotty.tools.dotc.typer.Applications +import dotty.tools.dotc.util.Positions +import typer.ErrorReporting._ +import ast.Trees._ +import Applications._ +import TypeApplications._ +import SymUtils._, core.NameOps._ +import core.Mode +import patmat._ + +import dotty.tools.dotc.util.Positions.Position +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags + +import scala.reflect.internal.util.Collections + +/** This transform eliminates patterns. Right now it's a dummy. + * Awaiting the real pattern matcher. + * elimRepeated is required + * TODO: outer tests are not generated yet. + */ +class PatternMatcher extends MiniPhaseTransform with DenotTransformer { + import dotty.tools.dotc.ast.tpd._ + + override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref + + override def runsAfter = Set(classOf[ElimRepeated]) + + override def runsAfterGroupsOf = Set(classOf[TailRec]) // tailrec is not capable of reversing the patmat tranformation made for tree + + override def phaseName = "patternMatcher" + + private var _id = 0 // left for debuging + + override def transformMatch(tree: Match)(implicit ctx: Context, info: TransformerInfo): Tree = { + val translated = new Translator()(ctx).translator.translateMatch(tree) + + // check exhaustivity and unreachability + val engine = new SpaceEngine + if (engine.checkable(tree)) { + engine.checkExhaustivity(tree) + engine.checkRedundancy(tree) + } + + translated.ensureConforms(tree.tpe) + } + + class Translator(implicit ctx: Context) { + + def translator = { + new OptimizingMatchTranslator/*(localTyper)*/ + } + + class OptimizingMatchTranslator extends MatchOptimizer/*(val typer: analyzer.Typer)*/ with MatchTranslator + + trait CodegenCore { + private var ctr = 0 // left for debugging + + // assert(owner ne null); assert(owner ne NoSymbol) + def freshSym(pos: Position, tp: Type = NoType, prefix: String = "x", owner: Symbol = ctx.owner) = { + ctr += 1 + ctx.newSymbol(owner, ctx.freshName(prefix + ctr).toTermName, Flags.Synthetic | Flags.Case, tp, coord = pos) + } + + def newSynthCaseLabel(name: String, tpe: Type, owner: Symbol = ctx.owner) = + ctx.newSymbol(owner, ctx.freshName(name).toTermName, Flags.Label | Flags.Synthetic | Flags.Method, tpe).asTerm + //NoSymbol.newLabel(freshName(name), NoPosition) setFlag treeInfo.SYNTH_CASE_FLAGS + + // codegen relevant to the structure of the translation (how extractors are combined) + trait AbsCodegen { + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree + + // local / context-free + + /* cast b to tp */ + def _asInstanceOf(b: Symbol, tp: Type): Tree + /* a check `checker` == binder */ + def _equals(checker: Tree, binder: Symbol): Tree + /* b.isIsInstanceOf[tp] */ + def _isInstanceOf(b: Symbol, tp: Type): Tree + /* tgt is expected to be a Seq, call tgt.drop(n) */ + def drop(tgt: Tree)(n: Int): Tree + /* tgt is expected to have method apply(int), call tgt.drop(i) */ + def index(tgt: Tree)(i: Int): Tree + /* make tree that accesses the i'th component of the tuple referenced by binder */ + def tupleSel(binder: Symbol)(i: Int): Tree + } + + // structure + trait Casegen extends AbsCodegen { + def one(res: Tree): Tree + + def flatMap(prev: Tree, b: Symbol, next: Tree): Tree + def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree + def flatMapGuard(cond: Tree, next: Tree): Tree + def ifThenElseZero(c: Tree, thenp: Tree): Tree = + If(c, thenp, zero) + protected def zero: Tree + } + + def codegen: AbsCodegen + + abstract class CommonCodegen extends AbsCodegen { + def tupleSel(binder: Symbol)(i: Int): Tree = ref(binder).select(nme.productAccessorName(i)) + def index(tgt: Tree)(i: Int): Tree = { + if (i > 0) tgt.select(defn.Seq_apply).appliedTo(Literal(Constant(i))) + else tgt.select(defn.Seq_head).ensureApplied + } + + // Right now this blindly calls drop on the result of the unapplySeq + // unless it verifiably has no drop method (this is the case in particular + // with Array.) You should not actually have to write a method called drop + // for name-based matching, but this was an expedient route for the basics. + def drop(tgt: Tree)(n: Int): Tree = { + def callDirect = tgt.select(nme.drop).appliedTo(Literal(Constant(n))) + def callRuntime = ref(defn.ScalaRuntime_drop).appliedTo(tgt, Literal(Constant(n))) + + def needsRuntime = !(tgt.tpe derivesFrom defn.SeqClass) /*typeOfMemberNamedDrop(tgt.tpe) == NoType*/ + + if (needsRuntime) callRuntime else callDirect + } + + // NOTE: checker must be the target of the ==, that's the patmat semantics for ya + def _equals(checker: Tree, binder: Symbol): Tree = + tpd.applyOverloaded(checker, nme.EQ, List(ref(binder)), List.empty, defn.BooleanType) + + // the force is needed mainly to deal with the GADT typing hack (we can't detect it otherwise as tp nor pt need contain an abstract type, we're just casting wildly) + def _asInstanceOf(b: Symbol, tp: Type): Tree = ref(b).ensureConforms(tp) // andType here breaks t1048 + def _isInstanceOf(b: Symbol, tp: Type): Tree = ref(b).select(defn.Any_isInstanceOf).appliedToType(tp) + } + } + + object Rebindings { + def apply(from: Symbol, to: Symbol) = new Rebindings(List(from), List(ref(to))) + // requires sameLength(from, to) + def apply(from: List[Symbol], to: List[Tree]) = + if (from nonEmpty) new Rebindings(from, to) else NoRebindings + } + + class Rebindings(val lhs: List[Symbol], val rhs: List[Tree]) { + def >>(other: Rebindings) = { + if (other eq NoRebindings) this + else if (this eq NoRebindings) other + else { + assert((lhs.toSet ++ other.lhs.toSet).size == lhs.length + other.lhs.length, "no double assignments") + new Rebindings(this.lhs ++ other.lhs, this.rhs ++ other.rhs) + } + } + + def emitValDefs: List[ValDef] = { + Collections.map2(lhs, rhs)((symbol, tree) => ValDef(symbol.asTerm, tree.ensureConforms(symbol.info))) + } + } + object NoRebindings extends Rebindings(Nil, Nil) + + trait OptimizedCodegen extends CodegenCore { + override def codegen: AbsCodegen = optimizedCodegen + + // when we know we're targetting Option, do some inlining the optimizer won't do + // for example, `o.flatMap(f)` becomes `if (o == None) None else f(o.get)`, similarly for orElse and guard + // this is a special instance of the advanced inlining optimization that takes a method call on + // an object of a type that only has two concrete subclasses, and inlines both bodies, guarded by an if to distinguish the two cases + object optimizedCodegen extends CommonCodegen { + + /** Inline runOrElse and get rid of Option allocations + * + * runOrElse(scrut: scrutTp)(matcher): resTp = matcher(scrut) getOrElse ${catchAll(`scrut`)} + * the matcher's optional result is encoded as a flag, keepGoing, where keepGoing == true encodes result.isEmpty, + * if keepGoing is false, the result Some(x) of the naive translation is encoded as matchRes == x + */ + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree = { + //val matchRes = ctx.newSymbol(NoSymbol, ctx.freshName("matchRes").toTermName, Flags.Synthetic | Flags.Param | Flags.Label | Flags.Method, restpe /*withoutAnnotations*/) + //NoSymbol.newValueParameter(newTermName("x"), NoPosition, newFlags = SYNTHETIC) setInfo restpe.withoutAnnotations + + + val caseSyms: List[TermSymbol] = cases.scanLeft(ctx.owner.asTerm)((curOwner, nextTree) => newSynthCaseLabel(ctx.freshName("case"), MethodType(Nil, restpe), curOwner)).tail + + // must compute catchAll after caseLabels (side-effects nextCase) + // catchAll.isEmpty iff no synthetic default case needed (the (last) user-defined case is a default) + // if the last user-defined case is a default, it will never jump to the next case; it will go immediately to matchEnd + val catchAllDef = matchFailGen.map { _(scrutSym) } + .getOrElse(Throw(New(defn.MatchErrorType, List(ref(scrutSym))))) + + val matchFail = newSynthCaseLabel(ctx.freshName("matchFail"), MethodType(Nil, restpe)) + val catchAllDefBody = DefDef(matchFail, catchAllDef) + + val nextCases = (caseSyms.tail ::: List(matchFail)).map(ref(_).ensureApplied) + val caseDefs = (cases zip caseSyms zip nextCases).foldRight[Tree](catchAllDefBody) { + // dotty deviation + //case (((mkCase, sym), nextCase), acc) => + (x: (((Casegen => Tree), TermSymbol), Tree), acc: Tree) => x match { + case ((mkCase, sym), nextCase) => + val body = mkCase(new OptimizedCasegen(nextCase)).ensureConforms(restpe) + + DefDef(sym, _ => Block(List(acc), body)) + } + } + + // scrutSym == NoSymbol when generating an alternatives matcher + // val scrutDef = scrutSym.fold(List[Tree]())(ValDef(_, scrut) :: Nil) // for alternatives + + Block(List(caseDefs), ref(caseSyms.head).ensureApplied) + } + + class OptimizedCasegen(nextCase: Tree) extends CommonCodegen with Casegen { + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree = + optimizedCodegen.matcher(scrut, scrutSym, restpe)(cases, matchFailGen) + + // only used to wrap the RHS of a body + // res: T + // returns MatchMonad[T] + def one(res: Tree): Tree = /*ref(matchEnd) appliedTo*/ res // a jump to a case label is special-cased in typedApply + protected def zero: Tree = nextCase + + // prev: MatchMonad[T] + // b: T + // next: MatchMonad[U] + // returns MatchMonad[U] + def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { + + val getTp = extractorMemberType(prev.tpe, nme.get) + val isDefined = extractorMemberType(prev.tpe, nme.isDefined) + + if ((isDefined isRef defn.BooleanClass) && getTp.exists) { + // isDefined and get may be overloaded + val getDenot = prev.tpe.member(nme.get).suchThat(_.info.isParameterless) + val isDefinedDenot = prev.tpe.member(nme.isDefined).suchThat(_.info.isParameterless) + + val tmpSym = freshSym(prev.pos, prev.tpe, "o") + val prevValue = ref(tmpSym).select(getDenot.symbol).ensureApplied + + Block( + List(ValDef(tmpSym, prev)), + // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) + ifThenElseZero( + ref(tmpSym).select(isDefinedDenot.symbol), + Block(List(ValDef(b.asTerm, prevValue)), next) + ) + ) + } else { + assert(defn.isProductSubType(prev.tpe)) + val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null))) + ifThenElseZero( + nullCheck, + Block( + List(ValDef(b.asTerm, prev)), + next //Substitution(b, ref(prevSym))(next) + ) + ) + } + } + + // cond: Boolean + // res: T + // nextBinder: T + // next == MatchMonad[U] + // returns MatchMonad[U] + def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree = { + val rest = Block(List(ValDef(nextBinder.asTerm, res)), next) + ifThenElseZero(cond, rest) + } + + // guardTree: Boolean + // next: MatchMonad[T] + // returns MatchMonad[T] + def flatMapGuard(guardTree: Tree, next: Tree): Tree = + ifThenElseZero(guardTree, next) + + def flatMapCondStored(cond: Tree, condSym: Symbol, res: Tree, nextBinder: Symbol, next: Tree): Tree = + ifThenElseZero(cond, Block( + List(Assign(ref(condSym), Literal(Constant(true))), + Assign(ref(nextBinder), res)), + next + )) + } + } + } + final case class Suppression(exhaustive: Boolean, unreachable: Boolean) + object Suppression { + val NoSuppression = Suppression(false, false) + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // the making of the trees + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + trait TreeMakers extends CodegenCore { + def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) + def analyzeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type, suppression: Suppression): Unit = {} + + def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Symbol => Tree], unchecked: Boolean): Option[Tree] = { + // TODO Deal with guards? + + def isSwitchableType(tpe: Type): Boolean = + (tpe isRef defn.IntClass) || + (tpe isRef defn.ByteClass) || + (tpe isRef defn.ShortClass) || + (tpe isRef defn.CharClass) + + object IntEqualityTestTreeMaker { + def unapply(treeMaker: EqualityTestTreeMaker): Option[Int] = treeMaker match { + case EqualityTestTreeMaker(`scrutSym`, _, Literal(const), _) => + if (const.isIntRange) Some(const.intValue) + else None + case _ => + None + } + } + + def isSwitchCase(treeMakers: List[TreeMaker]): Boolean = treeMakers match { + // case 5 => + case List(IntEqualityTestTreeMaker(_), _: BodyTreeMaker) => + true + + // case 5 | 6 => + case List(AlternativesTreeMaker(`scrutSym`, alts, _), _: BodyTreeMaker) => + alts.forall { + case List(IntEqualityTestTreeMaker(_)) => true + case _ => false + } + + // case _ => + case List(_: BodyTreeMaker) => + true + + /* case x @ pat => + * This includes: + * case x => + * case x @ 5 => + * case x @ (5 | 6) => + */ + case (_: SubstOnlyTreeMaker) :: rest => + isSwitchCase(rest) + + case _ => + false + } + + /* (Nil, body) means that `body` is the default case + * It's a bit hacky but it simplifies manipulations. + */ + def extractSwitchCase(treeMakers: List[TreeMaker]): (List[Int], BodyTreeMaker) = treeMakers match { + // case 5 => + case List(IntEqualityTestTreeMaker(intValue), body: BodyTreeMaker) => + (List(intValue), body) + + // case 5 | 6 => + case List(AlternativesTreeMaker(_, alts, _), body: BodyTreeMaker) => + val intValues = alts.map { + case List(IntEqualityTestTreeMaker(intValue)) => intValue + } + (intValues, body) + + // case _ => + case List(body: BodyTreeMaker) => + (Nil, body) + + // case x @ pat => + case (_: SubstOnlyTreeMaker) :: rest => + /* Rebindings have been propagated, so the eventual body in `rest` + * contains all the necessary information. The substitution can be + * dropped at this point. + */ + extractSwitchCase(rest) + } + + def doOverlap(a: List[Int], b: List[Int]): Boolean = + a.exists(b.contains _) + + def makeSwitch(valuesToCases: List[(List[Int], BodyTreeMaker)]): Tree = { + def genBody(body: BodyTreeMaker): Tree = { + val valDefs = body.rebindings.emitValDefs + if (valDefs.isEmpty) body.body + else Block(valDefs, body.body) + } + + val intScrut = + if (pt isRef defn.IntClass) ref(scrutSym) + else Select(ref(scrutSym), nme.toInt) + + val (normalCases, defaultCaseAndRest) = valuesToCases.span(_._1.nonEmpty) + + val newCases = for { + (values, body) <- normalCases + } yield { + val literals = values.map(v => Literal(Constant(v))) + val pat = + if (literals.size == 1) literals.head + else Alternative(literals) + CaseDef(pat, EmptyTree, genBody(body)) + } + + val catchAllDef = { + if (defaultCaseAndRest.isEmpty) { + matchFailGenOverride.fold[Tree]( + Throw(New(defn.MatchErrorType, List(ref(scrutSym)))))( + _(scrutSym)) + } else { + /* After the default case, assuming the IR even allows anything, + * things are unreachable anyway and can be removed. + */ + genBody(defaultCaseAndRest.head._2) + } + } + val defaultCase = CaseDef(Underscore(defn.IntType), EmptyTree, catchAllDef) + + Match(intScrut, newCases :+ defaultCase) + } + + val dealiased = scrut.tpe.widenDealias + if (isSwitchableType(dealiased) && cases.forall(isSwitchCase)) { + val valuesToCases = cases.map(extractSwitchCase) + val values = valuesToCases.map(_._1) + if (values.tails.exists { tail => tail.nonEmpty && tail.tail.exists(doOverlap(_, tail.head)) }) { + // TODO Deal with overlapping cases (mostly useless without guards) + None + } else { + Some(makeSwitch(valuesToCases)) + } + } else { + if (dealiased hasAnnotation defn.SwitchAnnot) + ctx.warning("failed to emit switch for `@switch` annotated match", scrut.pos) + None + } + } + + // for catch (no need to customize match failure) + def emitTypeSwitch(bindersAndCases: List[(Symbol, List[TreeMaker])], pt: Type): Option[List[CaseDef]] = + None // todo + + abstract class TreeMaker { + def pos: Position + + private[this] var currSub: Rebindings = null + + /** captures the scope and the value of the bindings in patterns + * important *when* the substitution happens (can't accumulate and do at once after the full matcher has been constructed) + */ + def rebindings: Rebindings = + if (currSub eq null) introducedRebindings + else currSub + + protected def introducedRebindings: Rebindings + + private[TreeMakers] def incorporateOuterRebinding(outerSubst: Rebindings): Unit = { + if (currSub ne null) { + ctx.debuglog("BUG: incorporateOuterRebinding called more than once for " + ((this, currSub, outerSubst))) + if (ctx.debug) Thread.dumpStack() + } + else currSub = outerSubst >> rebindings + } + + /** The substitution that specifies the trees that compute the values of the subpattern binders. + * + * Should not be used to perform actual substitution! + * Only used to reason symbolically about the values the subpattern binders are bound to. + * See TreeMakerToCond#updateSubstitution. + * + * Overridden in PreserveSubPatBinders to pretend it replaces the subpattern binders by subpattern refs + * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) + * + * TODO: clean this up, would be nicer to have some higher-level way to compute + * the binders bound by this tree maker and the symbolic values that correspond to them + */ + def subPatternsAsRebindings: Rebindings = rebindings + + // build Tree that chains `next` after the current extractor + def chainBefore(next: Tree)(casegen: Casegen): Tree + } + + sealed trait NoNewBinders extends TreeMaker { + protected val introducedRebindings: Rebindings = NoRebindings + } + + case class TrivialTreeMaker(tree: Tree) extends TreeMaker with NoNewBinders { + def pos = tree.pos + + def chainBefore(next: Tree)(casegen: Casegen): Tree = tree + } + + case class BodyTreeMaker(body: Tree, matchPt: Type) extends TreeMaker with NoNewBinders { + def pos = body.pos + + def chainBefore(next: Tree)(casegen: Casegen): Tree = // assert(next eq EmptyTree) + /*atPos(body.pos)*/(casegen.one(body)) // since SubstOnly treemakers are dropped, need to do it here + override def toString = "B" + ((body, matchPt)) + } + + /** + * In scalac for such block + * x match { + * case d => <body> + * } + * + * d inside <body> was to be substitued by x. + * + * In dotty, SubstOnlyTreeMakers instead generate normal ValDef, + * and does not create a new substitution. + * + * This was done for several reasons: + * 1) it is a lot easyer to Y-check, + * as d type could be used in <body>. + * 2) it would simplify debugging of the generated code as + * this works also for nested patterns, and previously they used unreadable names + * 3) It showed better(~30%), performance, + * Rebuilding tree and propagating types was taking substantial time. + */ + case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker { + val pos = Positions.NoPosition + + val introducedRebindings = Rebindings(prevBinder, nextBinder) + def chainBefore(next: Tree)(casegen: Casegen): Tree = next + //override def toString = "S" + localSubstitution + } + + sealed abstract class FunTreeMaker extends TreeMaker { + val nextBinder: Symbol + def pos = nextBinder.pos + } + + sealed abstract class CondTreeMaker extends FunTreeMaker { + val prevBinder: Symbol + val nextBinderTp: Type + val cond: Tree + val res: Tree + + val nextBinder: Symbol + lazy val introducedRebindings = /* + if (nextBinder ne prevBinder) Rebindings(prevBinder, nextBinder) + else */ NoRebindings + + def chainBefore(next: Tree)(casegen: Casegen): Tree = + if (prevBinder ne nextBinder) // happens when typeTest is known to succeed + /*atPos(pos)(*/casegen.flatMapCond(cond, res, nextBinder, next)//) + else casegen.flatMapGuard(cond, next) + } + + // unless we're optimizing, emit local variable bindings for all subpatterns of extractor/case class patterns + protected val debugInfoEmitVars = true //!settings.optimise.value + + /** + * Tree maker that captures sub pattern values during pattern match. + */ + sealed trait PreserveSubPatBinders extends TreeMaker { + val subPatBinders: List[Symbol] // captured values + val subPatRefs: List[Tree] // trees that will replace references to subPatBinders + val ignoredSubPatBinders: Set[Symbol] // ignored as they aren't used in body of pattern + + // unless `debugInfoEmitVars`, this set should contain the bare minimum for correctness + // mutable case class fields need to be stored regardless (SI-5158, SI-6070) -- see override in ProductExtractorTreeMaker + // sub patterns bound to wildcard (_) are never stored as they can't be referenced + // dirty debuggers will have to get dirty to see the wildcards + lazy val storedBinders: Set[Symbol] = + (if (debugInfoEmitVars) subPatBinders.toSet else Set.empty) ++ extraStoredBinders -- ignoredSubPatBinders + + // e.g., mutable fields of a case class in ProductExtractorTreeMaker + def extraStoredBinders: Set[Symbol] + + def emitVars = storedBinders.nonEmpty + + lazy val storedSubsted = (subPatBinders, subPatRefs).zipped.partition{ case (sym, _) => storedBinders(sym) } + + def stored = storedSubsted._1 + + def substed = storedSubsted._2 + + // dd: this didn't yet trigger error. But I believe it would. if this causes double denition of symbol error this can be replaced with NoRebindings + protected lazy val introducedRebindings: Rebindings = if (!emitVars) Rebindings(subPatBinders, subPatRefs) + else { + val (subPatBindersSubstituted, subPatRefsSubstituted) = substed.unzip + Rebindings(subPatBindersSubstituted.toList, subPatRefsSubstituted.toList) + } + + /** The substitution that specifies the trees that compute the values of the subpattern binders. + * + * We pretend to replace the subpattern binders by subpattern refs + * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) + */ + override def subPatternsAsRebindings = + Rebindings(subPatBinders, subPatRefs) >> super.subPatternsAsRebindings + + def bindSubPats(in: Tree): Tree = + if (!emitVars) in + else { + // binders in `subPatBindersStored` that are referenced by tree `in` + val usedBinders = new collection.mutable.HashSet[Symbol]() + // all potentially stored subpat binders + val potentiallyStoredBinders = stored.unzip._1.toSet + // compute intersection of all symbols in the tree `in` and all potentially stored subpat binders + new DeepFolder[Unit]((x: Unit, t: Tree) => + if (potentiallyStoredBinders(t.symbol)) usedBinders += t.symbol).apply((), in) + + if (usedBinders.isEmpty) in + else { + // only store binders actually used + val (subPatBindersStored, subPatRefsStored) = stored.filter{case (b, _) => usedBinders(b)}.unzip + + Block(Collections.map2(subPatBindersStored.toList, subPatRefsStored.toList)((bind, ref) => { + // required in case original pattern had a more precise type + // eg case s@"foo" => would be otherwise translated to s with type String instead of String("foo") + def refTpeWiden = ref.tpe.widen + def bindInfoWiden = bind.info.widen + def loc = bind.showFullName + if (!(ref.tpe <:< bind.info.widen)) { + ctx.debuglog(s"here ${bind.showFullName} expected: ${bindInfoWiden.show} got: ${refTpeWiden.show}") + } + val refCasted = ref.ensureConforms(bind.info) + ValDef(bind.asTerm, refCasted) + }), in) + } + } + } + + /** + * Make a TreeMaker that will result in an extractor call specified by `extractor` + * the next TreeMaker (here, we don't know which it'll be) is chained after this one by flatMap'ing + * a function with binder `nextBinder` over our extractor's result + * the function's body is determined by the next TreeMaker + * (furthermore, the interpretation of `flatMap` depends on the codegen instance we're using). + * + * The values for the subpatterns, as computed by the extractor call in `extractor`, + * are stored in local variables that re-use the symbols in `subPatBinders`. + * This makes extractor patterns more debuggable (SI-5739). + */ + case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol)( + val subPatBinders: List[Symbol], + val subPatRefs: List[Tree], + extractorReturnsBoolean: Boolean, + val checkedLength: Option[Int], + val prevBinder: Symbol, + val ignoredSubPatBinders: Set[Symbol] + ) extends FunTreeMaker with PreserveSubPatBinders { + + def extraStoredBinders: Set[Symbol] = Set() + + ctx.debuglog(s""" + |ExtractorTreeMaker($extractor, $extraCond, $nextBinder) { + | $subPatBinders + | $subPatRefs + | $extractorReturnsBoolean + | $checkedLength + | $prevBinder + | $ignoredSubPatBinders + |}""".stripMargin) + + def chainBefore(next: Tree)(casegen: Casegen): Tree = { + val condAndNext = extraCond match { + case Some(cond: Tree) => + casegen.ifThenElseZero(cond, bindSubPats(next)) + case _ => + bindSubPats(next) + } + + if (extractorReturnsBoolean) casegen.flatMapCond(extractor, unitLiteral, nextBinder, condAndNext) + else casegen.flatMap(extractor, nextBinder, condAndNext) // getType? + } + + override def toString = "X" + ((extractor, nextBinder.name)) + } + + /** + * An optimized version of ExtractorTreeMaker for Products. + * For now, this is hard-coded to case classes, and we simply extract the case class fields. + * + * The values for the subpatterns, as specified by the case class fields at the time of extraction, + * are stored in local variables that re-use the symbols in `subPatBinders`. + * This makes extractor patterns more debuggable (SI-5739) as well as + * avoiding mutation after the pattern has been matched (SI-5158, SI-6070) + * + * TODO: make this user-definable as follows + * When a companion object defines a method `def unapply_1(x: T): U_1`, but no `def unapply` or `def unapplySeq`, + * the extractor is considered to match any non-null value of type T + * the pattern is expected to have as many sub-patterns as there are `def unapply_I(x: T): U_I` methods, + * and the type of the I'th sub-pattern is `U_I`. + * The same exception for Seq patterns applies: if the last extractor is of type `Seq[U_N]`, + * the pattern must have at least N arguments (exactly N if the last argument is annotated with `: _*`). + * The arguments starting at N (and beyond) are taken from the sequence returned by apply_N, + * and it is checked that the sequence has enough elements to provide values for all expected sub-patterns. + * + * For a case class C, the implementation is assumed to be `def unapply_I(x: C) = x._I`, + * and the extractor call is inlined under that assumption. + */ + case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree])( + val subPatBinders: List[Symbol], + val subPatRefs: List[Tree], + val mutableBinders: List[Symbol], + binderKnownNonNull: Boolean, + val ignoredSubPatBinders: Set[Symbol] + ) extends FunTreeMaker with PreserveSubPatBinders { + + val nextBinder = prevBinder // just passing through + + // mutable binders must be stored to avoid unsoundness or seeing mutation of fields after matching (SI-5158, SI-6070) + def extraStoredBinders: Set[Symbol] = mutableBinders.toSet + + def chainBefore(next: Tree)(casegen: Casegen): Tree = { + val nullCheck: Tree = ref(prevBinder).select(defn.Object_ne).appliedTo(Literal(Constant(null))) + + val cond: Option[Tree] = + if (binderKnownNonNull) extraCond + else extraCond.map(nullCheck.select(defn.Boolean_&&).appliedTo).orElse(Some(nullCheck)) + + cond match { + case Some(cond: Tree) => + casegen.ifThenElseZero(cond, bindSubPats(next)) + case _ => + bindSubPats(next) + } + } + + override def toString = "P" + ((prevBinder.name, extraCond getOrElse "", introducedRebindings)) + } + + object IrrefutableExtractorTreeMaker { + // will an extractor with unapply method of methodtype `tp` always succeed? + // note: this assumes the other side-conditions implied by the extractor are met + // (argument of the right type, length check succeeds for unapplySeq,...) + def irrefutableExtractorType(tp: Type): Boolean = tp.resultType.dealias match { + // case TypeRef(_, SomeClass, _) => true todo + // probably not useful since this type won't be inferred nor can it be written down (yet) + // case ConstantTrue => true todo + case _ => false + } + + def unapply(xtm: ExtractorTreeMaker): Option[(Tree, Symbol)] = xtm match { + case ExtractorTreeMaker(extractor, None, nextBinder) if irrefutableExtractorType(extractor.tpe) => + Some((extractor, nextBinder)) + case _ => + None + } + } + + object TypeTestTreeMaker { + // factored out so that we can consistently generate other representations of the tree that implements the test + // (e.g. propositions for exhaustivity and friends, boolean for isPureTypeTest) + trait TypeTestCondStrategy { + type Result + + def outerTest(testedBinder: Symbol, expectedTp: Type): Result + // TODO: can probably always widen + def typeTest(testedBinder: Symbol, expectedTp: Type): Result + def nonNullTest(testedBinder: Symbol): Result + def equalsTest(pat: Tree, testedBinder: Symbol): Result + def eqTest(pat: Tree, testedBinder: Symbol): Result + def and(a: Result, b: Result): Result + def tru: Result + } + + object treeCondStrategy extends TypeTestCondStrategy { + type Result = Tree + + def and(a: Result, b: Result): Result = a.select(defn.Boolean_&&).appliedTo(b) + def tru = Literal(Constant(true)) + def typeTest(testedBinder: Symbol, expectedTp: Type) = codegen._isInstanceOf(testedBinder, expectedTp) + def nonNullTest(testedBinder: Symbol) = ref(testedBinder).select(defn.Object_ne).appliedTo(Literal(Constant(null))) + def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder) + def eqTest(pat: Tree, testedBinder: Symbol) = ref(testedBinder).select(defn.Object_eq).appliedTo(pat) + + def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = { + val expectedOuter = expectedTp.normalizedPrefix match { + //case NoType => Literal(Constant(true)) // fallback for SI-6183 todo? + case pre: SingletonType => singleton(pre) + } + + // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix` + // if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor? + // val outer = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedTp.prefix + + val expectedClass = expectedTp.dealias.classSymbol.asClass + val test = codegen._asInstanceOf(testedBinder, expectedTp) + // TODO: Use nme.OUTER_SELECT, like the Inliner does? + val outerAccessorTested = ctx.atPhase(ctx.explicitOuterPhase.next) { implicit ctx => + ExplicitOuter.ensureOuterAccessors(expectedClass) + test.select(ExplicitOuter.outerAccessor(expectedClass)).select(defn.Object_eq).appliedTo(expectedOuter) + } + outerAccessorTested + } + } + + /*object pureTypeTestChecker extends TypeTestCondStrategy { + type Result = Boolean + + def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true + + def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false + def nonNullTest(testedBinder: Symbol): Result = false + def equalsTest(pat: Tree, testedBinder: Symbol): Result = false + def eqTest(pat: Tree, testedBinder: Symbol): Result = false + def and(a: Result, b: Result): Result = false // we don't and type tests, so the conjunction must include at least one false + def tru = true + }*/ + + def nonNullImpliedByTestChecker(binder: Symbol) = new TypeTestCondStrategy { + type Result = Boolean + + def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder + def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false + def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder + def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null + def eqTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null + def and(a: Result, b: Result): Result = a || b + def tru = false + } + } + + /** implements the run-time aspects of (§8.2) (typedPattern has already done the necessary type transformations) + * + * Type patterns consist of types, type variables, and wildcards. A type pattern T is of one of the following forms: + - A reference to a class C, p.C, or T#C. + This type pattern matches any non-null instance of the given class. + Note that the prefix of the class, if it is given, is relevant for determining class instances. + For instance, the pattern p.C matches only instances of classes C which were created with the path p as prefix. + The bottom types scala.Nothing and scala.Null cannot be used as type patterns, because they would match nothing in any case. + + - A singleton type p.type. + This type pattern matches only the value denoted by the path p + (that is, a pattern match involved a comparison of the matched value with p using method eq in class AnyRef). // TODO: the actual pattern matcher uses ==, so that's what I'm using for now + // https://issues.scala-lang.org/browse/SI-4577 "pattern matcher, still disappointing us at equality time" + + - A compound type pattern T1 with ... with Tn where each Ti is a type pat- tern. + This type pattern matches all values that are matched by each of the type patterns Ti. + + - A parameterized type pattern T[a1,...,an], where the ai are type variable patterns or wildcards _. + This type pattern matches all values which match T for some arbitrary instantiation of the type variables and wildcards. + The bounds or alias type of these type variable are determined as described in (§8.3). + + - A parameterized type pattern scala.Array[T1], where T1 is a type pattern. // TODO + This type pattern matches any non-null instance of type scala.Array[U1], where U1 is a type matched by T1. + **/ + case class TypeTestTreeMaker(afterTest: Symbol, testedBinder: Symbol, expectedTp: Type, nextBinderTp: Type)(override val pos: Position, extractorArgTypeTest: Boolean = false) extends CondTreeMaker { + import TypeTestTreeMaker._ + + ctx.debuglog("TTTM" + ((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp))) + + val prevBinder = testedBinder + + val nextBinder = afterTest.asTerm + + def needsOuterTest(patType: Type, selType: Type, currentOwner: Symbol): Boolean = { + // See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest` + // generates an outer test based on `patType.prefix` with automatically dealises. + patType.dealias match { + case tref @ TypeRef(pre, name) => + (pre ne NoPrefix) && tref.symbol.isClass && + ExplicitOuter.needsOuterIfReferenced(tref.symbol.asClass) + case _ => + false + } + } + + override lazy val introducedRebindings = NoRebindings + + def outerTestNeeded = { + val np = expectedTp.normalizedPrefix + val ts = np.termSymbol + (ts ne NoSymbol) && needsOuterTest(expectedTp, testedBinder.info, ctx.owner) + } + + // the logic to generate the run-time test that follows from the fact that + // a `prevBinder` is expected to have type `expectedTp` + // the actual tree-generation logic is factored out, since the analyses generate Cond(ition)s rather than Trees + // TODO: `null match { x : T }` will yield a check that (indirectly) tests whether `null ne null` + // don't bother (so that we don't end up with the warning "comparing values of types Null and Null using `ne' will always yield false") + def renderCondition(cs: TypeTestCondStrategy): cs.Result = { + import cs._ + + // propagate expected type + def expTp(t: Tree): t.type = t // setType expectedTp todo: + + def testedWide = testedBinder.info.widen + def expectedWide = expectedTp.widen + def isAnyRef = testedWide <:< defn.AnyRefType + def isAsExpected = testedWide <:< expectedTp + def isExpectedPrimitiveType = isAsExpected && expectedTp.classSymbol.isPrimitiveValueClass + def isExpectedReferenceType = isAsExpected && (expectedTp <:< defn.AnyRefType) + def mkNullTest = nonNullTest(testedBinder) + def mkOuterTest = outerTest(testedBinder, expectedTp) + def mkTypeTest = typeTest(testedBinder, expectedWide) + + def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder) + def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder) + def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res + + // If we conform to expected primitive type: + // it cannot be null and cannot have an outer pointer. No further checking. + // If we conform to expected reference type: + // have to test outer and non-null + // If we do not conform to expected type: + // have to test type and outer (non-null is implied by successful type test) + def mkDefault = ( + if (isExpectedPrimitiveType) tru + else addOuterTest( + if (isExpectedReferenceType) mkNullTest + else mkTypeTest + ) + ) + + // true when called to type-test the argument to an extractor + // don't do any fancy equality checking, just test the type + // TODO: verify that we don't need to special-case Array + // I think it's okay: + // - the isInstanceOf test includes a test for the element type + // - Scala's arrays are invariant (so we don't drop type tests unsoundly) + if (extractorArgTypeTest) mkDefault + else expectedTp match { + case ThisType(tref) if tref.symbol.flags is Flags.Module => + and(mkEqualsTest(ref(tref.symbol.companionModule)), mkTypeTest) // must use == to support e.g. List() == Nil + case ConstantType(Constant(null)) if isAnyRef => mkEqTest(expTp(Literal(Constant(null)))) + case ConstantType(const) => mkEqualsTest(expTp(Literal(const))) + case t: SingletonType => mkEqTest(singleton(expectedTp)) // SI-4577, SI-4897 + //case ThisType(sym) => mkEqTest(expTp(This(sym))) + case _ => mkDefault + } + } + + val cond = renderCondition(treeCondStrategy) + val res = codegen._asInstanceOf(testedBinder, nextBinderTp) + + // is this purely a type test, e.g. no outer check, no equality tests (used in switch emission) + //def isPureTypeTest = renderCondition(pureTypeTestChecker) + + def impliesBinderNonNull(binder: Symbol): Boolean = + // @odersky: scalac is able to infer in this method that nonNullImpliedByTestChecker.Result, + // dotty instead infers type projection TreeMakers.this.TypeTestTreeMaker.TypeTestCondStrategy#Result + // which in turn doesn't typecheck in this method. Can you please explain why? + // dotty deviation + renderCondition(nonNullImpliedByTestChecker(binder)).asInstanceOf[Boolean] + + override def toString = "TT" + ((expectedTp, testedBinder.name, nextBinderTp)) + } + + // need to substitute to deal with existential types -- TODO: deal with existentials better, don't substitute (see RichClass during quick.comp) + case class EqualityTestTreeMaker(prevBinder: Symbol, subpatBinder: Symbol, patTree: Tree, override val pos: Position) extends CondTreeMaker { + val nextBinderTp = patTree.tpe & prevBinder.info + val nextBinder = if (prevBinder eq subpatBinder) freshSym(pos, nextBinderTp) else subpatBinder + + // NOTE: generate `patTree == patBinder`, since the extractor must be in control of the equals method (also, patBinder may be null) + // equals need not be well-behaved, so don't intersect with pattern's (stabilized) type (unlike MaybeBoundTyped's accumType, where it's required) + val cond = codegen._equals(patTree, prevBinder) + val res = ref(prevBinder).ensureConforms(nextBinderTp) + override def toString = "ET" + ((prevBinder.name, patTree)) + } + + case class AlternativesTreeMaker(prevBinder: Symbol, var altss: List[List[TreeMaker]], pos: Position) extends TreeMaker with NoNewBinders { + // don't substitute prevBinder to nextBinder, a set of alternatives does not need to introduce a new binder, simply reuse the previous one + + override private[TreeMakers] def incorporateOuterRebinding(outerSubst: Rebindings): Unit = { + super.incorporateOuterRebinding(outerSubst) + altss = altss map (alts => propagateRebindings(alts, rebindings)) + } + + def chainBefore(next: Tree)(codegenAlt: Casegen): Tree = { + /*atPos(pos)*/{ + // one alternative may still generate multiple trees (e.g., an extractor call + equality test) + // (for now,) alternatives may not bind variables (except wildcards), so we don't care about the final substitution built internally by makeTreeMakers + val combinedAlts = altss map (altTreeMakers => + ((casegen: Casegen) => combineExtractors(altTreeMakers :+ TrivialTreeMaker(casegen.one(Literal(Constant(true)))))(casegen)) + ) + + val findAltMatcher = codegenAlt.matcher(EmptyTree, NoSymbol, defn.BooleanType)(combinedAlts, Some((x: Symbol) => Literal(Constant(false)))) + codegenAlt.ifThenElseZero(findAltMatcher, next) + } + } + } + + case class GuardTreeMaker(guardTree: Tree) extends TreeMaker with NoNewBinders { + val pos = guardTree.pos + + def chainBefore(next: Tree)(casegen: Casegen): Tree = casegen.flatMapGuard(guardTree, next) + override def toString = "G(" + guardTree + ")" + } + + // combineExtractors changes the current substitution's of the tree makers in `treeMakers` + // requires propagateSubstitution(treeMakers) has been called + def combineExtractors(treeMakers: List[TreeMaker])(casegen: Casegen): Tree = { + val (testsMakers, guardAndBodyMakers) = treeMakers.span(t => !(t.isInstanceOf[NoNewBinders])) + val body = guardAndBodyMakers.foldRight(EmptyTree: Tree)((a, b) => a.chainBefore(b)(casegen)) + val rebindings = guardAndBodyMakers.last.rebindings.emitValDefs + testsMakers.foldRight(Block(rebindings, body): Tree)((a, b) => a.chainBefore(b)(casegen)) + } + // a foldLeft to accumulate the localSubstitution left-to-right + // unlike in scalace it does not drop SubstOnly tree makers, + // as there could be types having them as prefix + def propagateRebindings(treeMakers: List[TreeMaker], initial: Rebindings): List[TreeMaker] = { + var accumSubst: Rebindings = initial + treeMakers foreach { maker => + maker incorporateOuterRebinding accumSubst + accumSubst = maker.rebindings + } + treeMakers + } + + // calls propagateSubstitution on the treemakers + def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Symbol => Tree]): Tree = { + // unlike in scalac SubstOnlyTreeMakers are maintained. + val casesRebindingPropagated = casesRaw map (propagateRebindings(_, NoRebindings)) + + def matchFailGen = matchFailGenOverride orElse Some((arg: Symbol) => Throw(New(defn.MatchErrorType, List(ref(arg))))) + + ctx.debuglog("combining cases: " + (casesRebindingPropagated.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) + + val (suppression, requireSwitch): (Suppression, Boolean) = + /*if (settings.XnoPatmatAnalysis)*/ (Suppression.NoSuppression, false) + /*else scrut match { + case Typed(tree, tpt) => + val suppressExhaustive = tpt.tpe hasAnnotation UncheckedClass + val supressUnreachable = tree match { + case Ident(name) if name startsWith nme.CHECK_IF_REFUTABLE_STRING => true // SI-7183 don't warn for withFilter's that turn out to be irrefutable. + case _ => false + } + val suppression = Suppression(suppressExhaustive, supressUnreachable) + // matches with two or fewer cases need not apply for switchiness (if-then-else will do) + val requireSwitch = treeInfo.isSwitchAnnotation(tpt.tpe) && casesNoSubstOnly.lengthCompare(2) > 0 + (suppression, requireSwitch) + case _ => + (Suppression.NoSuppression, false) + }*/ + + emitSwitch(scrut, scrutSym, casesRebindingPropagated, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ + if (requireSwitch) ctx.warning("could not emit switch for @switch annotated match", scrut.pos) + + if (casesRebindingPropagated nonEmpty) { + // before optimizing, check casesNoSubstOnly for presence of a default case, + // since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one + // exhaustivity and reachability must be checked before optimization as well + // TODO: improve notion of trivial/irrefutable -- a trivial type test before the body still makes for a default case + // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) + // irrefutability checking should use the approximation framework also used for CSE, unreachability and exhaustivity checking + val synthCatchAll: Option[Symbol => Tree] = + if (casesRebindingPropagated.nonEmpty && { + val nonTrivLast = casesRebindingPropagated.last + nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] + }) None + else matchFailGen + + analyzeCases(scrutSym, casesRebindingPropagated, pt, suppression) + + val (cases, toHoist) = optimizeCases(scrutSym, casesRebindingPropagated, pt) + + val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases.map(x => combineExtractors(x) _), synthCatchAll) + + if (toHoist isEmpty) matchRes else Block(toHoist, matchRes) + } else { + codegen.matcher(scrut, scrutSym, pt)(Nil, matchFailGen) + } + } + } + } + + trait MatchOptimizer extends OptimizedCodegen with TreeMakers + /*with SwitchEmission // todo: toBe ported + with CommonSubconditionElimination*/ { + override def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) = { + // TODO: do CSE on result of doDCE(prevBinder, cases, pt) + val optCases = cases// todo: doCSE(prevBinder, cases, pt) + val toHoist = Nil/*( + for (treeMakers <- optCases) + yield treeMakers.collect{case tm: ReusedCondTreeMaker => tm.treesToHoist} + ).flatten.flatten.toList*/ + (optCases, toHoist) + } + } + + trait MatchTranslator extends TreeMakers with ScalacPatternExpanders { + + def isBackquoted(x: Ident) = x.isInstanceOf[BackquotedIdent] + + def isVarPattern(pat: Tree): Boolean = pat match { + case x: BackquotedIdent => false + case x: Ident => x.name.isVariableName + case _ => false + } + + /** A conservative approximation of which patterns do not discern anything. + * They are discarded during the translation. + */ + object WildcardPattern { + def unapply(pat: Tree): Boolean = pat match { + case Typed(_, arg) if arg.tpe.isRepeatedParam => true + case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! + case t if (tpd.isWildcardArg(t)) => true + case x: Ident => isVarPattern(x) + case Alternative(ps) => ps forall unapply + case EmptyTree => true + case _ => false + } + } + + object PatternBoundToUnderscore { + def unapply(pat: Tree): Boolean = pat match { + case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol! + case Ident(nme.WILDCARD) => true + case Alternative(ps) => ps forall unapply + case Typed(PatternBoundToUnderscore(), _) => false // true // Dmitry: change in dotty. Type test will be performed and the field must be stored + case _ => false + } + } + + object SymbolBound { + def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { + case Bind(_, expr) if tree.symbol.exists => Some(tree.symbol -> expr) + case _ => None + } + } + + def newBoundTree(tree: Tree, pt: Type): BoundTree = tree match { + case SymbolBound(sym, Typed(subpat, tpe)) => BoundTree(freshSym(tree.pos, pt, prefix = "pi"), tree) + case SymbolBound(sym, expr) => BoundTree(sym, expr) + case _ => BoundTree(freshSym(tree.pos, pt, prefix = "p"), tree) + } + + final case class BoundTree(binder: Symbol, tree: Tree) { + private lazy val extractor = ExtractorCall(tree, binder) + + def pos = tree.pos + def tpe = binder.info.widenDealias + def pt = unbound match { + // case Star(tpt) => this glbWith seqType(tpt.tpe) dd todo: + case TypeBound(tpe) => tpe + case tree => tree.tpe + } + + def glbWith(other: Type) = ctx.typeComparer.glb(tpe :: other :: Nil)// .normalize + + object SymbolAndTypeBound { + def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { + case SymbolBound(sym, Typed(_: UnApply, _)) => None // see comment in #189 + case SymbolBound(sym, TypeBound(tpe)) => Some(sym -> tpe) + case TypeBound(tpe) => Some(binder -> tpe) + case _ => None + } + } + + object SymbolAndValueBound { + def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { + case SymbolBound(sym, ConstantPattern(const)) => Some(sym -> const) + case _ => None + } + } + + object TypeBound { + def unapply(tree: Tree): Option[Type] = tree match { + case Typed(_, arg) if !arg.tpe.isRepeatedParam => Some(tree.typeOpt) + case _ => None + } + } + + object ConstantPattern { + def unapply(tree: Tree): Option[Tree] = tree match { + case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => Some(tree) + case _ => None + } + } + + private def rebindTo(pattern: Tree) = BoundTree(binder, pattern) + private def step(treeMakers: TreeMaker*)(subpatterns: BoundTree*): TranslationStep = TranslationStep(treeMakers.toList, subpatterns.toList) + + private def bindingStep(sub: Symbol, subpattern: Tree) = step(SubstOnlyTreeMaker(sub, binder))(rebindTo(subpattern)) + private def equalityTestStep(testedSymbol: Symbol, constantSymbol: Symbol, constant: Tree) + = step(EqualityTestTreeMaker(testedSymbol, constantSymbol, constant, pos))() + private def typeTestStep(sub: Symbol, subPt: Type) = step(TypeTestTreeMaker(sub, binder, subPt, sub.termRef)(pos))() + private def alternativesStep(alts: List[Tree]) = step(AlternativesTreeMaker(binder, translatedAlts(alts), alts.head.pos))() + private def translatedAlts(alts: List[Tree]) = alts map (alt => rebindTo(alt).translate()) + private def noStep() = step()() + + private def unsupportedPatternMsg = + i"unsupported pattern: ${tree.show} / $this (this is a scalac bug.)" + + // example check: List[Int] <:< ::[Int] + private def extractorStep(): TranslationStep = { + def paramType = extractor.aligner.wholeType + import extractor.treeMaker + // chain a type-testing extractor before the actual extractor call + // it tests the type, checks the outer pointer and casts to the expected type + // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] + // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) + lazy val typeTest = TypeTestTreeMaker(freshSym(pos, paramType), binder, paramType, paramType)(pos, extractorArgTypeTest = true) + // check whether typetest implies binder is not null, + // even though the eventual null check will be on typeTest.nextBinder + // it'll be equal to binder casted to paramType anyway (and the type test is on binder) + def extraction: TreeMaker = treeMaker(typeTest.nextBinder, typeTest.impliesBinderNonNull(binder), pos, paramType) + + // paramType = the type expected by the unapply + // TODO: paramType may contain unbound type params (run/t2800, run/t3530) + val makers = ( + // Statically conforms to paramType + if (tpe <:< paramType) treeMaker(binder, false, pos, tpe) :: Nil + else typeTest :: extraction :: Nil + ) + step(makers: _*)(extractor.subBoundTrees: _*) + } + + // Summary of translation cases. I moved the excerpts from the specification further below so all + // the logic can be seen at once. + // + // [1] skip wildcard trees -- no point in checking them + // [2] extractor and constructor patterns + // [3] replace subpatBinder by patBinder, as if the Bind was not there. + // It must be patBinder, as subpatBinder has the wrong info: even if the bind assumes a better type, + // this is not guaranteed until we cast + // [4] typed patterns - a typed pattern never has any subtrees + // must treat Typed and Bind together -- we need to know the patBinder of the Bind pattern to get at the actual type + // [5] literal and stable id patterns + // [6] pattern alternatives + // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later + // don't fail here though (or should we?) + def nextStep(): TranslationStep = tree match { + case _: UnApply | _: Apply | Typed(_: UnApply | _: Apply, _) => extractorStep() + case SymbolAndTypeBound(sym, tpe) => typeTestStep(sym, tpe) + case TypeBound(tpe) => typeTestStep(binder, tpe) + case SymbolBound(sym, expr) => bindingStep(sym, expr) + case WildcardPattern() => noStep() + case ConstantPattern(const) => equalityTestStep(binder, binder, const) + case Alternative(alts) => alternativesStep(alts) + case _ => ctx.error(unsupportedPatternMsg, pos) ; noStep() + } + def translate(): List[TreeMaker] = nextStep() merge (_.translate()) + + private def concreteType = tpe.bounds.hi + private def unbound = unbind(tree) + private def tpe_s = if (pt <:< concreteType) "" + pt else s"$pt (binder: $tpe)" + private def at_s = unbound match { + case WildcardPattern() => "" + case pat => s" @ $pat" + } + override def toString = s"${binder.name}: $tpe_s$at_s" + } + + // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns + final case class TranslationStep(makers: List[TreeMaker], subpatterns: List[BoundTree]) { + def merge(f: BoundTree => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f) + override def toString = if (subpatterns.isEmpty) "" else subpatterns.mkString("(", ", ", ")") + } + + def isSyntheticDefaultCase(cdef: CaseDef) = cdef match { + case CaseDef(Bind(nme.DEFAULT_CASE, _), EmptyTree, _) => true + case _ => false + } + + /** Implement a pattern match by turning its cases (including the implicit failure case) + * into the corresponding (monadic) extractors, and combining them with the `orElse` combinator. + * + * For `scrutinee match { case1 ... caseN }`, the resulting tree has the shape + * `runOrElse(scrutinee)(x => translateCase1(x).orElse(translateCase2(x)).....orElse(zero))` + * + * NOTE: the resulting tree is not type checked, nor are nested pattern matches transformed + * thus, you must typecheck the result (and that will in turn translate nested matches) + * this could probably be optimized... (but note that the matchStrategy must be solved for each nested patternmatch) + */ + def translateMatch(match_ : Match): Tree = { + val Match(sel, cases) = match_ + + val selectorTp = sel.tpe.widen.deAnonymize/*withoutAnnotations*/ + + val selectorSym = freshSym(sel.pos, selectorTp, "selector") + + val (nonSyntheticCases, defaultOverride) = cases match { + case init :+ last if isSyntheticDefaultCase(last) => (init, Some(((scrut: Symbol) => last.body))) + case _ => (cases, None) + } + + + // checkMatchVariablePatterns(nonSyntheticCases) // only used for warnings + + // we don't transform after uncurry + // (that would require more sophistication when generating trees, + // and the only place that emits Matches after typers is for exception handling anyway) + /*if (phase.id >= currentRun.uncurryPhase.id) + devWarning(s"running translateMatch past uncurry (at $phase) on $selector match $cases")*/ + + ctx.debuglog("translating " + cases.mkString("{", "\n", "}")) + + //val start = if (Statistics.canEnable) Statistics.startTimer(patmatNanos) else null + + // when one of the internal cps-type-state annotations is present, strip all CPS annotations + ///val origPt = removeCPSFromPt(match_.tpe) + // relevant test cases: pos/existentials-harmful.scala, pos/gadt-gilles.scala, pos/t2683.scala, pos/virtpatmat_exist4.scala + // pt is the skolemized version + val pt = match_.tpe.widen //repeatedToSeq(origPt) + + // val packedPt = repeatedToSeq(typer.packedType(match_, context.owner)) + selectorSym.setFlag(Flags.SyntheticCase) + + // pt = Any* occurs when compiling test/files/pos/annotDepMethType.scala with -Xexperimental + val combined = combineCases(sel, selectorSym, nonSyntheticCases map translateCase(selectorSym, pt), pt, ctx.owner, defaultOverride) + + // if (Statistics.canEnable) Statistics.stopTimer(patmatNanos, start) + Block(List(ValDef(selectorSym, sel)), combined) + } + + /** The translation of `pat if guard => body` has two aspects: + * 1) the substitution due to the variables bound by patterns + * 2) the combination of the extractor calls using `flatMap`. + * + * 2) is easy -- it looks like: `translatePattern_1.flatMap(translatePattern_2....flatMap(translatePattern_N.flatMap(translateGuard.flatMap((x_i) => success(Xbody(x_i)))))...)` + * this must be right-leaning tree, as can be seen intuitively by considering the scope of bound variables: + * variables bound by pat_1 must be visible from the function inside the left-most flatMap right up to Xbody all the way on the right + * 1) is tricky because translatePattern_i determines the shape of translatePattern_i + 1: + * zoom in on `translatePattern_1.flatMap(translatePattern_2)` for example -- it actually looks more like: + * `translatePattern_1(x_scrut).flatMap((x_1) => {y_i -> x_1._i}translatePattern_2)` + * + * `x_1` references the result (inside the monad) of the extractor corresponding to `pat_1`, + * this result holds the values for the constructor arguments, which translatePattern_1 has extracted + * from the object pointed to by `x_scrut`. The `y_i` are the symbols bound by `pat_1` (in order) + * in the scope of the remainder of the pattern, and they must thus be replaced by: + * - (for 1-ary unapply) x_1 + * - (for n-ary unapply, n > 1) selection of the i'th tuple component of `x_1` + * - (for unapplySeq) x_1.apply(i) + * + * in the treemakers, + * + * Thus, the result type of `translatePattern_i`'s extractor must conform to `M[(T_1,..., T_n)]`. + * + * Operationally, phase 1) is a foldLeft, since we must consider the depth-first-flattening of + * the transformed patterns from left to right. For every pattern ast node, it produces a transformed ast and + * a function that will take care of binding and substitution of the next ast (to the right). + * + */ + def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef): List[TreeMaker] = { + val CaseDef(pattern, guard, body) = caseDef + translatePattern(BoundTree(scrutSym, pattern)) ++ translateGuard(guard) :+ translateBody(body, pt) + } + + def translatePattern(bound: BoundTree): List[TreeMaker] = bound.translate() + + def translateGuard(guard: Tree): List[TreeMaker] = + if (guard == EmptyTree) Nil + else List(GuardTreeMaker(guard)) + + // TODO: 1) if we want to support a generalisation of Kotlin's patmat continue, must not hard-wire lifting into the monad (which is now done by codegen.one), + // so that user can generate failure when needed -- use implicit conversion to lift into monad on-demand? + // to enable this, probably need to move away from Option to a monad specific to pattern-match, + // so that we can return Option's from a match without ambiguity whether this indicates failure in the monad, or just some result in the monad + // 2) body.tpe is the type of the body after applying the substitution that represents the solution of GADT type inference + // need the explicit cast in case our substitutions in the body change the type to something that doesn't take GADT typing into account + def translateBody(body: Tree, matchPt: Type): TreeMaker = + BodyTreeMaker(body, matchPt) + + // Some notes from the specification + + /*A constructor pattern is of the form c(p1, ..., pn) where n ≥ 0. + It consists of a stable identifier c, followed by element patterns p1, ..., pn. + The constructor c is a simple or qualified name which denotes a case class (§5.3.2). + + If the case class is monomorphic, then it must conform to the expected type of the pattern, + and the formal parameter types of x’s primary constructor (§5.3) are taken as the expected + types of the element patterns p1, ..., pn. + + If the case class is polymorphic, then its type parameters are instantiated so that the + instantiation of c conforms to the expected type of the pattern. + The instantiated formal parameter types of c’s primary constructor are then taken as the + expected types of the component patterns p1, ..., pn. + + The pattern matches all objects created from constructor invocations c(v1, ..., vn) + where each element pattern pi matches the corresponding value vi . + A special case arises when c’s formal parameter types end in a repeated parameter. + This is further discussed in (§8.1.9). + **/ + + /* A typed pattern x : T consists of a pattern variable x and a type pattern T. + The type of x is the type pattern T, where each type variable and wildcard is replaced by a fresh, unknown type. + This pattern matches any value matched by the type pattern T (§8.2); it binds the variable name to that value. + */ + + /* A pattern binder x@p consists of a pattern variable x and a pattern p. + The type of the variable x is the static type T of the pattern p. + This pattern matches any value v matched by the pattern p, + provided the run-time type of v is also an instance of T, <-- TODO! https://issues.scala-lang.org/browse/SI-1503 + and it binds the variable name to that value. + */ + + /* 8.1.4 Literal Patterns + A literal pattern L matches any value that is equal (in terms of ==) to the literal L. + The type of L must conform to the expected type of the pattern. + + 8.1.5 Stable Identifier Patterns (a stable identifier r (see §3.1)) + The pattern matches any value v such that r == v (§12.1). + The type of r must conform to the expected type of the pattern. + */ + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // helper methods: they analyze types and trees in isolation, but they are not (directly) concerned with the structure of the overall translation + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + object ExtractorCall { + // TODO: check unargs == args + def apply(tree: Tree, binder: Symbol): ExtractorCall = { + tree match { + case UnApply(unfun, implicits, args) => + val castedBinder = ref(binder).ensureConforms(tree.tpe) + val synth = if (implicits.isEmpty) unfun.appliedTo(castedBinder) else unfun.appliedTo(castedBinder).appliedToArgs(implicits) + new ExtractorCallRegular(alignPatterns(tree, synth.tpe), synth, args, synth.tpe) // extractor + case Typed(unapply@ UnApply(unfun, implicits, args), tpt) => + val castedBinder = ref(binder).ensureConforms(unapply.tpe) + val synth = /*Typed(*/ if (implicits.isEmpty) unfun.appliedTo(castedBinder) else unfun.appliedTo(castedBinder).appliedToArgs(implicits) //, tpt) + new ExtractorCallRegular(alignPatterns(tree, synth.tpe), synth, args, synth.tpe) // extractor + case Apply(fun, args) => new ExtractorCallProd(alignPatterns(tree, tree.tpe), fun, args, fun.tpe) // case class + } + } + } + + abstract class ExtractorCall(val aligner: PatternAligned) { + + import aligner._ + + def args: List[Tree] + + // don't go looking for selectors if we only expect one pattern + def rawSubPatTypes = aligner.extractedTypes + + def typeArgOfBaseTypeOr(tp: Type, baseClass: Symbol)(or: => Type): Type = (tp.baseTypeWithArgs(baseClass)).argInfos match { + case x :: Nil => x + case _ => or + } + + def resultInMonad = if (aligner.isBool) defn.UnitType else { + val getTp = extractorMemberType(resultType, nme.get) + if ((extractorMemberType(resultType, nme.isDefined) isRef defn.BooleanClass) && getTp.exists) + getTp + else resultType + } + def resultType: Type + + /** Create the TreeMaker that embodies this extractor call + * + * `binder` has been casted to `paramType` if necessary + * `binderKnownNonNull` indicates whether the cast implies `binder` cannot be null + * when `binderKnownNonNull` is `true`, `ProductExtractorTreeMaker` does not do a (redundant) null check on binder + */ + def treeMaker(binder: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker + + // `subPatBinders` are the variables bound by this pattern in the following patterns + // subPatBinders are replaced by references to the relevant part of the extractor's result (tuple component, seq element, the result as-is) + // must set infos to `subPatTypes`, which are provided by extractor's result, + // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation + // (it will later result in a type test when `tp` is not a subtype of `b.info`) + // TODO: can we simplify this, together with the Bound case? + def subPatBinders = subBoundTrees map (_.binder) + lazy val subBoundTrees = (args, subPatTypes).zipped map newBoundTree + + // never store these in local variables (for PreserveSubPatBinders) + lazy val ignoredSubPatBinders: Set[Symbol] = subPatBinders zip args collect { case (b, PatternBoundToUnderscore()) => b } toSet + + // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) + private def nonStarSubPatTypes = aligner.typedNonStarPatterns map (_.tpe) + + def subPatTypes: List[Type] = typedPatterns map (_.tpe) + + // there are `prodArity` non-seq elements in the tuple. + protected def firstIndexingBinder = prodArity + protected def expectedLength = elementArity + protected def lastIndexingBinder = totalArity - starArity - 1 + + private def productElemsToN(binder: Symbol, n: Int): List[Tree] = 1 to n map tupleSel(binder) toList + private def genTake(binder: Symbol, n: Int): List[Tree] = (0 until n).toList map (codegen index seqTree(binder)) + private def genDrop(binder: Symbol, n: Int): List[Tree] = codegen.drop(seqTree(binder))(expectedLength) :: Nil + + // codegen.drop(seqTree(binder))(nbIndexingIndices)))).toList + protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1) + protected def tupleSel(binder: Symbol)(i: Int): Tree = { + val accessors = + if (defn.isProductSubType(binder.info)) + productSelectors(binder.info) + else binder.caseAccessors + val res = + if (accessors.isDefinedAt(i - 1)) ref(binder).select(accessors(i - 1).name) + else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN + val rsym = res.symbol // just for debugging + res + } + + // the trees that select the subpatterns on the extractor's result, + // referenced by `binder` + protected def subPatRefsSeq(binder: Symbol): List[Tree] = { + def lastTrees: List[Tree] = ( + if (!aligner.isStar) Nil + else if (expectedLength == 0) seqTree(binder) :: Nil + else genDrop(binder, expectedLength) + ) + // this error-condition has already been checked by checkStarPatOK: + // if (isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if (lastIsStar) 1 else 0) == totalArity, "(resultInMonad, ts, subPatTypes, subPats)= " +(resultInMonad, ts, subPatTypes, subPats)) + + // [1] there are `firstIndexingBinder` non-seq tuple elements preceding the Seq + // [2] then we have to index the binder that represents the sequence for the remaining subpatterns, except for... + // [3] the last one -- if the last subpattern is a sequence wildcard: + // drop the prefix (indexed by the refs on the preceding line), return the remainder + ( productElemsToN(binder, firstIndexingBinder) + ++ genTake(binder, expectedLength) + ++ lastTrees + ).toList + } + + // the trees that select the subpatterns on the extractor's result, referenced by `binder` + // require (nbSubPats > 0 && (!lastIsStar || isSeq)) + protected def subPatRefs(binder: Symbol): List[Tree] = { + val refs = if (totalArity > 0 && isSeq) subPatRefsSeq(binder) + else if (binder.info.member(nme._1).exists && !isSeq) productElemsToN(binder, totalArity) + else ref(binder) :: Nil + refs + } + + val mathSignymSymbol = defn.ScalaMathPackageVal.requiredMethod("signum".toTermName, List(defn.IntType)) + val mathSignum = ref(defn.ScalaMathPackageVal).select(mathSignymSymbol) + + + private def compareInts(t1: Tree, t2: Tree) = + mathSignum.appliedTo(t1.select(defn.Int_-).appliedTo(t2)) + //gen.mkMethodCall(termMember(ScalaPackage, "math"), TermName("signum"), Nil, (t1 INT_- t2) :: Nil) + + protected def lengthGuard(binder: Symbol): Option[Tree] = + // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied + checkedLength map { expectedLength => + // `binder.lengthCompare(expectedLength)` + // ...if binder has a lengthCompare method, otherwise + // `scala.math.signum(binder.length - expectedLength)` + def checkExpectedLength: Tree = sequenceType.member(nme.lengthCompare) match { + case NoDenotation => compareInts(Select(seqTree(binder), nme.length), Literal(Constant(expectedLength))) + case x:SingleDenotation => (seqTree(binder).select(x.symbol)).appliedTo(Literal(Constant(expectedLength))) + case _ => + ctx.error("TODO: multiple lengthCompare") + EmptyTree + } + + // the comparison to perform + // when the last subpattern is a wildcard-star the expectedLength is but a lower bound + // (otherwise equality is required) + def compareOp: (Tree, Tree) => Tree = + if (aligner.isStar) _.select(defn.Int_>=).appliedTo(_) + else _.select(defn.Int_==).appliedTo(_) + + // `if (binder != null && $checkExpectedLength [== | >=] 0) then else zero` + (seqTree(binder).select(defn.Any_!=).appliedTo(Literal(Constant(null)))).select(defn.Boolean_&&).appliedTo(compareOp(checkExpectedLength, Literal(Constant(0)))) + } + + def checkedLength: Option[Int] = + // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied + if (!isSeq || expectedLength < starArity) None + else Some(expectedLength) + } + + // TODO: to be called when there's a def unapplyProd(x: T): U + // U must have N members _1,..., _N -- the _i are type checked, call their type Ti, + // for now only used for case classes -- pretending there's an unapplyProd that's the identity (and don't call it) + class ExtractorCallProd(aligner: PatternAligned, val fun: Tree, val args: List[Tree], val resultType: Type) extends ExtractorCall(aligner) { + /** Create the TreeMaker that embodies this extractor call + * + * `binder` has been casted to `paramType` if necessary + * `binderKnownNonNull` indicates whether the cast implies `binder` cannot be null + * when `binderKnownNonNull` is `true`, `ProductExtractorTreeMaker` does not do a (redundant) null check on binder + */ + def treeMaker(binder: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker = { + val paramAccessors = binder.caseAccessors + // binders corresponding to mutable fields should be stored (SI-5158, SI-6070) + // make an exception for classes under the scala package as they should be well-behaved, + // to optimize matching on List + val mutableBinders = ( + if (//!binder.info.typeSymbol.hasTransOwner(ScalaPackageClass) // TODO: DDD ??? + // && + (paramAccessors exists (_.hasAltWith(x => x.symbol is Flags.Mutable)))) + subPatBinders.zipWithIndex.collect{ case (binder, idx) if paramAccessors(idx).hasAltWith(x => x.symbol is Flags.Mutable) => binder } + else Nil + ) + + // checks binder ne null before chaining to the next extractor + ProductExtractorTreeMaker(binder, lengthGuard(binder))(subPatBinders, subPatRefs(binder), mutableBinders, binderKnownNonNull, ignoredSubPatBinders) + } + } + + class ExtractorCallRegular(aligner: PatternAligned, extractorCallIncludingDummy: Tree, val args: List[Tree], val resultType: Type) extends ExtractorCall(aligner) { + + /** Create the TreeMaker that embodies this extractor call + * + * `binder` has been casted to `paramType` if necessary + * `binderKnownNonNull` is not used in this subclass + * + * TODO: implement review feedback by @retronym: + * Passing the pair of values around suggests: + * case class Binder(sym: Symbol, knownNotNull: Boolean). + * Perhaps it hasn't reached critical mass, but it would already clean things up a touch. + */ + def treeMaker(patBinderOrCasted: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker = { + // the extractor call (applied to the binder bound by the flatMap corresponding + // to the previous (i.e., enclosing/outer) pattern) + val extractorApply = extractorCallIncludingDummy// spliceApply(patBinderOrCasted) + // can't simplify this when subPatBinders.isEmpty, since UnitTpe is definitely + // wrong when isSeq, and resultInMonad should always be correct since it comes + // directly from the extractor's result type + val binder = freshSym(pos, resultInMonad) + val spb = subPatBinders + ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)( + spb, + subPatRefs(binder, spb, resultType), + aligner.isBool, + checkedLength, + patBinderOrCasted, + ignoredSubPatBinders + ) + } + + override protected def seqTree(binder: Symbol): Tree = + if (firstIndexingBinder == 0) ref(binder) + else super.seqTree(binder) + + // the trees that select the subpatterns on the extractor's result, referenced by `binder` + // require (totalArity > 0 && (!lastIsStar || isSeq)) + protected def subPatRefs(binder: Symbol, subpatBinders: List[Symbol], binderTypeTested: Type): List[Tree] = { + if (aligner.isSingle && aligner.extractor.prodArity == 1 && defn.isTupleType(binder.info)) { + // special case for extractor + // comparing with scalac additional assertions added + val subpw = subpatBinders.head.info.widen + val binderw = binder.info.widen + val go = subpatBinders.head.info <:< binder.info + val go1 = binder.info <:< subpatBinders.head.info + //val spr = subPatRefs(binder) + assert(go && go1) + ref(binder) :: Nil + } else { + lazy val getTp = extractorMemberType(binderTypeTested, nme.get) + if ((aligner.isSingle && aligner.extractor.prodArity == 1) && ((extractorMemberType(binderTypeTested, nme.isDefined) isRef defn.BooleanClass) && getTp.exists)) + List(ref(binder)) + else + subPatRefs(binder) + } + } + + /*protected def spliceApply(binder: Symbol): Tree = { + object splice extends TreeMap { + def binderRef(pos: Position): Tree = + ref(binder) //setPos pos + + override def transform(t: tpd.Tree)(implicit ctx: Context): tpd.Tree = t match { + // duplicated with the extractor Unapplied + case Apply(x, List(i @ Ident(nme.SELECTOR_DUMMY))) => + cpy.Apply(t, x, binderRef(i.pos) :: Nil) + // SI-7868 Account for numeric widening, e.g. <unappplySelector>.toInt + case Apply(x, List(i @ (sel @ Select(Ident(nme.SELECTOR_DUMMY), name)))) => + cpy.Apply(t, x, cpy.Select(sel, binderRef(i.pos), name) :: Nil) + case _ => + super.transform(t) + } + } + splice transform extractorCallIncludingDummy + }*/ + + override def rawSubPatTypes = aligner.extractor.varargsTypes + } + } + + /** An extractor returns: F1, F2, ..., Fi, opt[Seq[E] or E*] + * A case matches: P1, P2, ..., Pj, opt[Seq[E]] + * Put together: P1/F1, P2/F2, ... Pi/Fi, Pi+1/E, Pi+2/E, ... Pj/E, opt[Seq[E]] + * + * Here Pm/Fi is the last pattern to match the fixed arity section. + * + * prodArity: the value of i, i.e. the number of non-sequence types in the extractor + * nonStarArity: the value of j, i.e. the number of non-star patterns in the case definition + * elementArity: j - i, i.e. the number of non-star patterns which must match sequence elements + * starArity: 1 or 0 based on whether there is a star (sequence-absorbing) pattern + * totalArity: nonStarArity + starArity, i.e. the number of patterns in the case definition + * + * Note that prodArity is a function only of the extractor, and + * nonStar/star/totalArity are all functions of the patterns. The key + * value for aligning and typing the patterns is elementArity, as it + * is derived from both sets of information. + */ + trait PatternExpander[Pattern, Type] { + /** You'll note we're not inside the cake. "Pattern" and "Type" are + * arbitrary types here, and NoPattern and NoType arbitrary values. + */ + def NoPattern: Pattern + def NoType: Type + + /** It's not optimal that we're carrying both sequence and repeated + * type here, but the implementation requires more unraveling before + * it can be avoided. + * + * sequenceType is Seq[T], elementType is T, repeatedType is T*. + */ + sealed case class Repeated(sequenceType: Type, elementType: Type, repeatedType: Type) { + def exists = elementType != NoType + + def elementList = if (exists) elementType :: Nil else Nil + def sequenceList = if (exists) sequenceType :: Nil else Nil + def repeatedList = if (exists) repeatedType :: Nil else Nil + + override def toString = s"${elementType}*" + } + object NoRepeated extends Repeated(NoType, NoType, NoType) { + override def toString = "<none>" + } + + final case class Patterns(fixed: List[Pattern], star: Pattern) { + def hasStar = star != NoPattern + def starArity = if (hasStar) 1 else 0 + def nonStarArity = fixed.length + def totalArity = nonStarArity + starArity + def starPatterns = if (hasStar) star :: Nil else Nil + def all = fixed ::: starPatterns + + override def toString = all mkString ", " + } + + /** An 'extractor' can be a case class or an unapply or unapplySeq method. + * Decoding what it is that they extract takes place before we arrive here, + * so that this class can concentrate only on the relationship between + * patterns and types. + * + * In a case class, the class is the unextracted type and the fixed and + * repeated types are derived from its constructor parameters. + * + * In an unapply, this is reversed: the parameter to the unapply is the + * unextracted type, and the other types are derived based on the return + * type of the unapply method. + * + * In other words, this case class and unapply are encoded the same: + * + * case class Foo(x: Int, y: Int, zs: Char*) + * def unapplySeq(x: Foo): Option[(Int, Int, Seq[Char])] + * + * Both are Extractor(Foo, Int :: Int :: Nil, Repeated(Seq[Char], Char, Char*)) + * + * @param whole The type in its unextracted form + * @param fixed The non-sequence types which are extracted + * @param repeated The sequence type which is extracted + */ + final case class Extractor(whole: Type, fixed: List[Type], repeated: Repeated) { + require(whole != NoType, s"expandTypes($whole, $fixed, $repeated)") + + def prodArity = fixed.length + def hasSeq = repeated.exists + def elementType = repeated.elementType + def sequenceType = repeated.sequenceType + def allTypes = fixed ::: repeated.sequenceList + def varargsTypes = fixed ::: repeated.repeatedList + def isErroneous = allTypes contains NoType + + private def typeStrings = fixed.map("" + _) ::: ( if (hasSeq) List("" + repeated) else Nil ) + + def offeringString = if (isErroneous) "<error>" else typeStrings match { + case Nil => "Boolean" + case tp :: Nil => tp + case tps => tps.mkString("(", ", ", ")") + } + override def toString = "%s => %s".format(whole, offeringString) + } + + final case class TypedPat(pat: Pattern, tpe: Type) { + override def toString = s"$pat: $tpe" + } + + /** If elementArity is... + * 0: A perfect match between extractor and the fixed patterns. + * If there is a star pattern it will match any sequence. + * > 0: There are more patterns than products. There will have to be a + * sequence which can populate at least <elementArity> patterns. + * < 0: There are more products than patterns: compile time error. + */ + final case class Aligned(patterns: Patterns, extractor: Extractor) { + def elementArity = patterns.nonStarArity - prodArity + def prodArity = extractor.prodArity + def starArity = patterns.starArity + def totalArity = patterns.totalArity + + def wholeType = extractor.whole + def sequenceType = extractor.sequenceType + def productTypes = extractor.fixed + def extractedTypes = extractor.allTypes + def typedNonStarPatterns = products ::: elements + def typedPatterns = typedNonStarPatterns ::: stars + + def isBool = !isSeq && prodArity == 0 + def isSingle = !isSeq && totalArity == 1 + def isStar = patterns.hasStar + def isSeq = extractor.hasSeq + + private def typedAsElement(pat: Pattern) = TypedPat(pat, extractor.elementType) + private def typedAsSequence(pat: Pattern) = TypedPat(pat, extractor.sequenceType) + private def productPats = patterns.fixed take prodArity + private def elementPats = patterns.fixed drop prodArity + private def products = (productPats, productTypes).zipped map TypedPat + private def elements = elementPats map typedAsElement + private def stars = patterns.starPatterns map typedAsSequence + + override def toString = s""" + |Aligned { + | patterns $patterns + | extractor $extractor + | arities $prodArity/$elementArity/$starArity // product/element/star + | typed ${typedPatterns mkString ", "} + |}""".stripMargin.trim + } + } + + /** This is scalac-specific logic layered on top of the scalac-agnostic + * "matching products to patterns" logic defined in PatternExpander. + */ + trait ScalacPatternExpanders { + + type PatternAligned = ScalacPatternExpander#Aligned + + implicit class AlignedOps(val aligned: PatternAligned) { + import aligned._ + def expectedTypes = typedPatterns map (_.tpe) + def unexpandedFormals = extractor.varargsTypes + } + + trait ScalacPatternExpander extends PatternExpander[Tree, Type] { + def NoPattern = EmptyTree + def NoType = core.Types.NoType + + def newPatterns(patterns: List[Tree]): Patterns = patterns match { + case init :+ last if tpd.isWildcardStarArg(last) => Patterns(init, last) + case _ => Patterns(patterns, NoPattern) + } + def typeOfMemberNamedHead(tpe: Type): Type = tpe.select(nme.head) + def typeOfMemberNamedApply(tpe: Type): Type = tpe.select(nme.apply) + + def elementTypeOf(tpe: Type) = { + val seq = tpe //repeatedToSeq(tpe) + + ( typeOfMemberNamedHead(seq) + orElse typeOfMemberNamedApply(seq) + orElse seq.elemType + ) + } + def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated): Extractor = { + ctx.log(s"newExtractor($whole, $fixed, $repeated") + Extractor(whole, fixed, repeated) + } + + // Turn Seq[A] into Repeated(Seq[A], A, A*) + def repeatedFromSeq(seqType: Type): Repeated = { + val elem = elementTypeOf(seqType) + val repeated = /*scalaRepeatedType(*/elem//) + + Repeated(seqType, elem, repeated) + } + // Turn A* into Repeated(Seq[A], A, A*) + def repeatedFromVarargs(repeated: Type): Repeated = + //Repeated(repeatedToSeq(repeated), repeatedToSingle(repeated), repeated) + Repeated(repeated, repeated.elemType, repeated) + + /** In this case we are basing the pattern expansion on a case class constructor. + * The argument is the MethodType carried by the primary constructor. + */ + def applyMethodTypes(method: Type): Extractor = { + val whole = method.finalResultType + + method.paramTypess.head match { + case init :+ last if last.isRepeatedParam => newExtractor(whole, init, repeatedFromVarargs(last)) + case tps => newExtractor(whole, tps, NoRepeated) + } + } + + def hasSelectors(tpe: Type) = tpe.member(nme._1).exists && tpe.member(nme._2).exists // dd todo: ??? + + + /** In this case, expansion is based on an unapply or unapplySeq method. + * Unfortunately the MethodType does not carry the information of whether + * it was unapplySeq, so we have to funnel that information in separately. + */ + def unapplyMethodTypes(tree: Tree, fun: Tree, args: List[Tree], resultType: Type, isSeq: Boolean): Extractor = { + _id = _id + 1 + + val whole = tree.tpe // see scaladoc for Trees.Unapply + // fun.tpe.widen.paramTypess.headOption.flatMap(_.headOption).getOrElse(NoType)//firstParamType(method) + val resultOfGet = extractorMemberType(resultType, nme.get) + + val expanded: List[Type] = /*( + if (result =:= defn.BooleanType) Nil + else if (defn.isProductSubType(result)) productSelectorTypes(result) + else if (result.classSymbol is Flags.CaseClass) result.decls.filter(x => x.is(Flags.CaseAccessor) && x.is(Flags.Method)).map(_.info).toList + else result.select(nme.get) :: Nil + )*/ + if ((extractorMemberType(resultType, nme.isDefined) isRef defn.BooleanClass) && resultOfGet.exists) + getUnapplySelectors(resultOfGet, args) + else if (defn.isProductSubType(resultType)) productSelectorTypes(resultType) + else if (resultType isRef defn.BooleanClass) Nil + else { + ctx.error(i"invalid return type in Unapply node: $resultType") + Nil + } + + expanded match { + case init :+ last if isSeq => newExtractor(whole, init, repeatedFromSeq(last)) + case tps => newExtractor(whole, tps, NoRepeated) + } + } + } + + object alignPatterns extends ScalacPatternExpander { + /** Converts a T => (A, B, C) extractor to a T => ((A, B, CC)) extractor. + */ + def tupleExtractor(extractor: Extractor): Extractor = + extractor.copy(fixed = defn.tupleType(extractor.fixed) :: Nil) + + private def validateAligned(tree: Tree, aligned: Aligned): Aligned = { + import aligned._ + + def owner = tree.symbol.owner + def offering = extractor.offeringString + def symString = tree.symbol.showLocated + def offerString = if (extractor.isErroneous) "" else s" offering $offering" + def arityExpected = (if (extractor.hasSeq) "at least " else "") + prodArity + + def err(msg: String) = ctx.error(msg, tree.pos) + def warn(msg: String) = ctx.warning(msg, tree.pos) + def arityError(what: String) = err(s"${_id} $what patterns for $owner$offerString: expected $arityExpected, found $totalArity") + + if (isStar && !isSeq) + err("Star pattern must correspond with varargs or unapplySeq") + else if (elementArity < 0) + arityError("not enough") + else if (elementArity > 0 && !extractor.hasSeq) + arityError("too many") + + aligned + } + + object Applied { + // Duplicated with `spliceApply` + def unapply(tree: Tree): Option[Tree] = tree match { + // SI-7868 Admit Select() to account for numeric widening, e.g. <unappplySelector>.toInt + /*case Apply(fun, (Ident(nme.SELECTOR_DUMMY)| Select(Ident(nme.SELECTOR_DUMMY), _)) :: Nil) + => Some(fun)*/ + case Apply(fun, _) => unapply(fun) + case _ => None + } + } + + def apply(tree: Tree, sel: Tree, args: List[Tree], resultType: Type): Aligned = { + val fn = sel match { + case Applied(fn) => fn + case _ => sel + } + val patterns = newPatterns(args) + val isSeq = sel.symbol.name == nme.unapplySeq + val isUnapply = sel.symbol.name == nme.unapply + val extractor = sel.symbol.name match { + case nme.unapply => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = false) + case nme.unapplySeq => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = true) + case _ => applyMethodTypes(/*fn*/sel.tpe) + } + + /** Rather than let the error that is SI-6675 pollute the entire matching + * process, we will tuple the extractor before creation Aligned so that + * it contains known good values. + */ + def prodArity = extractor.prodArity + def acceptMessage = if (extractor.isErroneous) "" else s" to hold ${extractor.offeringString}" + val requiresTupling = isUnapply && patterns.totalArity == 1 && prodArity > 1 + + //if (requiresTupling && effectivePatternArity(args) == 1) + // currentUnit.deprecationWarning(sel.pos, s"${sel.symbol.owner} expects $prodArity patterns$acceptMessage but crushing into $prodArity-tuple to fit single pattern (SI-6675)") + + val normalizedExtractor = + if (requiresTupling) + tupleExtractor(extractor) + else extractor + validateAligned(fn, Aligned(patterns, normalizedExtractor)) + } + + def apply(tree: Tree, resultType: Type): Aligned = tree match { + case Typed(tree, _) => apply(tree, resultType) + case Apply(fn, args) => apply(tree, fn, args, resultType) + case UnApply(fn, implicits, args) => apply(tree, fn, args, resultType) + } + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala new file mode 100644 index 000000000..61c3ca5de --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -0,0 +1,108 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts.Context +import Decorators._ +import tasty._ +import config.Printers.{noPrinter, pickling} +import java.io.PrintStream +import Periods._ +import Phases._ +import Symbols._ +import Flags.Module +import collection.mutable + +/** This phase pickles trees */ +class Pickler extends Phase { + import ast.tpd._ + + override def phaseName: String = "pickler" + + private def output(name: String, msg: String) = { + val s = new PrintStream(name) + s.print(msg) + s.close + } + + // Maps that keep a record if -Ytest-pickler is set. + private val beforePickling = new mutable.HashMap[ClassSymbol, String] + private val picklers = new mutable.HashMap[ClassSymbol, TastyPickler] + + /** Drop any elements of this list that are linked module classes of other elements in the list */ + private def dropCompanionModuleClasses(clss: List[ClassSymbol])(implicit ctx: Context): List[ClassSymbol] = { + val companionModuleClasses = + clss.filterNot(_ is Module).map(_.linkedClass).filterNot(_.isAbsent) + clss.filterNot(companionModuleClasses.contains) + } + + override def run(implicit ctx: Context): Unit = { + val unit = ctx.compilationUnit + pickling.println(i"unpickling in run ${ctx.runId}") + + for { cls <- dropCompanionModuleClasses(topLevelClasses(unit.tpdTree)) + tree <- sliceTopLevel(unit.tpdTree, cls) } { + val pickler = new TastyPickler() + if (ctx.settings.YtestPickler.value) { + beforePickling(cls) = tree.show + picklers(cls) = pickler + } + val treePkl = pickler.treePkl + treePkl.pickle(tree :: Nil) + treePkl.compactify() + pickler.addrOfTree = treePkl.buf.addrOfTree + pickler.addrOfSym = treePkl.addrOfSym + if (tree.pos.exists) + new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil) + + // other pickle sections go here. + val pickled = pickler.assembleParts() + unit.pickled += (cls -> pickled) + + def rawBytes = // not needed right now, but useful to print raw format. + pickled.iterator.grouped(10).toList.zipWithIndex.map { + case (row, i) => s"${i}0: ${row.mkString(" ")}" + } + // println(i"rawBytes = \n$rawBytes%\n%") // DEBUG + if (pickling ne noPrinter) { + println(i"**** pickled info of $cls") + new TastyPrinter(pickler.assembleParts()).printContents() + } + } + } + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + val result = super.runOn(units) + if (ctx.settings.YtestPickler.value) + testUnpickler( + ctx.fresh + .setPeriod(Period(ctx.runId + 1, FirstPhaseId)) + .addMode(Mode.ReadPositions)) + result + } + + private def testUnpickler(implicit ctx: Context): Unit = { + pickling.println(i"testing unpickler at run ${ctx.runId}") + ctx.initialize() + val unpicklers = + for ((cls, pickler) <- picklers) yield { + val unpickler = new DottyUnpickler(pickler.assembleParts()) + unpickler.enter(roots = Set()) + cls -> unpickler + } + pickling.println("************* entered toplevel ***********") + for ((cls, unpickler) <- unpicklers) { + val unpickled = unpickler.body + testSame(i"$unpickled%\n%", beforePickling(cls), cls) + } + } + + private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(implicit ctx: Context) = + if (previous != unpickled) { + output("before-pickling.txt", previous) + output("after-pickling.txt", unpickled) + ctx.error(i"""pickling difference for ${cls.fullName} in ${cls.sourceFile}, for details: + | + | diff before-pickling.txt after-pickling.txt""") + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala new file mode 100644 index 000000000..1ed47d92e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -0,0 +1,286 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import dotty.tools.dotc.ast.{Trees, tpd} +import scala.collection.{ mutable, immutable } +import ValueClasses._ +import scala.annotation.tailrec +import core._ +import typer.ErrorReporting._ +import typer.Checking +import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import util.Positions._ +import Decorators._ +import config.Printers.typr +import Symbols._, TypeUtils._ + +/** A macro transform that runs immediately after typer and that performs the following functions: + * + * (1) Add super accessors and protected accessors (@see SuperAccessors) + * + * (2) Convert parameter fields that have the same name as a corresponding + * public parameter field in a superclass to a forwarder to the superclass + * field (corresponding = super class field is initialized with subclass field) + * (@see ForwardParamAccessors) + * + * (3) Add synthetic methods (@see SyntheticMethods) + * + * (4) Check that `New` nodes can be instantiated, and that annotations are valid + * + * (5) Convert all trees representing types to TypeTrees. + * + * (6) Check the bounds of AppliedTypeTrees + * + * (7) Insert `.package` for selections of package object members + * + * (8) Replaces self references by name with `this` + * + * (9) Adds SourceFile annotations to all top-level classes and objects + * + * (10) Adds Child annotations to all sealed classes + * + * (11) Minimizes `call` fields of `Inline` nodes to just point to the toplevel + * class from which code was inlined. + * + * The reason for making this a macro transform is that some functions (in particular + * super and protected accessors and instantiation checks) are naturally top-down and + * don't lend themselves to the bottom-up approach of a mini phase. The other two functions + * (forwarding param accessors and synthetic methods) only apply to templates and fit + * mini-phase or subfunction of a macro phase equally well. But taken by themselves + * they do not warrant their own group of miniphases before pickling. + */ +class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTransformer => + + import tpd._ + + /** the following two members override abstract members in Transform */ + override def phaseName: String = "posttyper" + + override def transformPhase(implicit ctx: Context) = thisTransformer.next + + protected def newTransformer(implicit ctx: Context): Transformer = + new PostTyperTransformer + + val superAcc = new SuperAccessors(thisTransformer) + val paramFwd = new ParamForwarding(thisTransformer) + val synthMth = new SyntheticMethods(thisTransformer) + + private def newPart(tree: Tree): Option[New] = methPart(tree) match { + case Select(nu: New, _) => Some(nu) + case _ => None + } + + private def checkValidJavaAnnotation(annot: Tree)(implicit ctx: Context): Unit = { + // TODO fill in + } + + /** If the type of `tree` is a TermRefWithSignature with an underdefined + * signature, narrow the type by re-computing the signature (which should + * be fully-defined by now). + */ + private def fixSignature[T <: Tree](tree: T)(implicit ctx: Context): T = tree.tpe match { + case tpe: TermRefWithSignature if tpe.signature.isUnderDefined => + typr.println(i"fixing $tree with type ${tree.tpe.widen.toString} with sig ${tpe.signature} to ${tpe.widen.signature}") + tree.withType(TermRef.withSig(tpe.prefix, tpe.name, tpe.widen.signature)).asInstanceOf[T] + case _ => tree + } + + class PostTyperTransformer extends Transformer { + + private var inJavaAnnot: Boolean = false + + private var parentNews: Set[New] = Set() + + private def transformAnnot(annot: Tree)(implicit ctx: Context): Tree = { + val saved = inJavaAnnot + inJavaAnnot = annot.symbol is JavaDefined + if (inJavaAnnot) checkValidJavaAnnotation(annot) + try transform(annot) + finally inJavaAnnot = saved + } + + private def transformAnnot(annot: Annotation)(implicit ctx: Context): Annotation = + annot.derivedAnnotation(transformAnnot(annot.tree)) + + private def transformMemberDef(tree: MemberDef)(implicit ctx: Context): Unit = { + val sym = tree.symbol + sym.transformAnnotations(transformAnnot) + if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass) { + val info1 = Checking.checkNoPrivateLeaks(sym, tree.pos) + if (info1 ne sym.info) + sym.copySymDenotation(info = info1).installAfter(thisTransformer) + } + } + + private def transformSelect(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = { + val qual = tree.qualifier + qual.symbol.moduleClass.denot match { + case pkg: PackageClassDenotation if !tree.symbol.maybeOwner.is(Package) => + transformSelect(cpy.Select(tree)(qual select pkg.packageObj.symbol, tree.name), targs) + case _ => + val tree1 = super.transform(tree) + constToLiteral(tree1) match { + case _: Literal => tree1 + case _ => superAcc.transformSelect(tree1, targs) + } + } + } + + private def normalizeTypeArgs(tree: TypeApply)(implicit ctx: Context): TypeApply = tree.tpe match { + case pt: PolyType => // wait for more arguments coming + tree + case _ => + def decompose(tree: TypeApply): (Tree, List[Tree]) = tree.fun match { + case fun: TypeApply => + val (tycon, args) = decompose(fun) + (tycon, args ++ tree.args) + case _ => + (tree.fun, tree.args) + } + def reorderArgs(pnames: List[Name], namedArgs: List[NamedArg], otherArgs: List[Tree]): List[Tree] = pnames match { + case pname :: pnames1 => + namedArgs.partition(_.name == pname) match { + case (NamedArg(_, arg) :: _, namedArgs1) => + arg :: reorderArgs(pnames1, namedArgs1, otherArgs) + case _ => + val otherArg :: otherArgs1 = otherArgs + otherArg :: reorderArgs(pnames1, namedArgs, otherArgs1) + } + case nil => + assert(namedArgs.isEmpty && otherArgs.isEmpty) + Nil + } + val (tycon, args) = decompose(tree) + tycon.tpe.widen match { + case tp: PolyType => + val (namedArgs, otherArgs) = args.partition(isNamedArg) + val args1 = reorderArgs(tp.paramNames, namedArgs.asInstanceOf[List[NamedArg]], otherArgs) + TypeApply(tycon, args1).withPos(tree.pos).withType(tree.tpe) + case _ => + tree + } + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = + try tree match { + case tree: Ident if !tree.isType => + tree.tpe match { + case tpe: ThisType => This(tpe.cls).withPos(tree.pos) + case _ => paramFwd.adaptRef(fixSignature(tree)) + } + case tree @ Select(qual, name) => + if (name.isTypeName) { + Checking.checkRealizable(qual.tpe, qual.pos.focus) + super.transform(tree) + } + else + transformSelect(paramFwd.adaptRef(fixSignature(tree)), Nil) + case tree: Super => + if (ctx.owner.enclosingMethod.isInlineMethod) + ctx.error(em"super not allowed in inline ${ctx.owner}", tree.pos) + super.transform(tree) + case tree: TypeApply => + val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) + Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) + fn match { + case sel: Select => + val args1 = transform(args) + val sel1 = transformSelect(sel, args1) + if (superAcc.isProtectedAccessor(sel1)) sel1 else cpy.TypeApply(tree1)(sel1, args1) + case _ => + super.transform(tree1) + } + case tree @ Assign(sel: Select, _) => + superAcc.transformAssign(super.transform(tree)) + case Inlined(call, bindings, expansion) => + // Leave only a call trace consisting of + // - a reference to the top-level class from which the call was inlined, + // - the call's position + // in the call field of an Inlined node. + // The trace has enough info to completely reconstruct positions. + // The minimization is done for two reasons: + // 1. To save space (calls might contain large inline arguments, which would otherwise + // be duplicated + // 2. To enable correct pickling (calls can share symbols with the inlined code, which + // would trigger an assertion when pickling). + val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) + cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)) + case tree: Template => + val saved = parentNews + parentNews ++= tree.parents.flatMap(newPart) + try { + val templ1 = paramFwd.forwardParamAccessors(tree) + synthMth.addSyntheticMethods( + superAcc.wrapTemplate(templ1)( + super.transform(_).asInstanceOf[Template])) + } + finally parentNews = saved + case tree: DefDef => + transformMemberDef(tree) + superAcc.wrapDefDef(tree)(super.transform(tree).asInstanceOf[DefDef]) + case tree: TypeDef => + transformMemberDef(tree) + val sym = tree.symbol + if (sym.isClass) { + // Add SourceFile annotation to top-level classes + if (sym.owner.is(Package) && + ctx.compilationUnit.source.exists && + sym != defn.SourceFileAnnot) + sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path)) + + // Add Child annotation to sealed parents unless current class is anonymous + if (!sym.isAnonymousClass) // ignore anonymous class + for (parent <- sym.asClass.classInfo.classParents) { + val pclazz = parent.classSymbol + if (pclazz.is(Sealed)) pclazz.addAnnotation(Annotation.makeChild(sym)) + } + + tree + } + super.transform(tree) + case tree: MemberDef => + transformMemberDef(tree) + super.transform(tree) + case tree: New if !inJavaAnnot && !parentNews.contains(tree) => + Checking.checkInstantiable(tree.tpe, tree.pos) + super.transform(tree) + case tree @ Annotated(annotated, annot) => + cpy.Annotated(tree)(transform(annotated), transformAnnot(annot)) + case tree: AppliedTypeTree => + Checking.checkAppliedType(tree) + super.transform(tree) + case SingletonTypeTree(ref) => + Checking.checkRealizable(ref.tpe, ref.pos.focus) + super.transform(tree) + case tree: TypeTree => + tree.withType( + tree.tpe match { + case AnnotatedType(tpe, annot) => AnnotatedType(tpe, transformAnnot(annot)) + case tpe => tpe + } + ) + case Import(expr, selectors) => + val exprTpe = expr.tpe + def checkIdent(ident: Ident): Unit = { + val name = ident.name.asTermName.encode + if (name != nme.WILDCARD && !exprTpe.member(name).exists && !exprTpe.member(name.toTypeName).exists) + ctx.error(s"${ident.name} is not a member of ${expr.show}", ident.pos) + } + selectors.foreach { + case ident: Ident => checkIdent(ident) + case Thicket((ident: Ident) :: _) => checkIdent(ident) + case _ => + } + super.transform(tree) + case tree => + super.transform(tree) + } + catch { + case ex : AssertionError => + println(i"error while transforming $tree") + throw ex + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/PrivateToStatic.scala.disabled b/compiler/src/dotty/tools/dotc/transform/PrivateToStatic.scala.disabled new file mode 100644 index 000000000..218839d01 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PrivateToStatic.scala.disabled @@ -0,0 +1,94 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers.SymTransformer +import Contexts.Context +import Symbols._ +import Scopes._ +import Flags._ +import StdNames._ +import SymDenotations._ +import Types._ +import collection.mutable +import TreeTransforms._ +import Decorators._ +import ast.Trees._ +import TreeTransforms.TransformerInfo + +/** Makes private methods static, provided they not deferred, accessors, or static, + * by rewriting a method `m` in class `C` as follows: + * + * private def m(ps) = e + * + * --> private static def($this: C, ps) = [this -> $this] e + */ +class PrivateToStatic extends MiniPhase with SymTransformer { thisTransform => + import ast.tpd._ + override def phaseName = "privateToStatic" + override def relaxedTyping = true + + private val Immovable = Deferred | Accessor | JavaStatic + + def shouldBeStatic(sd: SymDenotation)(implicit ctx: Context) = + sd.current(ctx.withPhase(thisTransform)).asSymDenotation + .is(PrivateMethod, butNot = Immovable) && + sd.owner.is(Trait) + + override def transformSym(sd: SymDenotation)(implicit ctx: Context): SymDenotation = + if (shouldBeStatic(sd)) { + val mt @ MethodType(pnames, ptypes) = sd.info + sd.copySymDenotation( + initFlags = sd.flags | JavaStatic, + info = MethodType(nme.SELF :: pnames, sd.owner.thisType :: ptypes, mt.resultType)) + } + else sd + + val treeTransform = new Transform(NoSymbol) + + class Transform(thisParam: Symbol) extends TreeTransform { + def phase = thisTransform + + override def prepareForDefDef(tree: DefDef)(implicit ctx: Context) = + if (shouldBeStatic(tree.symbol)) { + val selfParam = ctx.newSymbol(tree.symbol, nme.SELF, Param, tree.symbol.owner.thisType, coord = tree.pos) + new Transform(selfParam) + } + else this + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo) = + if (shouldBeStatic(tree.symbol)) { + val thisParamDef = ValDef(thisParam.asTerm) + val vparams :: Nil = tree.vparamss + cpy.DefDef(tree)(vparamss = (thisParamDef :: vparams) :: Nil) + } + else tree + + override def transformThis(tree: This)(implicit ctx: Context, info: TransformerInfo) = + if (shouldBeStatic(ctx.owner.enclosingMethod)) ref(thisParam).withPos(tree.pos) + else tree + + /** Rwrites a call to a method `m` which is made static as folows: + * + * qual.m(args) --> m(qual, args) + */ + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + tree.fun match { + case fun @ Select(qual, name) if shouldBeStatic(fun.symbol) => + ctx.debuglog(i"mapping $tree to ${cpy.Ident(fun)(name)} (${qual :: tree.args}%, %)") + cpy.Apply(tree)(ref(fun.symbol).withPos(fun.pos), qual :: tree.args) + case _ => + tree + } + + override def transformClosure(tree: Closure)(implicit ctx: Context, info: TransformerInfo) = + tree.meth match { + case meth @ Select(qual, name) if shouldBeStatic(meth.symbol) => + cpy.Closure(tree)( + env = qual :: tree.env, + meth = ref(meth.symbol).withPos(meth.pos)) + case _ => + tree + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala new file mode 100644 index 000000000..e718a7e60 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -0,0 +1,115 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import Contexts.Context +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import StdNames._ +import NameOps._ +import ast.Trees._ +import util.Positions._ +import Names._ +import collection.mutable +import ResolveSuper._ + +/** This phase adds super accessors and method overrides where + * linearization differs from Java's rule for default methods in interfaces. + * In particular: + * + * For every trait M directly implemented by the class (see SymUtils.mixin), in + * reverse linearization order, add the following definitions to C: + * + * 3.1 (done in `superAccessors`) For every superAccessor + * `<mods> def super$f[Ts](ps1)...(psN): U` in M: + * + * <mods> def super$f[Ts](ps1)...(psN): U = super[S].f[Ts](ps1)...(psN) + * + * where `S` is the superclass of `M` in the linearization of `C`. + * + * 3.2 (done in `methodOverrides`) For every method + * `<mods> def f[Ts](ps1)...(psN): U` in M` that needs to be disambiguated: + * + * <mods> def f[Ts](ps1)...(psN): U = super[M].f[Ts](ps1)...(psN) + * + * A method in M needs to be disambiguated if it is concrete, not overridden in C, + * and if it overrides another concrete method. + * + * This is the first part of what was the mixin phase. It is complemented by + * Mixin, which runs after erasure. + */ +class ResolveSuper extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + override def phaseName: String = "resolveSuper" + + override def runsAfter = Set(classOf[ElimByName], // verified empirically, need to figure out what the reason is. + classOf[AugmentScala2Traits]) + + override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = { + val cls = impl.symbol.owner.asClass + val ops = new MixinOps(cls, thisTransform) + import ops._ + + def superAccessors(mixin: ClassSymbol): List[Tree] = + for (superAcc <- mixin.info.decls.filter(_ is SuperAccessor).toList) + yield polyDefDef(implementation(superAcc.asTerm), forwarder(rebindSuper(cls, superAcc))) + + def methodOverrides(mixin: ClassSymbol): List[Tree] = + for (meth <- mixin.info.decls.toList if needsForwarder(meth)) + yield polyDefDef(implementation(meth.asTerm), forwarder(meth)) + + val overrides = mixins.flatMap(mixin => superAccessors(mixin) ::: methodOverrides(mixin)) + + cpy.Template(impl)(body = overrides ::: impl.body) + } + + override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo) = { + val meth = ddef.symbol.asTerm + if (meth.is(SuperAccessor, butNot = Deferred)) { + assert(ddef.rhs.isEmpty) + val cls = meth.owner.asClass + val ops = new MixinOps(cls, thisTransform) + import ops._ + polyDefDef(meth, forwarder(rebindSuper(cls, meth))) + } + else ddef + } + + private val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred +} + +object ResolveSuper{ + /** Returns the symbol that is accessed by a super-accessor in a mixin composition. + * + * @param base The class in which everything is mixed together + * @param acc The symbol statically referred to by the superaccessor in the trait + */ + def rebindSuper(base: Symbol, acc: Symbol)(implicit ctx: Context): Symbol = { + var bcs = base.info.baseClasses.dropWhile(acc.owner != _).tail + var sym: Symbol = NoSymbol + val unexpandedAccName = + if (acc.is(ExpandedName)) // Cannot use unexpandedName because of #765. t2183.scala would fail if we did. + acc.name + .drop(acc.name.indexOfSlice(nme.EXPAND_SEPARATOR ++ nme.SUPER_PREFIX)) + .drop(nme.EXPAND_SEPARATOR.length) + else acc.name + val SuperAccessorName(memberName) = unexpandedAccName: Name // dotty deviation: ": Name" needed otherwise pattern type is neither a subtype nor a supertype of selector type + ctx.debuglog(i"starting rebindsuper from $base of ${acc.showLocated}: ${acc.info} in $bcs, name = $memberName") + while (bcs.nonEmpty && sym == NoSymbol) { + val other = bcs.head.info.nonPrivateDecl(memberName) + if (ctx.settings.debug.value) + ctx.log(i"rebindsuper ${bcs.head} $other deferred = ${other.symbol.is(Deferred)}") + sym = other.matchingDenotation(base.thisType, base.thisType.memberInfo(acc)).symbol + bcs = bcs.tail + } + assert(sym.exists) + sym + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala new file mode 100644 index 000000000..8b9d2be0d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala @@ -0,0 +1,67 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers.IdentityDenotTransformer +import Contexts.Context +import Symbols._ +import Scopes._ +import collection.mutable +import TreeTransforms.MiniPhaseTransform +import SymDenotations._ +import ast.Trees._ +import NameOps._ +import TreeTransforms.TransformerInfo +import StdNames._ + +/** The preceding lambda lift and flatten phases move symbols to different scopes + * and rename them. This miniphase cleans up afterwards and makes sure that all + * class scopes contain the symbols defined in them. + */ +class RestoreScopes extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + override def phaseName = "restoreScopes" + + /* Note: We need to wait until we see a package definition because + * DropEmptyConstructors changes template members when analyzing the + * enclosing package definitions. So by the time RestoreScopes gets to + * see a typedef or template, it still might be changed by DropEmptyConstructors. + */ + override def transformPackageDef(pdef: PackageDef)(implicit ctx: Context, info: TransformerInfo) = { + pdef.stats.foreach(restoreScope) + pdef + } + + private def restoreScope(tree: Tree)(implicit ctx: Context, info: TransformerInfo) = tree match { + case TypeDef(_, impl: Template) => + val restoredDecls = newScope + for (stat <- impl.constr :: impl.body) + if (stat.isInstanceOf[MemberDef] && stat.symbol.exists) + restoredDecls.enter(stat.symbol) + // Enter class in enclosing package scope, in case it was an inner class before flatten. + // For top-level classes this does nothing. + val cls = tree.symbol.asClass + val pkg = cls.owner.asClass + + // Bring back companion links + val companionClass = cls.info.decls.lookup(nme.COMPANION_CLASS_METHOD) + val companionModule = cls.info.decls.lookup(nme.COMPANION_MODULE_METHOD) + + if (companionClass.exists) { + restoredDecls.enter(companionClass) + } + + if (companionModule.exists) { + restoredDecls.enter(companionModule) + } + + pkg.enter(cls) + val cinfo = cls.classInfo + tree.symbol.copySymDenotation( + info = cinfo.derivedClassInfo( // Dotty deviation: Cannot expand cinfo inline without a type error + decls = restoredDecls: Scope)).installAfter(thisTransform) + tree + case tree => tree + } +} + diff --git a/compiler/src/dotty/tools/dotc/transform/SelectStatic.scala b/compiler/src/dotty/tools/dotc/transform/SelectStatic.scala new file mode 100644 index 000000000..5d60bb984 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SelectStatic.scala @@ -0,0 +1,56 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core._ +import dotty.tools.dotc.transform.TreeTransforms._ + +/** Removes selects that would be compiled into GetStatic + * otherwise backend needs to be aware that some qualifiers need to be dropped. + * Similar transformation seems to be performed by flatten in nsc + * @author Dmytro Petrashko + */ +class SelectStatic extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + override def phaseName: String = "selectStatic" + + override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val sym = tree.symbol + def isStaticMember = + (sym is Flags.Module) && sym.initial.maybeOwner.initial.isStaticOwner || + (sym is Flags.JavaStatic) || + (sym.maybeOwner is Flags.ImplClass) || + sym.hasAnnotation(ctx.definitions.ScalaStaticAnnot) + val isStaticRef = !sym.is(Package) && !sym.maybeOwner.is(Package) && isStaticMember + val tree1 = + if (isStaticRef && !tree.qualifier.symbol.is(JavaModule) && !tree.qualifier.isType) + Block(List(tree.qualifier), ref(sym)) + else tree + + normalize(tree1) + } + + private def normalize(t: Tree)(implicit ctx: Context) = t match { + case Select(Block(stats, qual), nm) => + Block(stats, cpy.Select(t)(qual, nm)) + case Apply(Block(stats, qual), nm) => + Block(stats, Apply(qual, nm)) + case TypeApply(Block(stats, qual), nm) => + Block(stats, TypeApply(qual, nm)) + case _ => t + } + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + normalize(tree) + } + + override def transformTypeApply(tree: tpd.TypeApply)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + normalize(tree) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala b/compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala new file mode 100644 index 000000000..49ea69530 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala @@ -0,0 +1,48 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import dotty.tools.dotc.transform.TreeTransforms._ +import Contexts.Context +import Symbols._ +import Phases._ +import Decorators._ + +/** A transformer that eliminates SeqLiteral's, transforming `SeqLiteral(elems)` to an operation + * equivalent to + * + * JavaSeqLiteral(elems).toSeq + * + * Instead of `toSeq`, which takes an implicit, the appropriate "wrapArray" method + * is called directly. The reason for this step is that JavaSeqLiterals, being arrays + * keep a precise type after erasure, whereas SeqLiterals only get the erased type `Seq`, + */ +class SeqLiterals extends MiniPhaseTransform { + import ast.tpd._ + + override def phaseName = "seqLiterals" + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[PatternMatcher]) + + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { + case tpd: SeqLiteral => assert(tpd.isInstanceOf[JavaSeqLiteral]) + case _ => + } + + override def transformSeqLiteral(tree: SeqLiteral)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { + case tree: JavaSeqLiteral => tree + case _ => + val arr = JavaSeqLiteral(tree.elems, tree.elemtpt) + //println(i"trans seq $tree, arr = $arr: ${arr.tpe} ${arr.tpe.elemType}") + val elemtp = tree.elemtpt.tpe + val elemCls = elemtp.classSymbol + val (wrapMethStr, targs) = + if (elemCls.isPrimitiveValueClass) (s"wrap${elemCls.name}Array", Nil) + else if (elemtp derivesFrom defn.ObjectClass) ("wrapRefArray", elemtp :: Nil) + else ("genericWrapArray", elemtp :: Nil) + ref(defn.ScalaPredefModule) + .select(wrapMethStr.toTermName) + .appliedToTypes(targs) + .appliedTo(arr) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/Splitter.scala b/compiler/src/dotty/tools/dotc/transform/Splitter.scala new file mode 100644 index 000000000..d62be1a82 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Splitter.scala @@ -0,0 +1,121 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import ast.Trees._ +import core._ +import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._ + +/** Distribute applications into Block and If nodes + */ +class Splitter extends MiniPhaseTransform { thisTransform => + import ast.tpd._ + + override def phaseName: String = "splitter" + + /** Distribute arguments among splitted branches */ + def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { + def recur(fn: Tree): Tree = fn match { + case Block(stats, expr) => Block(stats, recur(expr)) + case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep)) + case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos + } + recur(tree.fun) + } + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, typeApply) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, apply) + + private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx) + private val apply = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx) + +/* The following is no longer necessary, since we select members on the join of an or type: + * + /** If we select a name, make sure the node has a symbol. + * If necessary, split the qualifier with type tests. + * Example: Assume: + * + * class A { def f(x: S): T } + * class B { def f(x: S): T } + * def p(): A | B + * + * Then p().f(a) translates to + * + * val ev$1 = p() + * if (ev$1.isInstanceOf[A]) ev$1.asInstanceOf[A].f(a) + * else ev$1.asInstanceOf[B].f(a) + */ + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = { + val Select(qual, name) = tree + + def memberDenot(tp: Type): SingleDenotation = { + val mbr = tp.member(name) + if (!mbr.isOverloaded) mbr.asSingleDenotation + else tree.tpe match { + case tref: TermRefWithSignature => mbr.atSignature(tref.sig).checkUnique + case _ => + def alts = mbr.alternatives.map(alt => i"$alt: ${alt.info}").mkString(", ") + ctx.error(s"cannot disambiguate overloaded members $alts", tree.pos) + NoDenotation + } + } + + def candidates(tp: Type): List[Symbol] = { + val mbr = memberDenot(tp) + if (mbr.symbol.exists) mbr.symbol :: Nil + else tp.widen match { + case tref: TypeRef => + tref.info match { + case TypeBounds(_, hi) => candidates(hi) + case _ => Nil + } + case OrType(tp1, tp2) => + candidates(tp1) | candidates(tp2) + case AndType(tp1, tp2) => + candidates(tp1) & candidates(tp2) + case tpw => + Nil + } + } + + def isStructuralSelect(tp: Type): Boolean = tp.stripTypeVar match { + case tp: RefinedType => tp.refinedName == name || isStructuralSelect(tp.parent) + case tp: TypeProxy => isStructuralSelect(tp.underlying) + case AndType(tp1, tp2) => isStructuralSelect(tp1) || isStructuralSelect(tp2) + case _ => false + } + + if (tree.symbol.exists) tree + else { + def choose(qual: Tree, syms: List[Symbol]): Tree = { + def testOrCast(which: Symbol, mbr: Symbol) = + qual.select(which).appliedToType(mbr.owner.typeRef) + def select(sym: Symbol) = { + val qual1 = + if (qual.tpe derivesFrom sym.owner) qual + else testOrCast(defn.Any_asInstanceOf, sym) + qual1.select(sym).withPos(tree.pos) + } + syms match { + case Nil => + def msg = + if (isStructuralSelect(qual.tpe)) + s"cannot access member '$name' from structural type ${qual.tpe.widen.show}; use Dynamic instead" + else + s"no candidate symbols for ${tree.tpe.show} found in ${qual.tpe.show}" + ctx.error(msg, tree.pos) + tree + case sym :: Nil => + select(sym) + case sym :: syms1 => + If(testOrCast(defn.Any_isInstanceOf, sym), select(sym), choose(qual, syms1)) + } + } + evalOnce(qual)(qual => choose(qual, candidates(qual.tpe))) + } + } +*/ +} diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala new file mode 100644 index 000000000..fea478c9b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -0,0 +1,424 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import dotty.tools.dotc.ast.{Trees, tpd} +import scala.collection.{ mutable, immutable } +import ValueClasses._ +import scala.annotation.tailrec +import core._ +import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import util.Positions._ +import Decorators._ +import Symbols._, TypeUtils._ + +/** This class performs the following functions: + * + * (1) Adds super accessors for all super calls that either + * appear in a trait or have as a target a member of some outer class. + * + * (2) Adds protected accessors if the access to the protected member happens + * in a class which is not a subclass of the member's owner. + * + * It also checks that: + * + * (1) Symbols accessed from super are not abstract, or are overridden by + * an abstract override. + * + * (2) If a symbol accessed from super is defined in a real class (not a trait), + * there are no abstract members which override this member in Java's rules + * (see SI-4989; such an access would lead to illegal bytecode) + * + * (3) Super calls do not go to some synthetic members of Any (see isDisallowed) + * + * (4) Super calls do not go to synthetic field accessors + */ +class SuperAccessors(thisTransformer: DenotTransformer) { + + import tpd._ + + + /** Some parts of trees will get a new owner in subsequent phases. + * These are value class methods, which will become extension methods. + * (By-name arguments used to be included also, but these + * don't get a new class anymore, they are just wrapped in a new method). + * + * These regions will have to be treated specially for the purpose + * of adding accessors. For instance, super calls from these regions + * always have to go through an accessor. + * + * The `invalidEnclClass` field, if different from NoSymbol, + * contains the symbol that is not a valid owner. + */ + private var invalidEnclClass: Symbol = NoSymbol + + private def withInvalidCurrentClass[A](trans: => A)(implicit ctx: Context): A = { + val saved = invalidEnclClass + invalidEnclClass = ctx.owner + try trans + finally invalidEnclClass = saved + } + + private def validCurrentClass(implicit ctx: Context): Boolean = + ctx.owner.enclosingClass != invalidEnclClass + + /** List buffers for new accessor definitions, indexed by class */ + private val accDefs = mutable.Map[Symbol, mutable.ListBuffer[Tree]]() + + /** A super accessor call corresponding to `sel` */ + private def superAccessorCall(sel: Select)(implicit ctx: Context) = { + val Select(qual, name) = sel + val sym = sel.symbol + val clazz = qual.symbol.asClass + var supername = name.superName + if (clazz is Trait) supername = supername.expandedName(clazz) + + val superAcc = clazz.info.decl(supername).suchThat(_.signature == sym.signature).symbol orElse { + ctx.debuglog(s"add super acc ${sym.showLocated} to $clazz") + val deferredOrPrivate = if (clazz is Trait) Deferred | ExpandedName else Private + val acc = ctx.newSymbol( + clazz, supername, SuperAccessor | Artifact | Method | deferredOrPrivate, + sel.tpe.widenSingleton.ensureMethodic, coord = sym.coord).enteredAfter(thisTransformer) + // Diagnostic for SI-7091 + if (!accDefs.contains(clazz)) + ctx.error(s"Internal error: unable to store accessor definition in ${clazz}. clazz.hasPackageFlag=${clazz is Package}. Accessor required for ${sel} (${sel.show})", sel.pos) + else accDefs(clazz) += DefDef(acc, EmptyTree) + acc + } + + This(clazz).select(superAcc).withPos(sel.pos) + } + + /** Check selection `super.f` for conforming to rules. If necessary, + * replace by a super accessor call. + */ + private def transformSuperSelect(sel: Select)(implicit ctx: Context): Tree = { + val Select(sup @ Super(_, mix), name) = sel + val sym = sel.symbol + assert(sup.symbol.exists, s"missing symbol in $sel: ${sup.tpe}") + val clazz = sup.symbol.asClass + + if (sym.isTerm && !sym.is(Method, butNot = Accessor) && !ctx.owner.is(ParamForwarder)) + // ParamForwaders as installed ParamForwarding.scala do use super calls to vals + ctx.error(s"super may be not be used on ${sym.underlyingSymbol}", sel.pos) + else if (isDisallowed(sym)) + ctx.error(s"super not allowed here: use this.${sel.name.decode} instead", sel.pos) + else if (sym is Deferred) { + val member = sym.overridingSymbol(clazz) + if (!mix.name.isEmpty || + !member.exists || + !((member is AbsOverride) && member.isIncompleteIn(clazz))) + ctx.error( + i"${sym.showLocated} is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override'", + sel.pos) + else ctx.log(i"ok super $sel ${sym.showLocated} $member $clazz ${member.isIncompleteIn(clazz)}") + } + else if (mix.name.isEmpty && !(sym.owner is Trait)) + // SI-4989 Check if an intermediate class between `clazz` and `sym.owner` redeclares the method as abstract. + for (intermediateClass <- clazz.info.baseClasses.tail.takeWhile(_ != sym.owner)) { + val overriding = sym.overridingSymbol(intermediateClass) + if ((overriding is (Deferred, butNot = AbsOverride)) && !(overriding.owner is Trait)) + ctx.error( + s"${sym.showLocated} cannot be directly accessed from ${clazz} because ${overriding.owner} redeclares it as abstract", + sel.pos) + + } + if (name.isTermName && mix.name.isEmpty && + ((clazz is Trait) || clazz != ctx.owner.enclosingClass || !validCurrentClass)) + superAccessorCall(sel)(ctx.withPhase(thisTransformer.next)) + else sel + } + + /** Disallow some super.XX calls targeting Any methods which would + * otherwise lead to either a compiler crash or runtime failure. + */ + private def isDisallowed(sym: Symbol)(implicit ctx: Context) = { + val d = defn + import d._ + (sym eq Any_isInstanceOf) || + (sym eq Any_asInstanceOf) || + (sym eq Any_==) || + (sym eq Any_!=) || + (sym eq Any_##) + } + + /** Replace `sel` (or `sel[targs]` if `targs` is nonempty) with a protected accessor + * call, if necessary. + */ + private def ensureProtectedAccessOK(sel: Select, targs: List[Tree])(implicit ctx: Context) = { + val sym = sel.symbol + if (sym.isTerm && !sel.name.isOuterSelect && needsProtectedAccessor(sym, sel.pos)) { + ctx.debuglog("Adding protected accessor for " + sel) + protectedAccessorCall(sel, targs) + } else sel + } + + /** Add a protected accessor, if needed, and return a tree that calls + * the accessor and returns the same member. The result is already + * typed. + */ + private def protectedAccessorCall(sel: Select, targs: List[Tree])(implicit ctx: Context): Tree = { + val Select(qual, _) = sel + val sym = sel.symbol.asTerm + val clazz = hostForAccessorOf(sym, currentClass) + assert(clazz.exists, sym) + ctx.debuglog("Decided for host class: " + clazz) + + val accName = sym.name.protectedAccessorName + + // if the result type depends on the this type of an enclosing class, the accessor + // has to take an object of exactly this type, otherwise it's more general + val receiverType = + if (isThisType(sym.info.finalResultType)) clazz.thisType + else clazz.classInfo.selfType + val accType = { + def accTypeOf(tpe: Type): Type = tpe match { + case tpe: PolyType => + tpe.derivedPolyType(tpe.paramNames, tpe.paramBounds, accTypeOf(tpe.resultType)) + case _ => + MethodType(receiverType :: Nil)(mt => tpe.substThis(sym.owner.asClass, MethodParam(mt, 0))) + } + accTypeOf(sym.info) + } + val protectedAccessor = clazz.info.decl(accName).suchThat(_.signature == accType.signature).symbol orElse { + val newAcc = ctx.newSymbol( + clazz, accName, Artifact, accType, coord = sel.pos).enteredAfter(thisTransformer) + val code = polyDefDef(newAcc, trefs => vrefss => { + val (receiver :: _) :: tail = vrefss + val base = receiver.select(sym).appliedToTypes(trefs) + (base /: vrefss)(Apply(_, _)) + }) + ctx.debuglog("created protected accessor: " + code) + accDefs(clazz) += code + newAcc + } + val res = This(clazz) + .select(protectedAccessor) + .appliedToTypeTrees(targs) + .appliedTo(qual) + .withPos(sel.pos) + ctx.debuglog(s"Replaced $sel with $res") + res + } + + def isProtectedAccessor(tree: Tree)(implicit ctx: Context): Boolean = tree match { + case Apply(TypeApply(Select(_, name), _), qual :: Nil) => name.isProtectedAccessorName + case _ => false + } + + /** Add a protected accessor, if needed, and return a tree that calls + * the accessor and returns the same member. The result is already + * typed. + */ + private def protectedAccessor(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = { + val Select(qual, _) = tree + val sym = tree.symbol.asTerm + val clazz = hostForAccessorOf(sym, currentClass) + assert(clazz.exists, sym) + ctx.debuglog("Decided for host class: " + clazz) + + val accName = sym.name.protectedAccessorName + + // if the result type depends on the this type of an enclosing class, the accessor + // has to take an object of exactly this type, otherwise it's more general + val receiverType = + if (isThisType(sym.info.finalResultType)) clazz.thisType + else clazz.classInfo.selfType + def accTypeOf(tpe: Type): Type = tpe match { + case tpe: PolyType => + tpe.derivedPolyType(tpe.paramNames, tpe.paramBounds, accTypeOf(tpe.resultType)) + case _ => + MethodType(receiverType :: Nil)(mt => tpe.substThis(sym.owner.asClass, MethodParam(mt, 0))) + } + val accType = accTypeOf(sym.info) + val protectedAccessor = clazz.info.decl(accName).suchThat(_.signature == accType.signature).symbol orElse { + val newAcc = ctx.newSymbol( + clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) + val code = polyDefDef(newAcc, trefs => vrefss => { + val (receiver :: _) :: tail = vrefss + val base = receiver.select(sym).appliedToTypes(trefs) + (base /: vrefss)(Apply(_, _)) + }) + ctx.debuglog("created protected accessor: " + code) + accDefs(clazz) += code + newAcc + } + val res = This(clazz) + .select(protectedAccessor) + .appliedToTypeTrees(targs) + .appliedTo(qual) + .withPos(tree.pos) + ctx.debuglog(s"Replaced $tree with $res") + res + } + + /** Add an accessor for field, if needed, and return a selection tree for it . + * The result is not typed. + */ + private def protectedSetter(tree: Select)(implicit ctx: Context): Tree = { + val field = tree.symbol.asTerm + val clazz = hostForAccessorOf(field, currentClass) + assert(clazz.exists, field) + ctx.debuglog("Decided for host class: " + clazz) + + val accName = field.name.protectedSetterName + val accType = MethodType(clazz.classInfo.selfType :: field.info :: Nil, defn.UnitType) + val protectedAccessor = clazz.info.decl(accName).symbol orElse { + val newAcc = ctx.newSymbol( + clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) + val code = DefDef(newAcc, vrefss => { + val (receiver :: value :: Nil) :: Nil = vrefss + Assign(receiver.select(field), value).withPos(tree.pos) + }) + ctx.debuglog("created protected setter: " + code) + accDefs(clazz) += code + newAcc + } + This(clazz).select(protectedAccessor).withPos(tree.pos) + } + + /** Does `sym` need an accessor when accessed from `currentClass`? + * A special case arises for classes with explicit self-types. If the + * self type is a Java class, and a protected accessor is needed, we issue + * an error. If the self type is a Scala class, we don't add an accessor. + * An accessor is not needed if the access boundary is larger than the + * enclosing package, since that translates to 'public' on the host sys. + * (as Java has no real package nesting). + * + * If the access happens inside a 'trait', access is more problematic since + * the implementation code is moved to an '$class' class which does not + * inherit anything. Since we can't (yet) add accessors for 'required' + * classes, this has to be signaled as error. + * FIXME Need to better understand this logic + */ + private def needsProtectedAccessor(sym: Symbol, pos: Position)(implicit ctx: Context): Boolean = { + val clazz = currentClass + val host = hostForAccessorOf(sym, clazz) + val selfType = host.classInfo.selfType + def accessibleThroughSubclassing = + validCurrentClass && (selfType <:< sym.owner.typeRef) && !clazz.is(Trait) + + val isCandidate = ( + sym.is(Protected) + && sym.is(JavaDefined) + && !sym.effectiveOwner.is(Package) + && !accessibleThroughSubclassing + && (sym.enclosingPackageClass != currentClass.enclosingPackageClass) + && (sym.enclosingPackageClass == sym.accessBoundary(sym.enclosingPackageClass)) + ) + def isSelfType = !(host.typeRef <:< selfType) && { + if (selfType.typeSymbol.is(JavaDefined)) + ctx.restrictionError(s"cannot accesses protected $sym from within $clazz with self type $selfType", pos) + true + } + def isJavaProtected = host.is(Trait) && sym.is(JavaDefined) && { + ctx.restrictionError( + s"""$clazz accesses protected $sym inside a concrete trait method. + |Add an accessor in a class extending ${sym.enclosingClass} as a workaround.""".stripMargin, + pos + ) + true + } + isCandidate && !host.is(Package) && !isSelfType && !isJavaProtected + } + + /** Return the innermost enclosing class C of referencingClass for which either + * of the following holds: + * - C is a subclass of sym.owner or + * - C is declared in the same package as sym's owner + */ + private def hostForAccessorOf(sym: Symbol, referencingClass: ClassSymbol)(implicit ctx: Context): ClassSymbol = + if (referencingClass.derivesFrom(sym.owner) + || referencingClass.classInfo.selfType <:< sym.owner.typeRef + || referencingClass.enclosingPackageClass == sym.owner.enclosingPackageClass) { + assert(referencingClass.isClass, referencingClass) + referencingClass + } + else if (referencingClass.owner.enclosingClass.exists) + hostForAccessorOf(sym, referencingClass.owner.enclosingClass.asClass) + else + referencingClass + + /** Is 'tpe' a ThisType, or a type proxy with a ThisType as transitively underlying type? */ + private def isThisType(tpe: Type)(implicit ctx: Context): Boolean = tpe match { + case tpe: ThisType => !tpe.cls.is(PackageClass) + case tpe: TypeProxy => isThisType(tpe.underlying) + case _ => false + } + + /** Transform select node, adding super and protected accessors as needed */ + def transformSelect(tree: Tree, targs: List[Tree])(implicit ctx: Context) = { + val sel @ Select(qual, name) = tree + val sym = sel.symbol + qual match { + case _: This => + /* + * A trait which extends a class and accesses a protected member + * of that class cannot implement the necessary accessor method + * because its implementation is in an implementation class (e.g. + * Foo$class) which inherits nothing, and jvm access restrictions + * require the call site to be in an actual subclass. So non-trait + * classes inspect their ancestors for any such situations and + * generate the accessors. See SI-2296. + */ + // FIXME (from scalac's SuperAccessors) + // - this should be unified with needsProtectedAccessor, but some + // subtlety which presently eludes me is foiling my attempts. + val shouldEnsureAccessor = ( + (currentClass is Trait) + && (sym is Protected) + && sym.enclosingClass != currentClass + && !(sym.owner is PackageClass) // SI-7091 no accessor needed package owned (ie, top level) symbols + && !(sym.owner is Trait) + && sym.owner.enclosingPackageClass != currentClass.enclosingPackageClass + && qual.symbol.info.member(sym.name).exists + && !needsProtectedAccessor(sym, sel.pos)) + if (shouldEnsureAccessor) { + ctx.log("Ensuring accessor for call to protected " + sym.showLocated + " from " + currentClass) + superAccessorCall(sel) + } else + ensureProtectedAccessOK(sel, targs) + + case Super(_, mix) => + transformSuperSelect(sel) + + case _ => + ensureProtectedAccessOK(sel, targs) + } + } + + /** Transform assignment, adding a protected setter if needed */ + def transformAssign(tree: Tree)(implicit ctx: Context) = { + val Assign(lhs @ Select(qual, name), rhs) = tree + if ((lhs.symbol is Mutable) && + (lhs.symbol is JavaDefined) && + needsProtectedAccessor(lhs.symbol, tree.pos)) { + ctx.debuglog("Adding protected setter for " + tree) + val setter = protectedSetter(lhs) + ctx.debuglog("Replaced " + tree + " with " + setter) + setter.appliedTo(qual, rhs) + } + else tree + } + + /** Wrap template to template transform `op` with needed initialization and finalization */ + def wrapTemplate(tree: Template)(op: Template => Template)(implicit ctx: Context) = { + accDefs(currentClass) = new mutable.ListBuffer[Tree] + val impl = op(tree) + val accessors = accDefs.remove(currentClass).get + if (accessors.isEmpty) impl + else { + val (params, rest) = impl.body span { + case td: TypeDef => !td.isClassDef + case vd: ValOrDefDef => vd.symbol.flags is ParamAccessor + case _ => false + } + cpy.Template(impl)(body = params ++ accessors ++ rest) + } + } + + /** Wrap `DefDef` producing operation `op`, potentially setting `invalidClass` info */ + def wrapDefDef(ddef: DefDef)(op: => DefDef)(implicit ctx: Context) = + if (isMethodWithExtension(ddef.symbol)) withInvalidCurrentClass(op) else op +} diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala new file mode 100644 index 000000000..05305575e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -0,0 +1,117 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import Contexts._ +import Symbols._ +import SymDenotations._ +import Decorators._ +import Names._ +import StdNames._ +import NameOps._ +import Flags._ +import Annotations._ +import language.implicitConversions + +object SymUtils { + implicit def decorateSymbol(sym: Symbol): SymUtils = new SymUtils(sym) + implicit def decorateSymDenot(d: SymDenotation): SymUtils = new SymUtils(d.symbol) +} + +/** A decorator that provides methods on symbols + * that are needed in the transformer pipeline. + */ +class SymUtils(val self: Symbol) extends AnyVal { + import SymUtils._ + + /** 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 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 directlyInheritedTraits + } + + def isTypeTestOrCast(implicit ctx: Context): Boolean = + self == defn.Any_asInstanceOf || self == defn.Any_isInstanceOf + + def isVolatile(implicit ctx: Context) = self.hasAnnotation(defn.VolatileAnnot) + + def isAnyOverride(implicit ctx: Context) = self.is(Override) || self.is(AbsOverride) + // careful: AbsOverride is a term only flag. combining with Override would catch only terms. + + /** If this is a constructor, its owner: otherwise this. */ + final def skipConstructor(implicit ctx: Context): Symbol = + if (self.isConstructor) self.owner else self + + /** The closest properly enclosing method or class of this symbol. */ + final def enclosure(implicit ctx: Context) = { + self.owner.enclosingMethodOrClass + } + + /** The closest enclosing method or class of this symbol */ + final def enclosingMethodOrClass(implicit ctx: Context): Symbol = + if (self.is(Method, butNot = Label) || self.isClass) self + else if (self.exists) self.owner.enclosingMethodOrClass + else NoSymbol + + /** Apply symbol/symbol substitution to this symbol */ + def subst(from: List[Symbol], to: List[Symbol]): Symbol = { + def loop(from: List[Symbol], to: List[Symbol]): Symbol = + if (from.isEmpty) self + else if (self eq from.head) to.head + else loop(from.tail, to.tail) + loop(from, to) + } + + def accessorNamed(name: TermName)(implicit ctx: Context): Symbol = + self.owner.info.decl(name).suchThat(_ is Accessor).symbol + + def termParamAccessors(implicit ctx: Context): List[Symbol] = + self.info.decls.filter(_ is TermParamAccessor).toList + + def caseAccessors(implicit ctx:Context) = + self.info.decls.filter(_ is CaseAccessor).toList + + def getter(implicit ctx: Context): Symbol = + if (self.isGetter) self else accessorNamed(self.asTerm.name.getterName) + + def setter(implicit ctx: Context): Symbol = + if (self.isSetter) self + else accessorNamed(self.asTerm.name.setterName) + + def field(implicit ctx: Context): Symbol = + self.owner.info.decl(self.asTerm.name.fieldName).suchThat(!_.is(Method)).symbol + + def isField(implicit ctx: Context): Boolean = + self.isTerm && !self.is(Method) + + def implClass(implicit ctx: Context): Symbol = + self.owner.info.decl(self.name.implClassName).symbol + + def annotationsCarrying(meta: ClassSymbol)(implicit ctx: Context): List[Annotation] = + self.annotations.filter(_.symbol.hasAnnotation(meta)) + + def withAnnotationsCarrying(from: Symbol, meta: ClassSymbol)(implicit ctx: Context): self.type = { + self.addAnnotations(from.annotationsCarrying(meta)) + self + } + + def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context) = { + if (!self.unforcedDecls.lookup(name).exists) { + val companionMethod = ctx.synthesizeCompanionMethod(name, target, self) + if (companionMethod.exists) { + companionMethod.entered + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala new file mode 100644 index 000000000..9dfd92fe9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -0,0 +1,198 @@ +package dotty.tools.dotc +package transform + +import core._ +import Symbols._, Types._, Contexts._, Names._, StdNames._, Constants._, SymUtils._ +import scala.collection.{ mutable, immutable } +import Flags._ +import TreeTransforms._ +import DenotTransformers._ +import ast.Trees._ +import ast.untpd +import Decorators._ +import NameOps._ +import ValueClasses.isDerivedValueClass +import scala.collection.mutable.ListBuffer +import scala.language.postfixOps + +/** Synthetic method implementations for case classes, case objects, + * and value classes. + * Selectively added to case classes/objects, unless a non-default + * implementation already exists: + * def equals(other: Any): Boolean + * def hashCode(): Int + * def canEqual(other: Any): Boolean + * def toString(): String + * def productArity: Int + * def productPrefix: String + * Special handling: + * protected def readResolve(): AnyRef + * + * Selectively added to value classes, unless a non-default + * implementation already exists: + * + * def equals(other: Any): Boolean + * def hashCode(): Int + */ +class SyntheticMethods(thisTransformer: DenotTransformer) { + import ast.tpd._ + + private var myValueSymbols: List[Symbol] = Nil + private var myCaseSymbols: List[Symbol] = Nil + + private def initSymbols(implicit ctx: Context) = + if (myValueSymbols.isEmpty) { + myValueSymbols = List(defn.Any_hashCode, defn.Any_equals) + myCaseSymbols = myValueSymbols ++ List(defn.Any_toString, defn.Product_canEqual, + defn.Product_productArity, defn.Product_productPrefix) + } + + def valueSymbols(implicit ctx: Context) = { initSymbols; myValueSymbols } + def caseSymbols(implicit ctx: Context) = { initSymbols; myCaseSymbols } + + /** The synthetic methods of the case or value class `clazz`. + */ + def syntheticMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = { + val clazzType = clazz.typeRef + lazy val accessors = + if (isDerivedValueClass(clazz)) + clazz.termParamAccessors + else + clazz.caseAccessors + + val symbolsToSynthesize: List[Symbol] = + if (clazz.is(Case)) caseSymbols + else if (isDerivedValueClass(clazz)) valueSymbols + else Nil + + def syntheticDefIfMissing(sym: Symbol): List[Tree] = { + val existing = sym.matchingMember(clazz.thisType) + if (existing == sym || existing.is(Deferred)) syntheticDef(sym) :: Nil + else Nil + } + + def syntheticDef(sym: Symbol): Tree = { + val synthetic = sym.copy( + owner = clazz, + flags = sym.flags &~ Deferred | Synthetic | Override, + coord = clazz.coord).enteredAfter(thisTransformer).asTerm + + def forwardToRuntime(vrefss: List[List[Tree]]): Tree = + ref(defn.runtimeMethodRef("_" + sym.name.toString)).appliedToArgs(This(clazz) :: vrefss.head) + + def ownName(vrefss: List[List[Tree]]): Tree = + Literal(Constant(clazz.name.stripModuleClassSuffix.decode.toString)) + + def syntheticRHS(implicit ctx: Context): List[List[Tree]] => Tree = synthetic.name match { + case nme.hashCode_ if isDerivedValueClass(clazz) => vrefss => valueHashCodeBody + case nme.hashCode_ => vrefss => caseHashCodeBody + case nme.toString_ => if (clazz.is(ModuleClass)) ownName else forwardToRuntime + case nme.equals_ => vrefss => equalsBody(vrefss.head.head) + case nme.canEqual_ => vrefss => canEqualBody(vrefss.head.head) + case nme.productArity => vrefss => Literal(Constant(accessors.length)) + case nme.productPrefix => ownName + } + ctx.log(s"adding $synthetic to $clazz at ${ctx.phase}") + DefDef(synthetic, syntheticRHS(ctx.withOwner(synthetic))) + } + + /** The class + * + * case class C(x: T, y: U) + * + * gets the equals method: + * + * def equals(that: Any): Boolean = + * (this eq that) || { + * that match { + * case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y + * case _ => false + * } + * + * If C is a value class the initial `eq` test is omitted. + */ + def equalsBody(that: Tree)(implicit ctx: Context): Tree = { + val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.pos) // x$0 + def wildcardAscription(tp: Type) = Typed(Underscore(tp), TypeTree(tp)) + val pattern = Bind(thatAsClazz, wildcardAscription(clazzType)) // x$0 @ (_: C) + val comparisons = accessors map (accessor => + This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor))) + val rhs = // this.x == this$0.x && this.y == x$0.y + if (comparisons.isEmpty) Literal(Constant(true)) else comparisons.reduceLeft(_ and _) + val matchingCase = CaseDef(pattern, EmptyTree, rhs) // case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y + val defaultCase = CaseDef(wildcardAscription(defn.AnyType), EmptyTree, Literal(Constant(false))) // case _ => false + val matchExpr = Match(that, List(matchingCase, defaultCase)) + if (isDerivedValueClass(clazz)) matchExpr + else { + val eqCompare = This(clazz).select(defn.Object_eq).appliedTo(that.asInstance(defn.ObjectType)) + eqCompare or matchExpr + } + } + + /** The class + * + * class C(x: T) extends AnyVal + * + * gets the hashCode method: + * + * def hashCode: Int = x.hashCode() + */ + def valueHashCodeBody(implicit ctx: Context): Tree = { + assert(accessors.length == 1) + ref(accessors.head).select(nme.hashCode_).ensureApplied + } + + /** The class + * + * case class C(x: T, y: T) + * + * gets the hashCode method: + * + * def hashCode: Int = { + * <synthetic> var acc: Int = 0xcafebabe; + * acc = Statics.mix(acc, x); + * acc = Statics.mix(acc, Statics.this.anyHash(y)); + * Statics.finalizeHash(acc, 2) + * } + */ + def caseHashCodeBody(implicit ctx: Context): Tree = { + val acc = ctx.newSymbol(ctx.owner, "acc".toTermName, Mutable | Synthetic, defn.IntType, coord = ctx.owner.pos) + val accDef = ValDef(acc, Literal(Constant(0xcafebabe))) + val mixes = for (accessor <- accessors.toList) yield + Assign(ref(acc), ref(defn.staticsMethod("mix")).appliedTo(ref(acc), hashImpl(accessor))) + val finish = ref(defn.staticsMethod("finalizeHash")).appliedTo(ref(acc), Literal(Constant(accessors.size))) + Block(accDef :: mixes, finish) + } + + /** The hashCode implementation for given symbol `sym`. */ + def hashImpl(sym: Symbol)(implicit ctx: Context): Tree = + defn.scalaClassName(sym.info.finalResultType) match { + case tpnme.Unit | tpnme.Null => Literal(Constant(0)) + case tpnme.Boolean => If(ref(sym), Literal(Constant(1231)), Literal(Constant(1237))) + case tpnme.Int => ref(sym) + case tpnme.Short | tpnme.Byte | tpnme.Char => ref(sym).select(nme.toInt) + case tpnme.Long => ref(defn.staticsMethod("longHash")).appliedTo(ref(sym)) + case tpnme.Double => ref(defn.staticsMethod("doubleHash")).appliedTo(ref(sym)) + case tpnme.Float => ref(defn.staticsMethod("floatHash")).appliedTo(ref(sym)) + case _ => ref(defn.staticsMethod("anyHash")).appliedTo(ref(sym)) + } + + /** The class + * + * case class C(...) + * + * gets the canEqual method + * + * def canEqual(that: Any) = that.isInstanceOf[C] + */ + def canEqualBody(that: Tree): Tree = that.isInstance(clazzType) + + symbolsToSynthesize flatMap syntheticDefIfMissing + } + + def addSyntheticMethods(impl: Template)(implicit ctx: Context) = + if (ctx.owner.is(Case) || isDerivedValueClass(ctx.owner)) + cpy.Template(impl)(body = impl.body ++ syntheticMethods(ctx.owner.asClass)) + else + impl +} diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala new file mode 100644 index 000000000..dc4454439 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -0,0 +1,384 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{TreeTypeMap, tpd} +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core._ +import dotty.tools.dotc.transform.TailRec._ +import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo} + +/** + * A Tail Rec Transformer + * @author Erik Stenman, Iulian Dragos, + * ported and heavily modified for dotty by Dmitry Petrashko + * @version 1.1 + * + * What it does: + * <p> + * Finds method calls in tail-position and replaces them with jumps. + * A call is in a tail-position if it is the last instruction to be + * executed in the body of a method. This is done by recursing over + * the trees that may contain calls in tail-position (trees that can't + * contain such calls are not transformed). However, they are not that + * many. + * </p> + * <p> + * Self-recursive calls in tail-position are replaced by jumps to a + * label at the beginning of the method. As the JVM provides no way to + * jump from a method to another one, non-recursive calls in + * tail-position are not optimized. + * </p> + * <p> + * A method call is self-recursive if it calls the current method and + * the method is final (otherwise, it could + * be a call to an overridden method in a subclass). + * + * Recursive calls on a different instance + * are optimized. Since 'this' is not a local variable it s added as + * a label parameter. + * </p> + * <p> + * This phase has been moved before pattern matching to catch more + * of the common cases of tail recursive functions. This means that + * more cases should be taken into account (like nested function, and + * pattern cases). + * </p> + * <p> + * If a method contains self-recursive calls, a label is added to at + * the beginning of its body and the calls are replaced by jumps to + * that label. + * </p> + * <p> + * + * In scalac, If the method had type parameters, the call must contain same + * parameters as type arguments. This is no longer case in dotc. + * In scalac, this is named tailCall but it does only provide optimization for + * self recursive functions, that's why it's renamed to tailrec + * </p> + */ +class TailRec extends MiniPhaseTransform with DenotTransformer with FullParameterization { thisTransform => + + import dotty.tools.dotc.ast.tpd._ + + override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref + + override def phaseName: String = "tailrec" + override def treeTransformPhase = thisTransform // TODO Make sure tailrec runs at next phase. + + final val labelPrefix = "tailLabel" + final val labelFlags = Flags.Synthetic | Flags.Label + + /** Symbols of methods that have @tailrec annotatios inside */ + private val methodsWithInnerAnnots = new collection.mutable.HashSet[Symbol]() + + override def transformUnit(tree: Tree)(implicit ctx: Context, info: TransformerInfo): Tree = { + methodsWithInnerAnnots.clear() + tree + } + + override def transformTyped(tree: Typed)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (tree.tpt.tpe.hasAnnotation(defn.TailrecAnnot)) + methodsWithInnerAnnots += ctx.owner.enclosingMethod + tree + } + + private def mkLabel(method: Symbol, abstractOverClass: Boolean)(implicit c: Context): TermSymbol = { + val name = c.freshName(labelPrefix) + + if (method.owner.isClass) + c.newSymbol(method, name.toTermName, labelFlags, fullyParameterizedType(method.info, method.enclosingClass.asClass, abstractOverClass, liftThisType = false)) + else c.newSymbol(method, name.toTermName, labelFlags, method.info) + } + + override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val sym = tree.symbol + tree match { + case dd@DefDef(name, tparams, vparamss0, tpt, _) + if (sym.isEffectivelyFinal) && !((sym is Flags.Accessor) || (dd.rhs eq EmptyTree) || (sym is Flags.Label)) => + val mandatory = sym.hasAnnotation(defn.TailrecAnnot) + atGroupEnd { implicit ctx: Context => + + cpy.DefDef(dd)(rhs = { + + val defIsTopLevel = sym.owner.isClass + val origMeth = sym + val label = mkLabel(sym, abstractOverClass = defIsTopLevel) + val owner = ctx.owner.enclosingClass.asClass + val thisTpe = owner.thisType.widen + + var rewrote = false + + // Note: this can be split in two separate transforms(in different groups), + // than first one will collect info about which transformations and rewritings should be applied + // and second one will actually apply, + // now this speculatively transforms tree and throws away result in many cases + val rhsSemiTransformed = { + val transformer = new TailRecElimination(origMeth, dd.tparams, owner, thisTpe, mandatory, label, abstractOverClass = defIsTopLevel) + val rhs = atGroupEnd(transformer.transform(dd.rhs)(_)) + rewrote = transformer.rewrote + rhs + } + + if (rewrote) { + val dummyDefDef = cpy.DefDef(tree)(rhs = rhsSemiTransformed) + if (tree.symbol.owner.isClass) { + val labelDef = fullyParameterizedDef(label, dummyDefDef, abstractOverClass = defIsTopLevel) + val call = forwarder(label, dd, abstractOverClass = defIsTopLevel, liftThisType = true) + Block(List(labelDef), call) + } else { // inner method. Tail recursion does not change `this` + val labelDef = polyDefDef(label, trefs => vrefss => { + val origMeth = tree.symbol + val origTParams = tree.tparams.map(_.symbol) + val origVParams = tree.vparamss.flatten map (_.symbol) + new TreeTypeMap( + typeMap = identity(_) + .substDealias(origTParams, trefs) + .subst(origVParams, vrefss.flatten.map(_.tpe)), + oldOwners = origMeth :: Nil, + newOwners = label :: Nil + ).transform(rhsSemiTransformed) + }) + val callIntoLabel = ( + if (dd.tparams.isEmpty) ref(label) + else ref(label).appliedToTypes(dd.tparams.map(_.tpe)) + ).appliedToArgss(vparamss0.map(_.map(x=> ref(x.symbol)))) + Block(List(labelDef), callIntoLabel) + }} else { + if (mandatory) ctx.error( + "TailRec optimisation not applicable, method not tail recursive", + // FIXME: want to report this error on `dd.namePos`, but + // because of extension method getting a weird pos, it is + // better to report on symbol so there's no overlap + sym.pos + ) + dd.rhs + } + }) + } + case d: DefDef if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => + ctx.error("TailRec optimisation not applicable, method is neither private nor final so can be overridden", sym.pos) + d + case d if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => + ctx.error("TailRec optimisation not applicable, not a method", sym.pos) + d + case _ => tree + } + + } + + class TailRecElimination(method: Symbol, methTparams: List[Tree], enclosingClass: Symbol, thisType: Type, isMandatory: Boolean, label: Symbol, abstractOverClass: Boolean) extends tpd.TreeMap { + + import dotty.tools.dotc.ast.tpd._ + + var rewrote = false + + private val defaultReason = "it contains a recursive call not in tail position" + + private var ctx: TailContext = yesTailContext + + /** Rewrite this tree to contain no tail recursive calls */ + def transform(tree: Tree, nctx: TailContext)(implicit c: Context): Tree = { + if (ctx == nctx) transform(tree) + else { + val saved = ctx + ctx = nctx + try transform(tree) + finally this.ctx = saved + } + } + + def yesTailTransform(tree: Tree)(implicit c: Context): Tree = + transform(tree, yesTailContext) + + def noTailTransform(tree: Tree)(implicit c: Context): Tree = + transform(tree, noTailContext) + + def noTailTransforms[Tr <: Tree](trees: List[Tr])(implicit c: Context): List[Tr] = + trees.map(noTailTransform).asInstanceOf[List[Tr]] + + override def transform(tree: Tree)(implicit c: Context): Tree = { + /* A possibly polymorphic apply to be considered for tail call transformation. */ + def rewriteApply(tree: Tree, sym: Symbol, required: Boolean = false): Tree = { + def receiverArgumentsAndSymbol(t: Tree, accArgs: List[List[Tree]] = Nil, accT: List[Tree] = Nil): + (Tree, Tree, List[List[Tree]], List[Tree], Symbol) = t match { + case TypeApply(fun, targs) if fun.symbol eq t.symbol => receiverArgumentsAndSymbol(fun, accArgs, targs) + case Apply(fn, args) if fn.symbol == t.symbol => receiverArgumentsAndSymbol(fn, args :: accArgs, accT) + case Select(qual, _) => (qual, t, accArgs, accT, t.symbol) + case x: This => (x, x, accArgs, accT, x.symbol) + case x: Ident if x.symbol eq method => (EmptyTree, x, accArgs, accT, x.symbol) + case x => (x, x, accArgs, accT, x.symbol) + } + + val (prefix, call, arguments, typeArguments, symbol) = receiverArgumentsAndSymbol(tree) + val hasConformingTargs = (typeArguments zip methTparams).forall{x => x._1.tpe <:< x._2.tpe} + val recv = noTailTransform(prefix) + + val targs = typeArguments.map(noTailTransform) + val argumentss = arguments.map(noTailTransforms) + + val recvWiden = recv.tpe.widenDealias + + val receiverIsSame = enclosingClass.typeRef.widenDealias =:= recvWiden + val receiverIsSuper = (method.name eq sym) && enclosingClass.typeRef.widen <:< recvWiden + val receiverIsThis = recv.tpe =:= thisType || recv.tpe.widen =:= thisType + + val isRecursiveCall = (method eq sym) + + def continue = { + val method = noTailTransform(call) + val methodWithTargs = if (targs.nonEmpty) TypeApply(method, targs) else method + if (methodWithTargs.tpe.widen.isParameterless) methodWithTargs + else argumentss.foldLeft(methodWithTargs) { + // case (method, args) => Apply(method, args) // Dotty deviation no auto-detupling yet. Interesting that one can do it in Scala2! + (method, args) => Apply(method, args) + } + } + def fail(reason: String) = { + if (isMandatory || required) c.error(s"Cannot rewrite recursive call: $reason", tree.pos) + else c.debuglog("Cannot rewrite recursive call at: " + tree.pos + " because: " + reason) + continue + } + + def rewriteTailCall(recv: Tree): Tree = { + c.debuglog("Rewriting tail recursive call: " + tree.pos) + rewrote = true + val receiver = noTailTransform(recv) + + val callTargs: List[tpd.Tree] = + if (abstractOverClass) { + val classTypeArgs = recv.tpe.baseTypeWithArgs(enclosingClass).argInfos + targs ::: classTypeArgs.map(x => ref(x.typeSymbol)) + } else targs + + val method = if (callTargs.nonEmpty) TypeApply(Ident(label.termRef), callTargs) else Ident(label.termRef) + val thisPassed = + if (this.method.owner.isClass) + method.appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head)) + else method + + val res = + if (thisPassed.tpe.widen.isParameterless) thisPassed + else argumentss.foldLeft(thisPassed) { + (met, ar) => Apply(met, ar) // Dotty deviation no auto-detupling yet. + } + res + } + + if (isRecursiveCall) { + if (ctx.tailPos) { + if (!hasConformingTargs) fail("it changes type arguments on a polymorphic recursive call") + else if (recv eq EmptyTree) rewriteTailCall(This(enclosingClass.asClass)) + else if (receiverIsSame || receiverIsThis) rewriteTailCall(recv) + else fail("it changes type of 'this' on a polymorphic recursive call") + } + else fail(defaultReason) + } else { + if (receiverIsSuper) fail("it contains a recursive call targeting a supertype") + else continue + } + } + + def rewriteTry(tree: Try): Try = { + if (tree.finalizer eq EmptyTree) { + // SI-1672 Catches are in tail position when there is no finalizer + tpd.cpy.Try(tree)( + noTailTransform(tree.expr), + transformSub(tree.cases), + EmptyTree + ) + } + else { + tpd.cpy.Try(tree)( + noTailTransform(tree.expr), + noTailTransforms(tree.cases), + noTailTransform(tree.finalizer) + ) + } + } + + val res: Tree = tree match { + + case Ident(qual) => + val sym = tree.symbol + if (sym == method && ctx.tailPos) rewriteApply(tree, sym) + else tree + + case tree: Select => + val sym = tree.symbol + if (sym == method && ctx.tailPos) rewriteApply(tree, sym) + else tpd.cpy.Select(tree)(noTailTransform(tree.qualifier), tree.name) + + case Apply(fun, args) => + val meth = fun.symbol + if (meth == defn.Boolean_|| || meth == defn.Boolean_&&) + tpd.cpy.Apply(tree)(fun, transform(args)) + else + rewriteApply(tree, meth) + + case tree@Block(stats, expr) => + tpd.cpy.Block(tree)( + noTailTransforms(stats), + transform(expr) + ) + case tree @ Typed(t: Apply, tpt) if tpt.tpe.hasAnnotation(defn.TailrecAnnot) => + tpd.Typed(rewriteApply(t, t.fun.symbol, required = true), tpt) + case tree@If(cond, thenp, elsep) => + tpd.cpy.If(tree)( + noTailTransform(cond), + transform(thenp), + transform(elsep) + ) + + case tree@CaseDef(_, _, body) => + cpy.CaseDef(tree)(body = transform(body)) + + case tree@Match(selector, cases) => + tpd.cpy.Match(tree)( + noTailTransform(selector), + transformSub(cases) + ) + + case tree: Try => + rewriteTry(tree) + + case Alternative(_) | Bind(_, _) => + assert(false, "We should never have gotten inside a pattern") + tree + + case t @ DefDef(_, _, _, _, _) => + t // todo: could improve to handle DefDef's with a label flag calls to which are in tail position + + case ValDef(_, _, _) | EmptyTree | Super(_, _) | This(_) | + Literal(_) | TypeTree() | TypeDef(_, _) => + tree + + case Return(expr, from) => + tpd.cpy.Return(tree)(noTailTransform(expr), from) + + case _ => + super.transform(tree) + } + + res + } + } + + /** If references to original `target` from fully parameterized method `derived` should be + * rewired to some fully parameterized method, that method symbol, + * otherwise NoSymbol. + */ + override protected def rewiredTarget(target: Symbol, derived: Symbol)(implicit ctx: Context): Symbol = NoSymbol +} + +object TailRec { + + final class TailContext(val tailPos: Boolean) extends AnyVal + + final val noTailContext = new TailContext(false) + final val yesTailContext = new TailContext(true) +} diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala new file mode 100644 index 000000000..4a09d2fef --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -0,0 +1,452 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.Names.Name +import core.DenotTransformers._ +import core.Denotations._ +import core.SymDenotations._ +import core.Contexts._ +import core.Symbols._ +import core.Types._ +import core.Flags._ +import core.Constants._ +import core.StdNames._ +import core.Decorators._ +import core.TypeErasure.isErasedType +import core.Phases.Phase +import core.Mode +import typer._ +import typer.ErrorReporting._ +import reporting.ThrowingReporter +import ast.Trees._ +import ast.{tpd, untpd} +import util.SourcePosition +import collection.mutable +import ProtoTypes._ +import config.Printers +import java.lang.AssertionError + +import dotty.tools.dotc.core.Names + +import scala.util.control.NonFatal + +/** Run by -Ycheck option after a given phase, this class retypes all syntax trees + * and verifies that the type of each tree node so obtained conforms to the type found in the tree node. + * It also performs the following checks: + * + * - The owner of each definition is the same as the owner of the current typing context. + * - Ident nodes do not refer to a denotation that would need a select to be accessible + * (see tpd.needsSelect). + * - After typer, identifiers and select nodes refer to terms only (all types should be + * represented as TypeTrees then). + */ +class TreeChecker extends Phase with SymTransformer { + import ast.tpd._ + + + private val seenClasses = collection.mutable.HashMap[String, Symbol]() + private val seenModuleVals = collection.mutable.HashMap[String, Symbol]() + + def isValidJVMName(name: Name) = + !name.exists(c => c == '.' || c == ';' || c =='[' || c == '/') + + def isValidJVMMethodName(name: Name) = + !name.exists(c => c == '.' || c == ';' || c =='[' || c == '/' || c == '<' || c == '>') + + def printError(str: String)(implicit ctx: Context) = { + ctx.echo(Console.RED + "[error] " + Console.WHITE + str) + } + + val NoSuperClass = Trait | Package + + def testDuplicate(sym: Symbol, registry: mutable.Map[String, Symbol], typ: String)(implicit ctx: Context) = { + val name = sym.fullName.toString + if (this.flatClasses && registry.contains(name)) + printError(s"$typ defined twice $sym ${sym.id} ${registry(name).id}") + registry(name) = sym + } + + def checkCompanion(symd: SymDenotation)(implicit ctx: Context): Unit = { + val cur = symd.linkedClass + val prev = ctx.atPhase(ctx.phase.prev) { implicit ctx => + symd.symbol.linkedClass + } + + if (prev.exists) + assert(cur.exists, i"companion disappeared from $symd") + } + + def transformSym(symd: SymDenotation)(implicit ctx: Context): SymDenotation = { + val sym = symd.symbol + + if (sym.isClass && !sym.isAbsent) { + val validSuperclass = sym.isPrimitiveValueClass || defn.syntheticCoreClasses.contains(sym) || + (sym eq defn.ObjectClass) || (sym is NoSuperClass) || (sym.asClass.superClass.exists) + if (!validSuperclass) + printError(s"$sym has no superclass set") + + testDuplicate(sym, seenClasses, "class") + } + + if (sym.is(Method) && sym.is(Deferred) && sym.is(Private)) + assert(false, s"$sym is both Deferred and Private") + + checkCompanion(symd) + + symd + } + + def phaseName: String = "Ycheck" + + def run(implicit ctx: Context): Unit = { + check(ctx.allPhases, ctx) + } + + private def previousPhases(phases: List[Phase])(implicit ctx: Context): List[Phase] = phases match { + case (phase: TreeTransformer) :: phases1 => + val subPhases = phase.miniPhases + val previousSubPhases = previousPhases(subPhases.toList) + if (previousSubPhases.length == subPhases.length) previousSubPhases ::: previousPhases(phases1) + else previousSubPhases + case phase :: phases1 if phase ne ctx.phase => + phase :: previousPhases(phases1) + case _ => + Nil + } + + def check(phasesToRun: Seq[Phase], ctx: Context) = { + val prevPhase = ctx.phase.prev // can be a mini-phase + val squahsedPhase = ctx.squashed(prevPhase) + ctx.echo(s"checking ${ctx.compilationUnit} after phase ${squahsedPhase}") + + val checkingCtx = ctx + .fresh + .setMode(Mode.ImplicitsEnabled) + .setReporter(new ThrowingReporter(ctx.reporter)) + + val checker = new Checker(previousPhases(phasesToRun.toList)(ctx)) + try checker.typedExpr(ctx.compilationUnit.tpdTree)(checkingCtx) + catch { + case NonFatal(ex) => //TODO CHECK. Check that we are bootstrapped + implicit val ctx: Context = checkingCtx + println(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***") + throw ex + } + } + + class Checker(phasesToCheck: Seq[Phase]) extends ReTyper { + + val nowDefinedSyms = new mutable.HashSet[Symbol] + val everDefinedSyms = new mutable.HashMap[Symbol, Tree] + + def withDefinedSym[T](tree: untpd.Tree)(op: => T)(implicit ctx: Context): T = tree match { + case tree: DefTree => + val sym = tree.symbol + assert(isValidJVMName(sym.name), s"${sym.fullName} name is invalid on jvm") + everDefinedSyms.get(sym) match { + case Some(t) => + if (t ne tree) + ctx.warning(i"symbol ${sym.fullName} is defined at least twice in different parts of AST") + // should become an error + case None => + everDefinedSyms(sym) = tree + } + assert(!nowDefinedSyms.contains(sym), i"doubly defined symbol: ${sym.fullName} in $tree") + + if (ctx.settings.YcheckMods.value) { + tree match { + case t: MemberDef => + if (t.name ne sym.name) ctx.warning(s"symbol ${sym.fullName} name doesn't correspond to AST: ${t}") + // todo: compare trees inside annotations + case _ => + } + } + + nowDefinedSyms += tree.symbol + //ctx.echo(i"defined: ${tree.symbol}") + val res = op + nowDefinedSyms -= tree.symbol + //ctx.echo(i"undefined: ${tree.symbol}") + res + case _ => op + } + + def withDefinedSyms[T](trees: List[untpd.Tree])(op: => T)(implicit ctx: Context) = + trees.foldRightBN(op)(withDefinedSym(_)(_)) + + def withDefinedSymss[T](vparamss: List[List[untpd.ValDef]])(op: => T)(implicit ctx: Context): T = + vparamss.foldRightBN(op)(withDefinedSyms(_)(_)) + + def assertDefined(tree: untpd.Tree)(implicit ctx: Context) = + if (tree.symbol.maybeOwner.isTerm) + assert(nowDefinedSyms contains tree.symbol, i"undefined symbol ${tree.symbol}") + + /** assert Java classes are not used as objects */ + def assertIdentNotJavaClass(tree: Tree)(implicit ctx: Context): Unit = tree match { + case _ : untpd.Ident => + assert(!tree.symbol.is(JavaModule), "Java class can't be used as value: " + tree) + case _ => + } + + /** check Java classes are not used as objects */ + def checkIdentNotJavaClass(tree: Tree)(implicit ctx: Context): Unit = tree match { + // case tree: untpd.Ident => + // case tree: untpd.Select => + // case tree: untpd.Bind => + case vd : ValDef => + assertIdentNotJavaClass(vd.forceIfLazy) + case dd : DefDef => + assertIdentNotJavaClass(dd.forceIfLazy) + // case tree: untpd.TypeDef => + case Apply(fun, args) => + assertIdentNotJavaClass(fun) + args.foreach(assertIdentNotJavaClass _) + // case tree: untpd.This => + // case tree: untpd.Literal => + // case tree: untpd.New => + case Typed(expr, _) => + assertIdentNotJavaClass(expr) + case NamedArg(_, arg) => + assertIdentNotJavaClass(arg) + case Assign(_, rhs) => + assertIdentNotJavaClass(rhs) + case Block(stats, expr) => + stats.foreach(assertIdentNotJavaClass _) + assertIdentNotJavaClass(expr) + case If(_, thenp, elsep) => + assertIdentNotJavaClass(thenp) + assertIdentNotJavaClass(elsep) + // case tree: untpd.Closure => + case Match(selector, cases) => + assertIdentNotJavaClass(selector) + cases.foreach(caseDef => assertIdentNotJavaClass(caseDef.body)) + case Return(expr, _) => + assertIdentNotJavaClass(expr) + case Try(expr, cases, finalizer) => + assertIdentNotJavaClass(expr) + cases.foreach(caseDef => assertIdentNotJavaClass(caseDef.body)) + assertIdentNotJavaClass(finalizer) + // case tree: TypeApply => + // case tree: Super => + case SeqLiteral(elems, _) => + elems.foreach(assertIdentNotJavaClass) + // case tree: TypeTree => + // case tree: SingletonTypeTree => + // case tree: AndTypeTree => + // case tree: OrTypeTree => + // case tree: RefinedTypeTree => + // case tree: AppliedTypeTree => + // case tree: ByNameTypeTree => + // case tree: TypeBoundsTree => + // case tree: Alternative => + // case tree: PackageDef => + case Annotated(arg, _) => + assertIdentNotJavaClass(arg) + case _ => + } + + override def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = { + val tpdTree = super.typed(tree, pt) + checkIdentNotJavaClass(tpdTree) + tpdTree + } + + override def typedUnadapted(tree: untpd.Tree, pt: Type)(implicit ctx: Context): tpd.Tree = { + val res = tree match { + case _: untpd.UnApply => + // can't recheck patterns + tree.asInstanceOf[tpd.Tree] + case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[_] => + super.typedUnadapted(tree) + case _ if tree.isType => + promote(tree) + case _ => + val tree1 = super.typedUnadapted(tree, pt) + def isSubType(tp1: Type, tp2: Type) = + (tp1 eq tp2) || // accept NoType / NoType + (tp1 <:< tp2) + def divergenceMsg(tp1: Type, tp2: Type) = + s"""Types differ + |Original type : ${tree.typeOpt.show} + |After checking: ${tree1.tpe.show} + |Original tree : ${tree.show} + |After checking: ${tree1.show} + |Why different : + """.stripMargin + core.TypeComparer.explained((tp1 <:< tp2)(_)) + if (tree.hasType) // it might not be typed because Typer sometimes constructs new untyped trees and resubmits them to typedUnadapted + assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt)) + tree1 + } + checkNoOrphans(res.tpe) + phasesToCheck.foreach(_.checkPostCondition(res)) + res + } + + /** Check that PolyParams and MethodParams refer to an enclosing type */ + def checkNoOrphans(tp: Type)(implicit ctx: Context) = new TypeMap() { + val definedBinders = mutable.Set[Type]() + def apply(tp: Type): Type = { + tp match { + case tp: BindingType => + definedBinders += tp + mapOver(tp) + definedBinders -= tp + case tp: ParamType => + assert(definedBinders.contains(tp.binder), s"orphan param: $tp") + case tp: TypeVar => + apply(tp.underlying) + case _ => + mapOver(tp) + } + tp + } + }.apply(tp) + + def checkNotRepeated(tree: Tree)(implicit ctx: Context): tree.type = { + def allowedRepeated = (tree.symbol.flags is Case) && tree.tpe.widen.isRepeatedParam + + assert(!tree.tpe.widen.isRepeatedParam || allowedRepeated, i"repeated parameter type not allowed here: $tree") + tree + } + + /** Check that all methods have MethodicType */ + def isMethodType(pt: Type)(implicit ctx: Context): Boolean = pt match { + case at: AnnotatedType => isMethodType(at.tpe) + case _: MethodicType => true // MethodType, ExprType, PolyType + case _ => false + } + + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase) + assert(tree.isType || !needsSelect(tree.tpe), i"bad type ${tree.tpe} for $tree # ${tree.uniqueId}") + assertDefined(tree) + + checkNotRepeated(super.typedIdent(tree, pt)) + } + + /** Makes sure the symbol in the tree can be approximately reconstructed by + * calling `member` on the qualifier type. + * Approximately means: The two symbols might be different but one still overrides the other. + */ + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase) + val tpe = tree.typeOpt + val sym = tree.symbol + if (!tpe.isInstanceOf[WithFixedSym] && sym.exists && !sym.is(Private)) { + val qualTpe = tree.qualifier.typeOpt + val member = + if (sym.is(Private)) qualTpe.member(tree.name) + else qualTpe.nonPrivateMember(tree.name) + val memberSyms = member.alternatives.map(_.symbol) + assert(memberSyms.exists(mbr => + sym == mbr || + sym.overriddenSymbol(mbr.owner.asClass) == mbr || + mbr.overriddenSymbol(sym.owner.asClass) == sym), + ex"""symbols differ for $tree + |was : $sym + |alternatives by type: $memberSyms%, % of types ${memberSyms.map(_.info)}%, % + |qualifier type : ${tree.qualifier.typeOpt} + |tree type : ${tree.typeOpt} of class ${tree.typeOpt.getClass}""") + } + checkNotRepeated(super.typedSelect(tree, pt)) + } + + override def typedThis(tree: untpd.This)(implicit ctx: Context) = { + val res = super.typedThis(tree) + val cls = res.symbol + assert(cls.isStaticOwner || ctx.owner.isContainedIn(cls), i"error while typing $tree, ${ctx.owner} is not contained in $cls") + res + } + + private def checkOwner(tree: untpd.Tree)(implicit ctx: Context): Unit = { + def ownerMatches(symOwner: Symbol, ctxOwner: Symbol): Boolean = + symOwner == ctxOwner || + ctxOwner.isWeakOwner && ownerMatches(symOwner, ctxOwner.owner) || + ctx.phase.labelsReordered && symOwner.isWeakOwner && ownerMatches(symOwner.owner, ctxOwner) + assert(ownerMatches(tree.symbol.owner, ctx.owner), + i"bad owner; ${tree.symbol} has owner ${tree.symbol.owner}, expected was ${ctx.owner}\n" + + i"owner chain = ${tree.symbol.ownersIterator.toList}%, %, ctxOwners = ${ctx.outersIterator.map(_.owner).toList}%, %") + } + + override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(implicit ctx: Context) = { + val TypeDef(_, impl @ Template(constr, _, _, _)) = cdef + assert(cdef.symbol == cls) + assert(impl.symbol.owner == cls) + assert(constr.symbol.owner == cls) + assert(cls.primaryConstructor == constr.symbol, i"mismatch, primary constructor ${cls.primaryConstructor}, in tree = ${constr.symbol}") + checkOwner(impl) + checkOwner(impl.constr) + + def isNonMagicalMethod(x: Symbol) = + x.is(Method) && + !x.isCompanionMethod && + !x.isValueClassConvertMethod + + val symbolsNotDefined = cls.classInfo.decls.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol + + assert(symbolsNotDefined.isEmpty, + i" $cls tree does not define methods: ${symbolsNotDefined.toList}%, %\n" + + i"expected: ${cls.classInfo.decls.toSet.filter(isNonMagicalMethod).toList}%, %\n" + + i"defined: ${impl.body.map(_.symbol)}%, %") + + super.typedClassDef(cdef, cls) + } + + override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = + withDefinedSyms(ddef.tparams) { + withDefinedSymss(ddef.vparamss) { + if (!sym.isClassConstructor && !(sym.name eq Names.STATIC_CONSTRUCTOR)) assert(isValidJVMMethodName(sym.name), s"${sym.fullName} name is invalid on jvm") + val tpdTree = super.typedDefDef(ddef, sym) + assert(isMethodType(sym.info), i"wrong type, expect a method type for ${sym.fullName}, but found: ${sym.info}") + tpdTree + } + } + + override def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = { + withDefinedSyms(tree.pat.asInstanceOf[tpd.Tree].filterSubTrees(_.isInstanceOf[ast.Trees.Bind[_]])) { + super.typedCase(tree, pt, selType, gadtSyms) + } + } + + override def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = + withDefinedSyms(tree.stats) { super.typedBlock(tree, pt) } + + override def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context) = + withDefinedSyms(tree.bindings) { super.typedInlined(tree, pt) } + + /** Check that all defined symbols have legal owners. + * An owner is legal if it is either the same as the context's owner + * or there's an owner chain of valdefs starting at the context's owner and + * reaching up to the symbol's owner. The reason for this relaxed matching + * is that we should be able to pull out an expression as an initializer + * of a helper value without having to do a change owner traversal of the expression. + */ + override def typedStats(trees: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + for (tree <- trees) tree match { + case tree: untpd.DefTree => checkOwner(tree) + case _: untpd.Thicket => assert(false, i"unexpanded thicket $tree in statement sequence $trees%\n%") + case _ => + } + super.typedStats(trees, exprOwner) + } + + override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol], forcedDefined: Boolean = false)(implicit ctx: Context): Tree = + tree + + override def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = { + def isPrimaryConstructorReturn = + ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass) + if (ctx.mode.isExpr && + !tree.isEmpty && + !isPrimaryConstructorReturn && + !pt.isInstanceOf[FunProto]) + assert(tree.tpe <:< pt, { + val mismatch = err.typeMismatchMsg(tree.tpe, pt) + i"""|${mismatch.msg} + |tree = $tree""".stripMargin + }) + tree + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/TreeExtractors.scala b/compiler/src/dotty/tools/dotc/transform/TreeExtractors.scala new file mode 100644 index 000000000..7a5c5df9d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TreeExtractors.scala @@ -0,0 +1,48 @@ +package dotty.tools.dotc +package transform + +import ast.{Trees, tpd} +import core._, core.Decorators._ +import Contexts._, Flags._, Trees._, Types._, StdNames._, Symbols._ +import ValueClasses._ + +object TreeExtractors { + import tpd._ + + /** Match arg1.op(arg2) and extract (arg1, op.symbol, arg2) */ + object BinaryOp { + def unapply(t: Tree)(implicit ctx: Context): Option[(Tree, Symbol, Tree)] = t match { + case Apply(sel @ Select(arg1, _), List(arg2)) => + Some((arg1, sel.symbol, arg2)) + case _ => + None + } + } + + /** Match new C(args) and extract (C, args) */ + object NewWithArgs { + def unapply(t: Tree)(implicit ctx: Context): Option[(Type, List[Tree])] = t match { + case Apply(Select(New(_), nme.CONSTRUCTOR), args) => + Some((t.tpe, args)) + case _ => + None + } + } + + /** For an instance v of a value class like: + * class V(val underlying: X) extends AnyVal + * Match v.underlying() and extract v + */ + object ValueClassUnbox { + def unapply(t: Tree)(implicit ctx: Context): Option[Tree] = t match { + case Apply(sel @ Select(ref, _), Nil) => + val d = ref.tpe.widenDealias.typeSymbol.denot + if (isDerivedValueClass(d) && (sel.symbol eq valueClassUnbox(d.asClass))) { + Some(ref) + } else + None + case _ => + None + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/TreeGen.scala b/compiler/src/dotty/tools/dotc/transform/TreeGen.scala new file mode 100644 index 000000000..7e507d905 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TreeGen.scala @@ -0,0 +1,26 @@ +package dotty.tools.dotc +package transform + +import core._ +import Symbols._, Contexts._, Types._, Names._, StdNames._ +import ast._ +import Trees._ +import TypeUtils._ + +object TreeGen { + + import tpd._ + + def wrapArrayMethodName(elemtp: Type)(implicit ctx: Context): TermName = { + val elemCls = elemtp.classSymbol + if (elemCls.isPrimitiveValueClass) nme.wrapXArray(elemCls.name) + else if (elemCls.derivesFrom(defn.ObjectClass) && !elemCls.isPhantomClass) nme.wrapRefArray + else nme.genericWrapArray + } + + def wrapArray(tree: Tree, elemtp: Type)(implicit ctx: Context): Tree = + ref(defn.ScalaPredefModule) + .select(wrapArrayMethodName(elemtp)) + .appliedToTypes(if (elemtp.isPrimitiveValueType) Nil else elemtp :: Nil) + .appliedTo(tree) +} diff --git a/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala new file mode 100644 index 000000000..5385ca720 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -0,0 +1,1221 @@ +package dotty.tools +package dotc +package transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Annotations.ConcreteAnnotation +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.DenotTransformers.{InfoTransformer, DenotTransformer} +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.core.Flags.PackageVal +import dotty.tools.dotc.core.Mode +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.util.DotClass +import scala.annotation.tailrec +import config.Printers.transforms +import scala.util.control.NonFatal + +object TreeTransforms { + import tpd._ + + /** The base class of tree transforms. For each kind of tree K, there are + * two methods which can be overridden: + * + * prepareForK // return a new TreeTransform which gets applied to the K + * // node and its children + * transformK // transform node of type K + * + * If a transform does not need to visit a node or any of its children, it + * signals this fact by returning a NoTransform from a prepare method. + * + * If all transforms in a group are NoTransforms, the tree is no longer traversed. + * + * + * Performance analysis: Taking the dotty compiler frontend as a use case, we are aiming for a warm performance of + * about 4000 lines / sec. This means 6 seconds for a codebase of 24'000 lines. Of these the frontend consumes + * over 2.5 seconds, erasure and code generation will most likely consume over 1 second each. So we would have + * about 1 sec for all other transformations in our budget. Of this second, let's assume a maximum of 20% for + * the general dispatch overhead as opposed to the concrete work done in transformations. So that leaves us with + * 0.2sec, or roughly 600M processor cycles. + * + * Now, to the amount of work that needs to be done. The codebase produces an average of about 250'000 trees after typechecking. + * Transformations are likely to make this bigger so let's assume 300K trees on average. We estimate to have about 100 + * micro-transformations. Let's say 5 transformation groups of 20 micro-transformations each. (by comparison, + * scalac has in excess of 20 phases, and most phases do multiple transformations). There are then 30M visits + * of a node by a transformation. Each visit has a budget of 20 processor cycles. + * + * A more detailed breakdown: I assume that about one third of all transformations have real work to do for each node. + * This might look high, but keep in mind that the most common nodes are Idents and Selects, and most transformations + * touch these. By contrast the amount of work for generating new transformations should be negligible. + * + * So, in 400 clock cycles we need to (1) perform a pattern match according to the type of node, (2) generate new + * transformations if applicable, (3) reconstitute the tree node from the result of transforming the children, and + * (4) chain 7 out of 20 transformations over the resulting tree node. I believe the current algorithm is suitable + * for achieving this goal, but there can be no wasted cycles anywhere. + */ + abstract class TreeTransform extends DotClass { + + def phase: MiniPhase + + def treeTransformPhase: Phase = phase.next + + def prepareForIdent(tree: Ident)(implicit ctx: Context) = this + def prepareForSelect(tree: Select)(implicit ctx: Context) = this + def prepareForThis(tree: This)(implicit ctx: Context) = this + def prepareForSuper(tree: Super)(implicit ctx: Context) = this + def prepareForApply(tree: Apply)(implicit ctx: Context) = this + def prepareForTypeApply(tree: TypeApply)(implicit ctx: Context) = this + def prepareForLiteral(tree: Literal)(implicit ctx: Context) = this + def prepareForNew(tree: New)(implicit ctx: Context) = this + def prepareForTyped(tree: Typed)(implicit ctx: Context) = this + def prepareForAssign(tree: Assign)(implicit ctx: Context) = this + def prepareForBlock(tree: Block)(implicit ctx: Context) = this + def prepareForIf(tree: If)(implicit ctx: Context) = this + def prepareForClosure(tree: Closure)(implicit ctx: Context) = this + def prepareForMatch(tree: Match)(implicit ctx: Context) = this + def prepareForCaseDef(tree: CaseDef)(implicit ctx: Context) = this + def prepareForReturn(tree: Return)(implicit ctx: Context) = this + def prepareForTry(tree: Try)(implicit ctx: Context) = this + def prepareForSeqLiteral(tree: SeqLiteral)(implicit ctx: Context) = this + def prepareForInlined(tree: Inlined)(implicit ctx: Context) = this + def prepareForTypeTree(tree: TypeTree)(implicit ctx: Context) = this + def prepareForBind(tree: Bind)(implicit ctx: Context) = this + def prepareForAlternative(tree: Alternative)(implicit ctx: Context) = this + def prepareForTypeDef(tree: TypeDef)(implicit ctx: Context) = this + def prepareForUnApply(tree: UnApply)(implicit ctx: Context) = this + def prepareForValDef(tree: ValDef)(implicit ctx: Context) = this + def prepareForDefDef(tree: DefDef)(implicit ctx: Context) = this + def prepareForTemplate(tree: Template)(implicit ctx: Context) = this + def prepareForPackageDef(tree: PackageDef)(implicit ctx: Context) = this + def prepareForStats(trees: List[Tree])(implicit ctx: Context) = this + + def prepareForUnit(tree: Tree)(implicit ctx: Context) = this + + def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformThis(tree: This)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformSuper(tree: Super)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformLiteral(tree: Literal)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformNew(tree: New)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformTyped(tree: Typed)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformIf(tree: If)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformClosure(tree: Closure)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformMatch(tree: Match)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformCaseDef(tree: CaseDef)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformReturn(tree: Return)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformTry(tree: Try)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformSeqLiteral(tree: SeqLiteral)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformInlined(tree: Inlined)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformTypeTree(tree: TypeTree)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformBind(tree: Bind)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformAlternative(tree: Alternative)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformUnApply(tree: UnApply)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformTypeDef(tree: TypeDef)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformPackageDef(tree: PackageDef)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = trees + def transformOther(tree: Tree)(implicit ctx: Context, info: TransformerInfo): Tree = tree + + def transformUnit(tree: Tree)(implicit ctx: Context, info: TransformerInfo): Tree = tree + + /** Transform tree using all transforms of current group (including this one) */ + def transform(tree: Tree)(implicit ctx: Context, info: TransformerInfo): Tree = info.group.transform(tree, info, 0) + + /** Transform subtree using all transforms following the current one in this group */ + def transformFollowingDeep(tree: Tree)(implicit ctx: Context, info: TransformerInfo): Tree = info.group.transform(tree, info, phase.idx + 1) + + /** Transform single node using all transforms following the current one in this group */ + def transformFollowing(tree: Tree)(implicit ctx: Context, info: TransformerInfo): Tree = info.group.transformSingle(tree, phase.idx + 1) + + def atGroupEnd[T](action : Context => T)(implicit ctx: Context, info: TransformerInfo) = { + val last = info.transformers(info.transformers.length - 1) + action(ctx.withPhase(last.phase.next)) + } + } + + /** A phase that defines a TreeTransform to be used in a group */ + trait MiniPhase extends Phase { thisPhase => + def treeTransform: TreeTransform + + /** id of this mini phase in group */ + var idx: Int = _ + + /** List of names of phases that should have finished their processing of all compilation units + * before this phase starts + */ + def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set.empty + + protected def mkTreeTransformer = new TreeTransformer { + override def phaseName: String = thisPhase.phaseName + override def miniPhases = Array(thisPhase) + } + + override def run(implicit ctx: Context): Unit = { + mkTreeTransformer.run + } + } + + /** A mini phase that is its own tree transform */ + abstract class MiniPhaseTransform extends TreeTransform with MiniPhase { + def treeTransform = this + def phase = this + } + + /** A helper trait to transform annotations on MemberDefs */ + trait AnnotationTransformer extends MiniPhaseTransform with DenotTransformer { + + val annotationTransformer = mkTreeTransformer + override final def treeTransformPhase = this + // need to run at own phase because otherwise we get ahead of ourselves in transforming denotations + + abstract override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = + super.transform(ref) match { + case ref1: SymDenotation if ref1.symbol.isDefinedInCurrentRun => + val annotTrees = ref1.annotations.map(_.tree) + val annotTrees1 = annotTrees.mapConserve(annotationTransformer.macroTransform) + if (annotTrees eq annotTrees1) ref1 + else ref1.copySymDenotation(annotations = annotTrees1.map(new ConcreteAnnotation(_))) + case ref1 => + ref1 + } + } + + @sharable val NoTransform = new TreeTransform { + def phase = unsupported("phase") + } + + type Mutator[T] = (TreeTransform, T, Context) => TreeTransform + + class TransformerInfo(val transformers: Array[TreeTransform], val nx: NXTransformations, val group: TreeTransformer) + + /** This class maintains track of which methods are redefined in MiniPhases and creates execution plans for transformXXX and prepareXXX + * Thanks to Martin for this idea + * @see NXTransformations.index for format of plan + */ + class NXTransformations { + + private def hasRedefinedMethod(cls: Class[_], name: String): Boolean = + if (cls.getDeclaredMethods.exists(_.getName == name)) cls != classOf[TreeTransform] + else hasRedefinedMethod(cls.getSuperclass, name) + + /** Create an index array `next` of size one larger than the size of `transforms` such that + * for each index i, `next(i)` is the smallest index j such that + * + * i <= j + * j == transforms.length || transform(j) defines a non-default method with given `name` + */ + private def index(transformations: Array[Class[_]], name: String): Array[Int] = { + val len = transformations.length + val next = new Array[Int](len + 1) + var nextTransform: Int = len + + /* loop invariant: nextTransform == the smallest j such that + * i < j and + * j == transforms.length || transform(j) defines a non-default method with given `name` + */ + next(len) = len + var i = len - 1 + while (i >= 0) { + // update nextTransform if this phase redefines the method + if (hasRedefinedMethod(transformations(i), name)) { + nextTransform = i + } + next(i) = nextTransform + i -= 1 + } + next + } + + private def indexUpdate(prev: Array[Int], changedTransformation: Class[_], index: Int, name: String, copy: Boolean = true) = { + val isDefinedNow = hasRedefinedMethod(changedTransformation, name) + val wasDefinedBefore = prev(index) == index + if (isDefinedNow == wasDefinedBefore) prev + else { + val result = if (copy) prev.clone() else prev + val oldValue = result(index) + val newValue = + if (wasDefinedBefore /* && !isDefinedNow */ ) prev(index + 1) + else index // isDefinedNow + var i = index + while (i >= 0 && result(i) == oldValue) { + result(i) = newValue + i -= 1 + } + result + } + } + + def this(transformations: Array[Class[_]]) = { + this() + nxPrepIdent = index(transformations, "prepareForIdent") + nxPrepSelect = index(transformations, "prepareForSelect") + nxPrepThis = index(transformations, "prepareForThis") + nxPrepSuper = index(transformations, "prepareForSuper") + nxPrepApply = index(transformations, "prepareForApply") + nxPrepTypeApply = index(transformations, "prepareForTypeApply") + nxPrepLiteral = index(transformations, "prepareForLiteral") + nxPrepNew = index(transformations, "prepareForNew") + nxPrepTyped = index(transformations, "prepareForTyped") + nxPrepAssign = index(transformations, "prepareForAssign") + nxPrepBlock = index(transformations, "prepareForBlock") + nxPrepIf = index(transformations, "prepareForIf") + nxPrepClosure = index(transformations, "prepareForClosure") + nxPrepCaseDef = index(transformations, "prepareForCaseDef") + nxPrepMatch = index(transformations, "prepareForMatch") + nxPrepReturn = index(transformations, "prepareForReturn") + nxPrepTry = index(transformations, "prepareForTry") + nxPrepSeqLiteral = index(transformations, "prepareForSeqLiteral") + nxPrepInlined = index(transformations, "prepareForInlined") + nxPrepTypeTree = index(transformations, "prepareForTypeTree") + nxPrepBind = index(transformations, "prepareForBind") + nxPrepAlternative = index(transformations, "prepareForAlternative") + nxPrepUnApply = index(transformations, "prepareForUnApply") + nxPrepValDef = index(transformations, "prepareForValDef") + nxPrepDefDef = index(transformations, "prepareForDefDef") + nxPrepTypeDef = index(transformations, "prepareForTypeDef") + nxPrepTemplate = index(transformations, "prepareForTemplate") + nxPrepPackageDef = index(transformations, "prepareForPackageDef") + nxPrepStats = index(transformations, "prepareForStats") + nxPrepUnit = index(transformations, "prepareForUnit") + + nxTransIdent = index(transformations, "transformIdent") + nxTransSelect = index(transformations, "transformSelect") + nxTransThis = index(transformations, "transformThis") + nxTransSuper = index(transformations, "transformSuper") + nxTransApply = index(transformations, "transformApply") + nxTransTypeApply = index(transformations, "transformTypeApply") + nxTransLiteral = index(transformations, "transformLiteral") + nxTransNew = index(transformations, "transformNew") + nxTransTyped = index(transformations, "transformTyped") + nxTransAssign = index(transformations, "transformAssign") + nxTransBlock = index(transformations, "transformBlock") + nxTransIf = index(transformations, "transformIf") + nxTransClosure = index(transformations, "transformClosure") + nxTransMatch = index(transformations, "transformMatch") + nxTransCaseDef = index(transformations, "transformCaseDef") + nxTransReturn = index(transformations, "transformReturn") + nxTransTry = index(transformations, "transformTry") + nxTransSeqLiteral = index(transformations, "transformSeqLiteral") + nxTransInlined = index(transformations, "transformInlined") + nxTransTypeTree = index(transformations, "transformTypeTree") + nxTransBind = index(transformations, "transformBind") + nxTransAlternative = index(transformations, "transformAlternative") + nxTransUnApply = index(transformations, "transformUnApply") + nxTransValDef = index(transformations, "transformValDef") + nxTransDefDef = index(transformations, "transformDefDef") + nxTransTypeDef = index(transformations, "transformTypeDef") + nxTransTemplate = index(transformations, "transformTemplate") + nxTransPackageDef = index(transformations, "transformPackageDef") + nxTransStats = index(transformations, "transformStats") + nxTransUnit = index(transformations, "transformUnit") + nxTransOther = index(transformations, "transformOther") + } + + def this(transformations: Array[TreeTransform]) = { + this(transformations.map(_.getClass).asInstanceOf[Array[Class[_]]]) + } + + def this(prev: NXTransformations, changedTransformation: TreeTransform, transformationIndex: Int, reuse: Boolean = false) = { + this() + val copy = !reuse + val changedTransformationClass = changedTransformation.getClass + nxPrepIdent = indexUpdate(prev.nxPrepIdent, changedTransformationClass, transformationIndex, "prepareForIdent", copy) + nxPrepSelect = indexUpdate(prev.nxPrepSelect, changedTransformationClass, transformationIndex, "prepareForSelect", copy) + nxPrepThis = indexUpdate(prev.nxPrepThis, changedTransformationClass, transformationIndex, "prepareForThis", copy) + nxPrepSuper = indexUpdate(prev.nxPrepSuper, changedTransformationClass, transformationIndex, "prepareForSuper", copy) + nxPrepApply = indexUpdate(prev.nxPrepApply, changedTransformationClass, transformationIndex, "prepareForApply", copy) + nxPrepTypeApply = indexUpdate(prev.nxPrepTypeApply, changedTransformationClass, transformationIndex, "prepareForTypeApply", copy) + nxPrepLiteral = indexUpdate(prev.nxPrepLiteral, changedTransformationClass, transformationIndex, "prepareForLiteral", copy) + nxPrepNew = indexUpdate(prev.nxPrepNew, changedTransformationClass, transformationIndex, "prepareForNew", copy) + nxPrepTyped = indexUpdate(prev.nxPrepTyped, changedTransformationClass, transformationIndex, "prepareForTyped", copy) + nxPrepAssign = indexUpdate(prev.nxPrepAssign, changedTransformationClass, transformationIndex, "prepareForAssign", copy) + nxPrepBlock = indexUpdate(prev.nxPrepBlock, changedTransformationClass, transformationIndex, "prepareForBlock", copy) + nxPrepIf = indexUpdate(prev.nxPrepIf, changedTransformationClass, transformationIndex, "prepareForIf", copy) + nxPrepClosure = indexUpdate(prev.nxPrepClosure, changedTransformationClass, transformationIndex, "prepareForClosure", copy) + nxPrepMatch = indexUpdate(prev.nxPrepMatch, changedTransformationClass, transformationIndex, "prepareForMatch", copy) + nxPrepCaseDef = indexUpdate(prev.nxPrepCaseDef, changedTransformationClass, transformationIndex, "prepareForCaseDef", copy) + nxPrepReturn = indexUpdate(prev.nxPrepReturn, changedTransformationClass, transformationIndex, "prepareForReturn", copy) + nxPrepTry = indexUpdate(prev.nxPrepTry, changedTransformationClass, transformationIndex, "prepareForTry", copy) + nxPrepSeqLiteral = indexUpdate(prev.nxPrepSeqLiteral, changedTransformationClass, transformationIndex, "prepareForSeqLiteral", copy) + nxPrepInlined = indexUpdate(prev.nxPrepInlined, changedTransformationClass, transformationIndex, "prepareForInlined", copy) + nxPrepTypeTree = indexUpdate(prev.nxPrepTypeTree, changedTransformationClass, transformationIndex, "prepareForTypeTree", copy) + nxPrepBind = indexUpdate(prev.nxPrepBind, changedTransformationClass, transformationIndex, "prepareForBind", copy) + nxPrepAlternative = indexUpdate(prev.nxPrepAlternative, changedTransformationClass, transformationIndex, "prepareForAlternative", copy) + nxPrepUnApply = indexUpdate(prev.nxPrepUnApply, changedTransformationClass, transformationIndex, "prepareForUnApply", copy) + nxPrepValDef = indexUpdate(prev.nxPrepValDef, changedTransformationClass, transformationIndex, "prepareForValDef", copy) + nxPrepDefDef = indexUpdate(prev.nxPrepDefDef, changedTransformationClass, transformationIndex, "prepareForDefDef", copy) + nxPrepTypeDef = indexUpdate(prev.nxPrepTypeDef, changedTransformationClass, transformationIndex, "prepareForTypeDef", copy) + nxPrepTemplate = indexUpdate(prev.nxPrepTemplate, changedTransformationClass, transformationIndex, "prepareForTemplate", copy) + nxPrepPackageDef = indexUpdate(prev.nxPrepPackageDef, changedTransformationClass, transformationIndex, "prepareForPackageDef", copy) + nxPrepStats = indexUpdate(prev.nxPrepStats, changedTransformationClass, transformationIndex, "prepareForStats", copy) + + nxTransIdent = indexUpdate(prev.nxTransIdent, changedTransformationClass, transformationIndex, "transformIdent", copy) + nxTransSelect = indexUpdate(prev.nxTransSelect, changedTransformationClass, transformationIndex, "transformSelect", copy) + nxTransThis = indexUpdate(prev.nxTransThis, changedTransformationClass, transformationIndex, "transformThis", copy) + nxTransSuper = indexUpdate(prev.nxTransSuper, changedTransformationClass, transformationIndex, "transformSuper", copy) + nxTransApply = indexUpdate(prev.nxTransApply, changedTransformationClass, transformationIndex, "transformApply", copy) + nxTransTypeApply = indexUpdate(prev.nxTransTypeApply, changedTransformationClass, transformationIndex, "transformTypeApply", copy) + nxTransLiteral = indexUpdate(prev.nxTransLiteral, changedTransformationClass, transformationIndex, "transformLiteral", copy) + nxTransNew = indexUpdate(prev.nxTransNew, changedTransformationClass, transformationIndex, "transformNew", copy) + nxTransTyped = indexUpdate(prev.nxTransTyped, changedTransformationClass, transformationIndex, "transformTyped", copy) + nxTransAssign = indexUpdate(prev.nxTransAssign, changedTransformationClass, transformationIndex, "transformAssign", copy) + nxTransBlock = indexUpdate(prev.nxTransBlock, changedTransformationClass, transformationIndex, "transformBlock", copy) + nxTransIf = indexUpdate(prev.nxTransIf, changedTransformationClass, transformationIndex, "transformIf", copy) + nxTransClosure = indexUpdate(prev.nxTransClosure, changedTransformationClass, transformationIndex, "transformClosure", copy) + nxTransMatch = indexUpdate(prev.nxTransMatch, changedTransformationClass, transformationIndex, "transformMatch", copy) + nxTransCaseDef = indexUpdate(prev.nxTransCaseDef, changedTransformationClass, transformationIndex, "transformCaseDef", copy) + nxTransReturn = indexUpdate(prev.nxTransReturn, changedTransformationClass, transformationIndex, "transformReturn", copy) + nxTransTry = indexUpdate(prev.nxTransTry, changedTransformationClass, transformationIndex, "transformTry", copy) + nxTransSeqLiteral = indexUpdate(prev.nxTransSeqLiteral, changedTransformationClass, transformationIndex, "transformSeqLiteral", copy) + nxTransInlined = indexUpdate(prev.nxTransInlined, changedTransformationClass, transformationIndex, "transformInlined", copy) + nxTransTypeTree = indexUpdate(prev.nxTransTypeTree, changedTransformationClass, transformationIndex, "transformTypeTree", copy) + nxTransBind = indexUpdate(prev.nxTransBind, changedTransformationClass, transformationIndex, "transformBind", copy) + nxTransAlternative = indexUpdate(prev.nxTransAlternative, changedTransformationClass, transformationIndex, "transformAlternative", copy) + nxTransUnApply = indexUpdate(prev.nxTransUnApply, changedTransformationClass, transformationIndex, "transformUnApply", copy) + nxTransValDef = indexUpdate(prev.nxTransValDef, changedTransformationClass, transformationIndex, "transformValDef", copy) + nxTransDefDef = indexUpdate(prev.nxTransDefDef, changedTransformationClass, transformationIndex, "transformDefDef", copy) + nxTransTypeDef = indexUpdate(prev.nxTransTypeDef, changedTransformationClass, transformationIndex, "transformTypeDef", copy) + nxTransTemplate = indexUpdate(prev.nxTransTemplate, changedTransformationClass, transformationIndex, "transformTemplate", copy) + nxTransPackageDef = indexUpdate(prev.nxTransPackageDef, changedTransformationClass, transformationIndex, "transformPackageDef", copy) + nxTransStats = indexUpdate(prev.nxTransStats, changedTransformationClass, transformationIndex, "transformStats", copy) + nxTransOther = indexUpdate(prev.nxTransOther, changedTransformationClass, transformationIndex, "transformOther", copy) + } + + /** Those arrays are used as "execution plan" in order to only execute non-trivial transformations\preparations + * for every integer i array(i) contains first non trivial transformation\preparation on particular tree subtype. + * If no nontrivial transformation are left stored value is greater than transformers.size + */ + var nxPrepIdent: Array[Int] = _ + var nxPrepSelect: Array[Int] = _ + var nxPrepThis: Array[Int] = _ + var nxPrepSuper: Array[Int] = _ + var nxPrepApply: Array[Int] = _ + var nxPrepTypeApply: Array[Int] = _ + var nxPrepLiteral: Array[Int] = _ + var nxPrepNew: Array[Int] = _ + var nxPrepTyped: Array[Int] = _ + var nxPrepAssign: Array[Int] = _ + var nxPrepBlock: Array[Int] = _ + var nxPrepIf: Array[Int] = _ + var nxPrepClosure: Array[Int] = _ + var nxPrepMatch: Array[Int] = _ + var nxPrepCaseDef: Array[Int] = _ + var nxPrepReturn: Array[Int] = _ + var nxPrepTry: Array[Int] = _ + var nxPrepSeqLiteral: Array[Int] = _ + var nxPrepInlined: Array[Int] = _ + var nxPrepTypeTree: Array[Int] = _ + var nxPrepBind: Array[Int] = _ + var nxPrepAlternative: Array[Int] = _ + var nxPrepUnApply: Array[Int] = _ + var nxPrepValDef: Array[Int] = _ + var nxPrepDefDef: Array[Int] = _ + var nxPrepTypeDef: Array[Int] = _ + var nxPrepTemplate: Array[Int] = _ + var nxPrepPackageDef: Array[Int] = _ + var nxPrepStats: Array[Int] = _ + var nxPrepUnit: Array[Int] = _ + + var nxTransIdent: Array[Int] = _ + var nxTransSelect: Array[Int] = _ + var nxTransThis: Array[Int] = _ + var nxTransSuper: Array[Int] = _ + var nxTransApply: Array[Int] = _ + var nxTransTypeApply: Array[Int] = _ + var nxTransLiteral: Array[Int] = _ + var nxTransNew: Array[Int] = _ + var nxTransTyped: Array[Int] = _ + var nxTransAssign: Array[Int] = _ + var nxTransBlock: Array[Int] = _ + var nxTransIf: Array[Int] = _ + var nxTransClosure: Array[Int] = _ + var nxTransMatch: Array[Int] = _ + var nxTransCaseDef: Array[Int] = _ + var nxTransReturn: Array[Int] = _ + var nxTransTry: Array[Int] = _ + var nxTransSeqLiteral: Array[Int] = _ + var nxTransInlined: Array[Int] = _ + var nxTransTypeTree: Array[Int] = _ + var nxTransBind: Array[Int] = _ + var nxTransAlternative: Array[Int] = _ + var nxTransUnApply: Array[Int] = _ + var nxTransValDef: Array[Int] = _ + var nxTransDefDef: Array[Int] = _ + var nxTransTypeDef: Array[Int] = _ + var nxTransTemplate: Array[Int] = _ + var nxTransPackageDef: Array[Int] = _ + var nxTransStats: Array[Int] = _ + var nxTransUnit: Array[Int] = _ + var nxTransOther: Array[Int] = _ + } + + /** A group of tree transforms that are applied in sequence during the same phase */ + abstract class TreeTransformer extends Phase { + + def miniPhases: Array[MiniPhase] + + override def run(implicit ctx: Context): Unit = { + val curTree = ctx.compilationUnit.tpdTree + val newTree = macroTransform(curTree) + ctx.compilationUnit.tpdTree = newTree + } + + def mutateTransformers[T](info: TransformerInfo, mutator: Mutator[T], mutationPlan: Array[Int], tree: T, cur: Int)(implicit ctx: Context) = { + var transformersCopied = false + var nxCopied = false + var result = info.transformers + var resultNX = info.nx + 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) { + val oldTransform = result(i) + val newTransform = mutator(oldTransform, tree, ctx.withPhase(oldTransform.treeTransformPhase)) + allDone = allDone && (newTransform eq NoTransform) + if (!(oldTransform eq newTransform)) { + if (!transformersCopied) result = result.clone() + transformersCopied = true + result(i) = newTransform + if (!(newTransform.getClass == oldTransform.getClass)) { + resultNX = new NXTransformations(resultNX, newTransform, i, nxCopied) + nxCopied = true + } + } + i = mutationPlan(i + 1) + } + if (allDone) null + else if (!transformersCopied) info + else new TransformerInfo(result, resultNX, info.group) + } + + val prepForIdent: Mutator[Ident] = (trans, tree, ctx) => trans.prepareForIdent(tree)(ctx) + val prepForSelect: Mutator[Select] = (trans, tree, ctx) => trans.prepareForSelect(tree)(ctx) + val prepForThis: Mutator[This] = (trans, tree, ctx) => trans.prepareForThis(tree)(ctx) + val prepForSuper: Mutator[Super] = (trans, tree, ctx) => trans.prepareForSuper(tree)(ctx) + val prepForApply: Mutator[Apply] = (trans, tree, ctx) => trans.prepareForApply(tree)(ctx) + val prepForTypeApply: Mutator[TypeApply] = (trans, tree, ctx) => trans.prepareForTypeApply(tree)(ctx) + val prepForNew: Mutator[New] = (trans, tree, ctx) => trans.prepareForNew(tree)(ctx) + val prepForTyped: Mutator[Typed] = (trans, tree, ctx) => trans.prepareForTyped(tree)(ctx) + val prepForAssign: Mutator[Assign] = (trans, tree, ctx) => trans.prepareForAssign(tree)(ctx) + val prepForLiteral: Mutator[Literal] = (trans, tree, ctx) => trans.prepareForLiteral(tree)(ctx) + val prepForBlock: Mutator[Block] = (trans, tree, ctx) => trans.prepareForBlock(tree)(ctx) + val prepForIf: Mutator[If] = (trans, tree, ctx) => trans.prepareForIf(tree)(ctx) + val prepForClosure: Mutator[Closure] = (trans, tree, ctx) => trans.prepareForClosure(tree)(ctx) + val prepForMatch: Mutator[Match] = (trans, tree, ctx) => trans.prepareForMatch(tree)(ctx) + val prepForCaseDef: Mutator[CaseDef] = (trans, tree, ctx) => trans.prepareForCaseDef(tree)(ctx) + val prepForReturn: Mutator[Return] = (trans, tree, ctx) => trans.prepareForReturn(tree)(ctx) + val prepForTry: Mutator[Try] = (trans, tree, ctx) => trans.prepareForTry(tree)(ctx) + val prepForSeqLiteral: Mutator[SeqLiteral] = (trans, tree, ctx) => trans.prepareForSeqLiteral(tree)(ctx) + val prepForInlined: Mutator[Inlined] = (trans, tree, ctx) => trans.prepareForInlined(tree)(ctx) + val prepForTypeTree: Mutator[TypeTree] = (trans, tree, ctx) => trans.prepareForTypeTree(tree)(ctx) + val prepForBind: Mutator[Bind] = (trans, tree, ctx) => trans.prepareForBind(tree)(ctx) + val prepForAlternative: Mutator[Alternative] = (trans, tree, ctx) => trans.prepareForAlternative(tree)(ctx) + val prepForUnApply: Mutator[UnApply] = (trans, tree, ctx) => trans.prepareForUnApply(tree)(ctx) + val prepForValDef: Mutator[ValDef] = (trans, tree, ctx) => trans.prepareForValDef(tree)(ctx) + val prepForDefDef: Mutator[DefDef] = (trans, tree, ctx) => trans.prepareForDefDef(tree)(ctx) + val prepForTypeDef: Mutator[TypeDef] = (trans, tree, ctx) => trans.prepareForTypeDef(tree)(ctx) + val prepForTemplate: Mutator[Template] = (trans, tree, ctx) => trans.prepareForTemplate(tree)(ctx) + val prepForPackageDef: Mutator[PackageDef] = (trans, tree, ctx) => trans.prepareForPackageDef(tree)(ctx) + val prepForStats: Mutator[List[Tree]] = (trans, trees, ctx) => trans.prepareForStats(trees)(ctx) + val prepForUnit: Mutator[Tree] = (trans, tree, ctx) => trans.prepareForUnit(tree)(ctx) + + val initialTransformationsCache = miniPhases.zipWithIndex.map { + case (miniPhase, id) => + miniPhase.idx = id + miniPhase.treeTransform + } + + val initialInfoCache = new TransformerInfo(initialTransformationsCache, new NXTransformations(initialTransformationsCache), this) + + def macroTransform(t: Tree)(implicit ctx: Context): Tree = { + val info = initialInfoCache + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForUnit, info.nx.nxPrepUnit, t, 0) + if (mutatedInfo eq null) t + else goUnit(transform(t, mutatedInfo, 0), mutatedInfo.nx.nxTransUnit(0)) + } + + @tailrec + final private[TreeTransforms] def goIdent(tree: Ident, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformIdent(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Ident => goIdent(t, info.nx.nxTransIdent(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goSelect(tree: Select, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformSelect(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Select => goSelect(t, info.nx.nxTransSelect(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goThis(tree: This, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformThis(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: This => goThis(t, info.nx.nxTransThis(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goSuper(tree: Super, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformSuper(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Super => goSuper(t, info.nx.nxTransSuper(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goApply(tree: Apply, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformApply(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Apply => goApply(t, info.nx.nxTransApply(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goTypeApply(tree: TypeApply, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformTypeApply(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: TypeApply => goTypeApply(t, info.nx.nxTransTypeApply(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goNew(tree: New, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformNew(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: New => goNew(t, info.nx.nxTransNew(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goTyped(tree: Typed, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformTyped(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Typed => goTyped(t, info.nx.nxTransTyped(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goAssign(tree: Assign, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformAssign(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Assign => goAssign(t, info.nx.nxTransAssign(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goLiteral(tree: Literal, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformLiteral(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Literal => goLiteral(t, info.nx.nxTransLiteral(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goBlock(tree: Block, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformBlock(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Block => goBlock(t, info.nx.nxTransBlock(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goIf(tree: If, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformIf(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: If => goIf(t, info.nx.nxTransIf(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goClosure(tree: Closure, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformClosure(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Closure => goClosure(t, info.nx.nxTransClosure(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goMatch(tree: Match, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformMatch(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Match => goMatch(t, info.nx.nxTransMatch(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goCaseDef(tree: CaseDef, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformCaseDef(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: CaseDef => goCaseDef(t, info.nx.nxTransCaseDef(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goReturn(tree: Return, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformReturn(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Return => goReturn(t, info.nx.nxTransReturn(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goTry(tree: Try, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformTry(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Try => goTry(t, info.nx.nxTransTry(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goSeqLiteral(tree: SeqLiteral, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformSeqLiteral(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: SeqLiteral => goSeqLiteral(t, info.nx.nxTransSeqLiteral(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goInlined(tree: Inlined, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformInlined(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Inlined => goInlined(t, info.nx.nxTransInlined(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goTypeTree(tree: TypeTree, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformTypeTree(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: TypeTree => goTypeTree(t, info.nx.nxTransTypeTree(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goBind(tree: Bind, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformBind(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Bind => goBind(t, info.nx.nxTransBind(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goAlternative(tree: Alternative, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformAlternative(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Alternative => goAlternative(t, info.nx.nxTransAlternative(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goValDef(tree: ValDef, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformValDef(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: ValDef => goValDef(t, info.nx.nxTransValDef(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goDefDef(tree: DefDef, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformDefDef(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: DefDef => goDefDef(t, info.nx.nxTransDefDef(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goUnApply(tree: UnApply, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformUnApply(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: UnApply => goUnApply(t, info.nx.nxTransUnApply(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goTypeDef(tree: TypeDef, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformTypeDef(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: TypeDef => goTypeDef(t, info.nx.nxTransTypeDef(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goTemplate(tree: Template, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformTemplate(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Template => goTemplate(t, info.nx.nxTransTemplate(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goPackageDef(tree: PackageDef, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformPackageDef(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: PackageDef => goPackageDef(t, info.nx.nxTransPackageDef(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + + @tailrec + final private[TreeTransforms] def goUnit(tree: Tree, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + val t = trans.transformUnit(tree)(ctx.withPhase(trans.treeTransformPhase), info) + goUnit(t, info.nx.nxTransUnit(cur + 1)) + } else tree + } + + final private[TreeTransforms] def goOther(tree: Tree, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + val t = trans.transformOther(tree)(ctx.withPhase(trans.treeTransformPhase), info) + transformSingle(t, cur + 1) + } else tree + } + + final private[TreeTransforms] def goNamed(tree: NameTree, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = + tree match { + case tree: Ident => goIdent(tree, info.nx.nxTransIdent(cur)) + case tree: Select => goSelect(tree, info.nx.nxTransSelect(cur)) + case tree: Bind => goBind(tree, cur) + case tree: ValDef if !tree.isEmpty => goValDef(tree, info.nx.nxTransValDef(cur)) + case tree: DefDef => goDefDef(tree, info.nx.nxTransDefDef(cur)) + case tree: TypeDef => goTypeDef(tree, info.nx.nxTransTypeDef(cur)) + case _ => tree + } + + final private[TreeTransforms] def goUnnamed(tree: Tree, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = + tree match { + case tree: This => goThis(tree, info.nx.nxTransThis(cur)) + case tree: Super => goSuper(tree, info.nx.nxTransSuper(cur)) + case tree: Apply => goApply(tree, info.nx.nxTransApply(cur)) + case tree: TypeApply => goTypeApply(tree, info.nx.nxTransTypeApply(cur)) + case tree: Literal => goLiteral(tree, info.nx.nxTransLiteral(cur)) + case tree: New => goNew(tree, info.nx.nxTransNew(cur)) + case tree: Typed => goTyped(tree, info.nx.nxTransTyped(cur)) + case tree: Assign => goAssign(tree, info.nx.nxTransAssign(cur)) + case tree: Block => goBlock(tree, info.nx.nxTransBlock(cur)) + case tree: If => goIf(tree, info.nx.nxTransIf(cur)) + case tree: Closure => goClosure(tree, info.nx.nxTransClosure(cur)) + case tree: Match => goMatch(tree, info.nx.nxTransMatch(cur)) + case tree: CaseDef => goCaseDef(tree, info.nx.nxTransCaseDef(cur)) + case tree: Return => goReturn(tree, info.nx.nxTransReturn(cur)) + case tree: Try => goTry(tree, info.nx.nxTransTry(cur)) + case tree: SeqLiteral => goSeqLiteral(tree, info.nx.nxTransSeqLiteral(cur)) + case tree: Inlined => goInlined(tree, info.nx.nxTransInlined(cur)) + case tree: TypeTree => goTypeTree(tree, info.nx.nxTransTypeTree(cur)) + case tree: Alternative => goAlternative(tree, info.nx.nxTransAlternative(cur)) + case tree: UnApply => goUnApply(tree, info.nx.nxTransUnApply(cur)) + case tree: Template => goTemplate(tree, info.nx.nxTransTemplate(cur)) + case tree: PackageDef => goPackageDef(tree, info.nx.nxTransPackageDef(cur)) + case Thicket(trees) => tree + case tree => goOther(tree, info.nx.nxTransOther(cur)) + } + + final private[TreeTransforms] def transformSingle(tree: Tree, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = + if (cur < info.transformers.length) { + tree match { + // split one big match into 2 smaller ones + case tree: NameTree => goNamed(tree, cur) + case tree => goUnnamed(tree, cur) + } + } else tree + + // TODO merge with localCtx in MacroTransform + // Generally: If we will keep MacroTransform, merge common behavior with TreeTransform + def localContext(sym: Symbol)(implicit ctx: Context) = { + val owner = if (sym is PackageVal) sym.moduleClass else sym + ctx.fresh.setOwner(owner) + } + + final private[TreeTransforms] def transformNamed(tree: NameTree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = + tree match { + case tree: Ident => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForIdent, info.nx.nxPrepIdent, tree, cur) + // Dotty deviation: implicits need explicit type + if (mutatedInfo eq null) tree + else goIdent(tree, mutatedInfo.nx.nxTransIdent(cur)) + case tree: Select => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForSelect, info.nx.nxPrepSelect, tree, cur) + if (mutatedInfo eq null) tree + else { + val qual = transform(tree.qualifier, mutatedInfo, cur) + goSelect(cpy.Select(tree)(qual, tree.name), mutatedInfo.nx.nxTransSelect(cur)) + } + case tree: Bind => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForBind, info.nx.nxPrepBind, tree, cur) + if (mutatedInfo eq null) tree + else { + val body = transform(tree.body, mutatedInfo, cur) + goBind(cpy.Bind(tree)(tree.name, body), mutatedInfo.nx.nxTransBind(cur)) + } + case tree: ValDef if !tree.isEmpty => // As a result of discussing with Martin: emptyValDefs shouldn't be copied // NAME + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForValDef, info.nx.nxPrepValDef, tree, cur) + if (mutatedInfo eq null) tree + else { + val nestedCtx = if (tree.symbol.exists) localContext(tree.symbol) else ctx + val tpt = transform(tree.tpt, mutatedInfo, cur)(nestedCtx) + val rhs = transform(tree.rhs, mutatedInfo, cur)(nestedCtx) + goValDef(cpy.ValDef(tree)(tree.name, tpt, rhs), mutatedInfo.nx.nxTransValDef(cur)) + } + case tree: DefDef => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForDefDef, info.nx.nxPrepDefDef, tree, cur) + if (mutatedInfo eq null) tree + else { + val nestedCtx = localContext(tree.symbol) + val tparams = transformSubTrees(tree.tparams, mutatedInfo, cur)(nestedCtx) + val vparams = tree.vparamss.mapConserve(x => transformSubTrees(x, mutatedInfo, cur)(nestedCtx)) + val tpt = transform(tree.tpt, mutatedInfo, cur)(nestedCtx) + val rhs = transform(tree.rhs, mutatedInfo, cur)(nestedCtx) + goDefDef(cpy.DefDef(tree)(tree.name, tparams, vparams, tpt, rhs), mutatedInfo.nx.nxTransDefDef(cur)) + } + case tree: TypeDef => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTypeDef, info.nx.nxPrepTypeDef, tree, cur) + if (mutatedInfo eq null) tree + else { + val rhs = transform(tree.rhs, mutatedInfo, cur)(localContext(tree.symbol)) + goTypeDef(cpy.TypeDef(tree)(tree.name, rhs), mutatedInfo.nx.nxTransTypeDef(cur)) + } + case _ => + tree + } + + final private[TreeTransforms] def transformUnnamed(tree: Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = + tree match { + case tree: This => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForThis, info.nx.nxPrepThis, tree, cur) + if (mutatedInfo eq null) tree + else goThis(tree, mutatedInfo.nx.nxTransThis(cur)) + case tree: Super => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForSuper, info.nx.nxPrepSuper, tree, cur) + if (mutatedInfo eq null) tree + else { + val qual = transform(tree.qual, mutatedInfo, cur) + goSuper(cpy.Super(tree)(qual, tree.mix), mutatedInfo.nx.nxTransSuper(cur)) + } + case tree: Apply => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForApply, info.nx.nxPrepApply, tree, cur) + if (mutatedInfo eq null) tree + else { + val fun = transform(tree.fun, mutatedInfo, cur) + val args = transformSubTrees(tree.args, mutatedInfo, cur) + goApply(cpy.Apply(tree)(fun, args), mutatedInfo.nx.nxTransApply(cur)) + } + case tree: TypeApply => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTypeApply, info.nx.nxPrepTypeApply, tree, cur) + if (mutatedInfo eq null) tree + else { + val fun = transform(tree.fun, mutatedInfo, cur) + val args = transformTrees(tree.args, mutatedInfo, cur) + goTypeApply(cpy.TypeApply(tree)(fun, args), mutatedInfo.nx.nxTransTypeApply(cur)) + } + case tree: Literal => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForLiteral, info.nx.nxPrepLiteral, tree, cur) + if (mutatedInfo eq null) tree + else goLiteral(tree, mutatedInfo.nx.nxTransLiteral(cur)) + case tree: New => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForNew, info.nx.nxPrepNew, tree, cur) + if (mutatedInfo eq null) tree + else { + val tpt = transform(tree.tpt, mutatedInfo, cur) + goNew(cpy.New(tree)(tpt), mutatedInfo.nx.nxTransNew(cur)) + } + case tree: Typed => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTyped, info.nx.nxPrepTyped, tree, cur) + if (mutatedInfo eq null) tree + else { + val expr = transform(tree.expr, mutatedInfo, cur) + val tpt = transform(tree.tpt, mutatedInfo, cur) + goTyped(cpy.Typed(tree)(expr, tpt), mutatedInfo.nx.nxTransTyped(cur)) + } + case tree: Assign => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForAssign, info.nx.nxPrepAssign, tree, cur) + if (mutatedInfo eq null) tree + else { + val lhs = transform(tree.lhs, mutatedInfo, cur) + val rhs = transform(tree.rhs, mutatedInfo, cur) + goAssign(cpy.Assign(tree)(lhs, rhs), mutatedInfo.nx.nxTransAssign(cur)) + } + case tree: Block => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForBlock, info.nx.nxPrepBlock, tree, cur) + if (mutatedInfo eq null) tree + else { + val stats = transformStats(tree.stats, ctx.owner, mutatedInfo, cur) + val expr = transform(tree.expr, mutatedInfo, cur) + goBlock(cpy.Block(tree)(stats, expr), mutatedInfo.nx.nxTransBlock(cur)) + } + case tree: If => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForIf, info.nx.nxPrepIf, tree, cur) + if (mutatedInfo eq null) tree + else { + val cond = transform(tree.cond, mutatedInfo, cur) + val thenp = transform(tree.thenp, mutatedInfo, cur) + val elsep = transform(tree.elsep, mutatedInfo, cur) + goIf(cpy.If(tree)(cond, thenp, elsep), mutatedInfo.nx.nxTransIf(cur)) + } + case tree: Closure => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForClosure, info.nx.nxPrepClosure, tree, cur) + if (mutatedInfo eq null) tree + else { + val env = transformTrees(tree.env, mutatedInfo, cur) + val meth = transform(tree.meth, mutatedInfo, cur) + val tpt = transform(tree.tpt, mutatedInfo, cur) + goClosure(cpy.Closure(tree)(env, meth, tpt), mutatedInfo.nx.nxTransClosure(cur)) + } + case tree: Match => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForMatch, info.nx.nxPrepMatch, tree, cur) + if (mutatedInfo eq null) tree + else { + val selector = transform(tree.selector, mutatedInfo, cur) + val cases = transformSubTrees(tree.cases, mutatedInfo, cur) + goMatch(cpy.Match(tree)(selector, cases), mutatedInfo.nx.nxTransMatch(cur)) + } + case tree: CaseDef => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForCaseDef, info.nx.nxPrepCaseDef, tree, cur) + if (mutatedInfo eq null) tree + else { + val pat = transform(tree.pat, mutatedInfo, cur)(ctx.addMode(Mode.Pattern)) + val guard = transform(tree.guard, mutatedInfo, cur) + val body = transform(tree.body, mutatedInfo, cur) + goCaseDef(cpy.CaseDef(tree)(pat, guard, body), mutatedInfo.nx.nxTransCaseDef(cur)) + } + case tree: Return => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForReturn, info.nx.nxPrepReturn, tree, cur) + if (mutatedInfo eq null) tree + else { + val expr = transform(tree.expr, mutatedInfo, cur) + val from = tree.from + // don't transform the `from` part, as this is not a normal ident, but + // a pointer to the enclosing method. Transforming this as a normal ident + // can go wrong easily. If a transformation is needed, it should be + // the responsibility of the transformReturn method to handle this also. + goReturn(cpy.Return(tree)(expr, from), mutatedInfo.nx.nxTransReturn(cur)) + } + case tree: Try => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTry, info.nx.nxPrepTry, tree, cur) + if (mutatedInfo eq null) tree + else { + val block = transform(tree.expr, mutatedInfo, cur) + val cases1 = tree.cases.mapConserve(transform(_, mutatedInfo, cur)).asInstanceOf[List[CaseDef]] + val finalizer = transform(tree.finalizer, mutatedInfo, cur) + goTry(cpy.Try(tree)(block, cases1, finalizer), mutatedInfo.nx.nxTransTry(cur)) + } + case tree: SeqLiteral => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForSeqLiteral, info.nx.nxPrepSeqLiteral, tree, cur) + if (mutatedInfo eq null) tree + else { + val elems = transformTrees(tree.elems, mutatedInfo, cur) + val elemtpt = transform(tree.elemtpt, mutatedInfo, cur) + goSeqLiteral(cpy.SeqLiteral(tree)(elems, elemtpt), mutatedInfo.nx.nxTransSeqLiteral(cur)) + } + case tree: Inlined => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForInlined, info.nx.nxPrepInlined, tree, cur) + if (mutatedInfo eq null) tree + else { + val bindings = transformSubTrees(tree.bindings, mutatedInfo, cur) + val expansion = transform(tree.expansion, mutatedInfo, cur)(inlineContext(tree)) + goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), mutatedInfo.nx.nxTransInlined(cur)) + } + case tree: TypeTree => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTypeTree, info.nx.nxPrepTypeTree, tree, cur) + if (mutatedInfo eq null) tree + else goTypeTree(tree, mutatedInfo.nx.nxTransTypeTree(cur)) + case tree: Alternative => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForAlternative, info.nx.nxPrepAlternative, tree, cur) + if (mutatedInfo eq null) tree + else { + val trees = transformTrees(tree.trees, mutatedInfo, cur) + goAlternative(cpy.Alternative(tree)(trees), mutatedInfo.nx.nxTransAlternative(cur)) + } + case tree: UnApply => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForUnApply, info.nx.nxPrepUnApply, tree, cur) + if (mutatedInfo eq null) tree + else { + val fun = transform(tree.fun, mutatedInfo, cur) + val implicits = transformTrees(tree.implicits, mutatedInfo, cur) + val patterns = transformTrees(tree.patterns, mutatedInfo, cur) + goUnApply(cpy.UnApply(tree)(fun, implicits, patterns), mutatedInfo.nx.nxTransUnApply(cur)) + } + case tree: Template => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTemplate, info.nx.nxPrepTemplate, tree, cur) + if (mutatedInfo eq null) tree + else { + val constr = transformSub(tree.constr, mutatedInfo, cur) + val parents = transformTrees(tree.parents, mutatedInfo, cur)(ctx.superCallContext) + val self = transformSub(tree.self, mutatedInfo, cur) + val body = transformStats(tree.body, tree.symbol, mutatedInfo, cur) + goTemplate(cpy.Template(tree)(constr, parents, self, body), mutatedInfo.nx.nxTransTemplate(cur)) + } + case tree: PackageDef => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForPackageDef, info.nx.nxPrepPackageDef, tree, cur) + if (mutatedInfo eq null) tree + else { + val nestedCtx = localContext(tree.symbol) + val pid = transformSub(tree.pid, mutatedInfo, cur) + val stats = transformStats(tree.stats, tree.symbol, mutatedInfo, cur)(nestedCtx) + goPackageDef(cpy.PackageDef(tree)(pid, stats), mutatedInfo.nx.nxTransPackageDef(cur)) + } + case Thicket(trees) => + cpy.Thicket(tree)(transformTrees(trees, info, cur)) + case tree => + implicit val originalInfo: TransformerInfo = info + goOther(tree, info.nx.nxTransOther(cur)) + } + + private var crashingTree: Tree = EmptyTree + + def transform(tree: Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show} at ${ctx.phase}", transforms, show = true) { + try + if (cur < info.transformers.length) { + // if cur > 0 then some of the symbols can be created by already performed transformations + // this means that their denotations could not exists in previous period + val pctx = ctx.withPhase(info.transformers(cur).treeTransformPhase) + tree match { + //split one big match into 2 smaller ones + case tree: NameTree => transformNamed(tree, info, cur)(pctx) + case tree => transformUnnamed(tree, info, cur)(pctx) + } + } else tree + catch { + case NonFatal(ex) => + if (tree ne crashingTree) { + crashingTree = tree + println(i"exception while transforming $tree of class ${tree.getClass} # ${tree.uniqueId}") + } + throw ex + } + } + + @tailrec + final private[TreeTransforms] def goStats(trees: List[Tree], cur: Int)(implicit ctx: Context, info: TransformerInfo): List[Tree] = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + val stats = trans.transformStats(trees)(ctx.withPhase(trans.treeTransformPhase), info) + goStats(stats, info.nx.nxTransStats(cur + 1)) + } else trees + } + + def transformStats(trees: List[Tree], exprOwner: Symbol, info: TransformerInfo, current: Int)(implicit ctx: Context): List[Tree] = { + val newInfo = mutateTransformers(info, prepForStats, info.nx.nxPrepStats, trees, current) + def transformStat(stat: Tree): Tree = stat match { + case _: Import | _: DefTree => transform(stat, newInfo, current) + case Thicket(stats) => cpy.Thicket(stat)(stats mapConserve transformStat) + case _ => transform(stat, newInfo, current)(ctx.exprContext(stat, exprOwner)) + } + val newTrees = flatten(trees.mapconserve(transformStat)) + goStats(newTrees, newInfo.nx.nxTransStats(current))(ctx, newInfo) + } + + def transformTrees(trees: List[Tree], info: TransformerInfo, current: Int)(implicit ctx: Context): List[Tree] = + flatten(trees mapConserve (x => transform(x, info, current))) + + def transformSub[Tr <: Tree](tree: Tr, info: TransformerInfo, current: Int)(implicit ctx: Context): Tr = + transform(tree, info, current).asInstanceOf[Tr] + + def transformSubTrees[Tr <: Tree](trees: List[Tr], info: TransformerInfo, current: Int)(implicit ctx: Context): List[Tr] = + transformTrees(trees, info, current)(ctx).asInstanceOf[List[Tr]] + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala new file mode 100644 index 000000000..9a6ecef51 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala @@ -0,0 +1,99 @@ +package dotty.tools.dotc +package transform + +import core.Symbols._ +import core.StdNames._ +import ast.Trees._ +import core.Types._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo} +import dotty.tools.dotc.util.Positions.Position + +/** Compiles the cases that can not be handled by primitive catch cases as a common pattern match. + * + * The following code: + * ``` + * try { <code> } + * catch { + * <tryCases> // Cases that can be handled by catch + * <patternMatchCases> // Cases starting with first one that can't be handled by catch + * } + * ``` + * will become: + * ``` + * try { <code> } + * catch { + * <tryCases> + * case e => e match { + * <patternMatchCases> + * } + * } + * ``` + * + * Cases that are not supported include: + * - Applies and unapplies + * - Idents + * - Alternatives + * - `case _: T =>` where `T` is not `Throwable` + * + */ +class TryCatchPatterns extends MiniPhaseTransform { + import dotty.tools.dotc.ast.tpd._ + + def phaseName: String = "tryCatchPatterns" + + override def runsAfter = Set(classOf[ElimRepeated]) + + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { + case Try(_, cases, _) => + cases.foreach { + case CaseDef(Typed(_, _), guard, _) => assert(guard.isEmpty, "Try case should not contain a guard.") + case CaseDef(Bind(_, _), guard, _) => assert(guard.isEmpty, "Try case should not contain a guard.") + case c => + assert(isDefaultCase(c), "Pattern in Try should be Bind, Typed or default case.") + } + case _ => + } + + override def transformTry(tree: Try)(implicit ctx: Context, info: TransformerInfo): Tree = { + val (tryCases, patternMatchCases) = tree.cases.span(isCatchCase) + val fallbackCase = mkFallbackPatterMatchCase(patternMatchCases, tree.pos) + cpy.Try(tree)(cases = tryCases ++ fallbackCase) + } + + /** Is this pattern node a catch-all or type-test pattern? */ + private def isCatchCase(cdef: CaseDef)(implicit ctx: Context): Boolean = cdef match { + case CaseDef(Typed(Ident(nme.WILDCARD), tpt), EmptyTree, _) => isSimpleThrowable(tpt.tpe) + case CaseDef(Bind(_, Typed(Ident(nme.WILDCARD), tpt)), EmptyTree, _) => isSimpleThrowable(tpt.tpe) + case _ => isDefaultCase(cdef) + } + + private def isSimpleThrowable(tp: Type)(implicit ctx: Context): Boolean = tp match { + case tp @ TypeRef(pre, _) => + (pre == NoPrefix || pre.widen.typeSymbol.isStatic) && // Does not require outer class check + !tp.symbol.is(Flags.Trait) && // Traits not supported by JVM + tp.derivesFrom(defn.ThrowableClass) + case _ => + false + } + + private def mkFallbackPatterMatchCase(patternMatchCases: List[CaseDef], pos: Position)( + implicit ctx: Context, info: TransformerInfo): Option[CaseDef] = { + if (patternMatchCases.isEmpty) None + else { + val exName = ctx.freshName("ex").toTermName + val fallbackSelector = + ctx.newSymbol(ctx.owner, exName, Flags.Synthetic | Flags.Case, defn.ThrowableType, coord = pos) + val sel = Ident(fallbackSelector.termRef).withPos(pos) + val rethrow = CaseDef(EmptyTree, EmptyTree, Throw(ref(fallbackSelector))) + Some(CaseDef( + Bind(fallbackSelector, Underscore(fallbackSelector.info).withPos(pos)), + EmptyTree, + transformFollowing(Match(sel, patternMatchCases ::: rethrow :: Nil))) + ) + } + } + +} diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala new file mode 100644 index 000000000..3774127fa --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -0,0 +1,124 @@ +package dotty.tools.dotc +package transform + +import core.Contexts._ +import core.Symbols._ +import core.Types._ +import core.Constants._ +import core.StdNames._ +import core.TypeErasure.isUnboundedGeneric +import ast.Trees._ +import Erasure.Boxing._ +import core.TypeErasure._ +import ValueClasses._ + +/** This transform normalizes type tests and type casts, + * also replacing type tests with singleton argument type with reference equality check + * Any remaining type tests + * - use the object methods $isInstanceOf and $asInstanceOf + * - have a reference type as receiver + * - can be translated directly to machine instructions + * + * + * Unfortunately this phase ended up being not Y-checkable unless types are erased. A cast to an ConstantType(3) or x.type + * cannot be rewritten before erasure. + */ +trait TypeTestsCasts { + import ast.tpd._ + + // override def phaseName: String = "typeTestsCasts" + + def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show}", show = true) { + tree.fun match { + case fun @ Select(qual, selector) => + val sym = tree.symbol + + def isPrimitive(tp: Type) = tp.classSymbol.isPrimitiveValueClass + + def derivedTree(qual1: Tree, sym: Symbol, tp: Type) = + cpy.TypeApply(tree)(qual1.select(sym).withPos(qual.pos), List(TypeTree(tp))) + + def qualCls = qual.tpe.widen.classSymbol + + def transformIsInstanceOf(expr:Tree, argType: Type): Tree = { + def argCls = argType.classSymbol + if ((expr.tpe <:< argType) && isPureExpr(expr)) + Literal(Constant(true)) withPos tree.pos + else if (argCls.isPrimitiveValueClass) + if (qualCls.isPrimitiveValueClass) Literal(Constant(qualCls == argCls)) withPos tree.pos + else transformIsInstanceOf(expr, defn.boxedType(argCls.typeRef)) + else argType.dealias match { + case _: SingletonType => + val cmpOp = if (argType derivesFrom defn.AnyValClass) defn.Any_equals else defn.Object_eq + expr.select(cmpOp).appliedTo(singleton(argType)) + case AndType(tp1, tp2) => + evalOnce(expr) { fun => + val erased1 = transformIsInstanceOf(fun, tp1) + val erased2 = transformIsInstanceOf(fun, tp2) + erased1 match { + case Literal(Constant(true)) => erased2 + case _ => + erased2 match { + case Literal(Constant(true)) => erased1 + case _ => erased1 and erased2 + } + } + } + case defn.MultiArrayOf(elem, ndims) if isUnboundedGeneric(elem) => + def isArrayTest(arg: Tree) = + ref(defn.runtimeMethodRef(nme.isArray)).appliedTo(arg, Literal(Constant(ndims))) + if (ndims == 1) isArrayTest(qual) + else evalOnce(qual) { qual1 => + derivedTree(qual1, defn.Any_isInstanceOf, qual1.tpe) and isArrayTest(qual1) + } + case _ => + derivedTree(expr, defn.Any_isInstanceOf, argType) + } + } + + def transformAsInstanceOf(argType: Type): Tree = { + def argCls = argType.widen.classSymbol + if (qual.tpe <:< argType) + Typed(qual, tree.args.head) + else if (qualCls.isPrimitiveValueClass) { + if (argCls.isPrimitiveValueClass) primitiveConversion(qual, argCls) + else derivedTree(box(qual), defn.Any_asInstanceOf, argType) + } + else if (argCls.isPrimitiveValueClass) + unbox(qual.ensureConforms(defn.ObjectType), argType) + else if (isDerivedValueClass(argCls)) { + qual // adaptToType in Erasure will do the necessary type adaptation + } + else + derivedTree(qual, defn.Any_asInstanceOf, argType) + } + + /** Transform isInstanceOf OrType + * + * expr.isInstanceOf[A | B] ~~> expr.isInstanceOf[A] | expr.isInstanceOf[B] + * + * The transform happens before erasure of `argType`, thus cannot be merged + * with `transformIsInstanceOf`, which depends on erased type of `argType`. + */ + def transformOrTypeTest(qual: Tree, argType: Type): Tree = argType.dealias match { + case OrType(tp1, tp2) => + evalOnce(qual) { fun => + transformOrTypeTest(fun, tp1) + .select(nme.OR) + .appliedTo(transformOrTypeTest(fun, tp2)) + } + case _ => + transformIsInstanceOf(qual, erasure(argType)) + } + + if (sym eq defn.Any_isInstanceOf) + transformOrTypeTest(qual, tree.args.head.tpe) + else if (sym eq defn.Any_asInstanceOf) + transformAsInstanceOf(erasure(tree.args.head.tpe)) + else tree + + case _ => + tree + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala new file mode 100644 index 000000000..d474c77b4 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -0,0 +1,34 @@ +package dotty.tools.dotc +package transform + +import core._ +import TypeErasure.ErasedValueType +import Types._ +import Contexts._ +import Symbols._ +import Decorators._ +import StdNames.nme +import NameOps._ +import language.implicitConversions + +object TypeUtils { + implicit def decorateTypeUtils(tpe: Type): TypeUtils = new TypeUtils(tpe) +} + +/** A decorator that provides methods on types + * that are needed in the transformer pipeline. + */ +class TypeUtils(val self: Type) extends AnyVal { + import TypeUtils._ + + def isErasedValueType(implicit ctx: Context): Boolean = + self.isInstanceOf[ErasedValueType] + + def isPrimitiveValueType(implicit ctx: Context): Boolean = + self.classSymbol.isPrimitiveValueClass + + def ensureMethodic(implicit ctx: Context): Type = self match { + case self: MethodicType => self + case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala b/compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala new file mode 100644 index 000000000..1582158ac --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala @@ -0,0 +1,41 @@ +package dotty.tools.dotc +package transform + +import ast.{Trees, tpd} +import core._, core.Decorators._ +import Contexts._, Trees._, StdNames._, Symbols._ +import DenotTransformers._, TreeTransforms._, Phases.Phase +import ExtensionMethods._, TreeExtractors._, ValueClasses._ + +/** This phase elides unnecessary value class allocations + * + * For a value class V defined as: + * class V(val underlying: U) extends AnyVal + * we avoid unnecessary allocations: + * new V(u1) == new V(u2) => u1 == u2 + * (new V(u)).underlying() => u + */ +class VCElideAllocations extends MiniPhaseTransform with IdentityDenotTransformer { + import tpd._ + + override def phaseName: String = "vcElideAllocations" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimErasedValueType]) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = + tree match { + // new V(u1) == new V(u2) => u1 == u2 + // (We don't handle != because it has been eliminated by InterceptedMethods) + case BinaryOp(NewWithArgs(tp1, List(u1)), op, NewWithArgs(tp2, List(u2))) + if (tp1 eq tp2) && (op eq defn.Any_==) && isDerivedValueClass(tp1.typeSymbol) => + // == is overloaded in primitive classes + applyOverloaded(u1, nme.EQ, List(u2), Nil, defn.BooleanType) + + // (new V(u)).underlying() => u + case ValueClassUnbox(NewWithArgs(_, List(u))) => + u + + case _ => + tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala new file mode 100644 index 000000000..ddd414417 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala @@ -0,0 +1,104 @@ +package dotty.tools.dotc +package transform + +import ast.{Trees, tpd} +import core._, core.Decorators._ +import Contexts._, Trees._, Types._ +import DenotTransformers._, TreeTransforms._, Phases.Phase +import ExtensionMethods._, ValueClasses._ + +import collection.mutable.ListBuffer + +/** This phase inlines calls to methods of value classes. + * + * A value class V after [[ExtensionMethods]] will look like: + * class V[A, B, ...](val underlying: U) extends AnyVal { + * def foo[T, S, ...](arg1: A1, arg2: A2, ...) = + * V.foo$extension[T, S, ..., A, B, ...](this)(arg1, arg2, ...) + * + * ... + * } + * + * Let e have type V, if e is a stable prefix or if V does not have any class + * type parameter, then we can rewrite: + * e.foo[X, Y, ...](args) + * as: + * V.foo$extension[X, Y, ..., e.A, e.B, ...](e)(args) + * Otherwise, we need to evaluate e first: + * { + * val ev = e + * V.foo$extension[X, Y, ..., ev.A, ev.B, ...](ev)(args) + * } + * + * This phase needs to be placed after phases which may introduce calls to + * value class methods (like [[PatternMatcher]]). This phase uses name mangling + * to find the correct extension method corresponding to a value class method + * (see [[ExtensionMethods.extensionMethod]]), therefore we choose to place it + * before phases which may perform their own name mangling on value class + * methods (like [[TypeSpecializer]]), this way [[VCInlineMethods]] does not + * need to have any knowledge of the name mangling done by other phases. + */ +class VCInlineMethods extends MiniPhaseTransform with IdentityDenotTransformer { + import tpd._ + + override def phaseName: String = "vcInlineMethods" + + override def runsAfter: Set[Class[_ <: Phase]] = + Set(classOf[ExtensionMethods], classOf[PatternMatcher]) + + /** Replace a value class method call by a call to the corresponding extension method. + * + * @param tree The tree corresponding to the method call + * @param mtArgs Type arguments for the method call not present in `tree` + * @param mArgss Arguments for the method call not present in `tree` + * @return A tree for the extension method call + */ + private def rewire(tree: Tree, mtArgs: List[Tree] = Nil, mArgss: List[List[Tree]] = Nil) + (implicit ctx: Context): Tree = + tree match { + case Apply(qual, mArgs) => + rewire(qual, mtArgs, mArgs :: mArgss) + case TypeApply(qual, mtArgs2) => + assert(mtArgs == Nil) + rewire(qual, mtArgs2, mArgss) + case sel @ Select(qual, _) => + val origMeth = sel.symbol + val ctParams = origMeth.enclosingClass.typeParams + val extensionMeth = extensionMethod(origMeth) + + if (!ctParams.isEmpty) { + evalOnce(qual) { ev => + val ctArgs = ctParams map (ev.select(_)) + ref(extensionMeth) + .appliedToTypeTrees(mtArgs ++ ctArgs) + .appliedTo(ev) + .appliedToArgss(mArgss) + } + } else { + ref(extensionMeth) + .appliedToTypeTrees(mtArgs) + .appliedTo(qual) + .appliedToArgss(mArgss) + } + } + + /** If this tree corresponds to a fully-applied value class method call, replace it + * by a call to the corresponding extension method, otherwise return it as is. + */ + private def rewireIfNeeded(tree: Tree)(implicit ctx: Context) = tree.tpe.widen match { + case tp: MethodOrPoly => + tree // The rewiring will be handled by a fully-applied parent node + case _ => + if (isMethodWithExtension(tree.symbol)) + rewire(tree).ensureConforms(tree.tpe) + else + tree + } + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = + rewireIfNeeded(tree) + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = + rewireIfNeeded(tree) + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = + rewireIfNeeded(tree) +} diff --git a/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala b/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala new file mode 100644 index 000000000..93005c57a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala @@ -0,0 +1,56 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import Symbols._ +import SymDenotations._ +import Contexts._ +import Flags._ +import StdNames._ + +/** Methods that apply to user-defined value classes */ +object ValueClasses { + + def isDerivedValueClass(d: SymDenotation)(implicit ctx: Context) = { + !ctx.settings.XnoValueClasses.value && + !d.isRefinementClass && + d.isValueClass && + (d.initial.symbol ne defn.AnyValClass) && // Compare the initial symbol because AnyVal does not exist after erasure + !d.isPrimitiveValueClass + } + + def isMethodWithExtension(d: SymDenotation)(implicit ctx: Context) = + d.isRealMethod && + isDerivedValueClass(d.owner) && + !d.isConstructor && + !d.is(SuperAccessor) && + !d.is(Macro) + + /** The member that of a derived value class that unboxes it. */ + def valueClassUnbox(d: ClassDenotation)(implicit ctx: Context): Symbol = + // (info.decl(nme.unbox)).orElse(...) uncomment once we accept unbox methods + d.classInfo.decls + .find(d => d.isTerm && d.symbol.is(ParamAccessor)) + .map(_.symbol) + .getOrElse(NoSymbol) + + /** For a value class `d`, this returns the synthetic cast from the underlying type to + * ErasedValueType defined in the companion module. This method is added to the module + * and further described in [[ExtensionMethods]]. + */ + def u2evt(d: ClassDenotation)(implicit ctx: Context): Symbol = + d.linkedClass.info.decl(nme.U2EVT).symbol + + /** For a value class `d`, this returns the synthetic cast from ErasedValueType to the + * underlying type defined in the companion module. This method is added to the module + * and further described in [[ExtensionMethods]]. + */ + def evt2u(d: ClassDenotation)(implicit ctx: Context): Symbol = + d.linkedClass.info.decl(nme.EVT2U).symbol + + /** The unboxed type that underlies a derived value class */ + def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type = + valueClassUnbox(d).info.resultType + +} diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala new file mode 100644 index 000000000..8d926fcf0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -0,0 +1,615 @@ +package dotty.tools.dotc +package transform +package patmat + +import core.Types._ +import core.Contexts._ +import core.Flags._ +import ast.Trees._ +import ast.tpd +import core.Decorators._ +import core.Symbols._ +import core.StdNames._ +import core.NameOps._ +import core.Constants._ +import reporting.diagnostic.messages._ + +/** Space logic for checking exhaustivity and unreachability of pattern matching + * + * Space can be thought of as a set of possible values. A type or a pattern + * both refer to spaces. The space of a type is the values that inhabit the + * type. The space of a pattern is the values that can be covered by the + * pattern. + * + * Space is recursively defined as follows: + * + * 1. `Empty` is a space + * 2. For a type T, `Typ(T)` is a space + * 3. A union of spaces `S1 | S2 | ...` is a space + * 4. For a case class Kon(x1: T1, x2: T2, .., xn: Tn), if S1, S2, ..., Sn + * are spaces, then `Kon(S1, S2, ..., Sn)` is a space. + * 5. A constant `Const(value, T)` is a point in space + * 6. A stable identifier `Var(sym, T)` is a space + * + * For the problem of exhaustivity check, its formulation in terms of space is as follows: + * + * Is the space Typ(T) a subspace of the union of space covered by all the patterns? + * + * The problem of unreachable patterns can be formulated as follows: + * + * Is the space covered by a pattern a subspace of the space covered by previous patterns? + * + * Assumption: + * (1) One case class cannot be inherited directly or indirectly by another + * case class. + * (2) Inheritance of a case class cannot be well handled by the algorithm. + * + */ + + +/** space definition */ +sealed trait Space + +/** Empty space */ +case object Empty extends Space + +/** Space representing the set of all values of a type + * + * @param tp: the type this space represents + * @param decomposed: does the space result from decomposition? Used for pretty print + * + */ +case class Typ(tp: Type, decomposed: Boolean) extends Space + +/** Space representing a constructor pattern */ +case class Kon(tp: Type, params: List[Space]) extends Space + +/** Union of spaces */ +case class Or(spaces: List[Space]) extends Space + +/** Point in space */ +sealed trait Point extends Space + +/** Point representing variables(stable identifier) in patterns */ +case class Var(sym: Symbol, tp: Type) extends Point + +/** Point representing literal constants in patterns */ +case class Const(value: Constant, tp: Type) extends Point + +/** abstract space logic */ +trait SpaceLogic { + /** Is `tp1` a subtype of `tp2`? */ + def isSubType(tp1: Type, tp2: Type): Boolean + + /** Is `tp1` the same type as `tp2`? */ + def isEqualType(tp1: Type, tp2: Type): Boolean + + /** Is the type `tp` decomposable? i.e. all values of the type can be covered + * by its decomposed types. + * + * Abstract sealed class, OrType, Boolean and Java enums can be decomposed. + */ + def canDecompose(tp: Type): Boolean + + /** Return term parameter types of the case class `tp` */ + def signature(tp: Type): List[Type] + + /** Get components of decomposable types */ + def decompose(tp: Type): List[Space] + + /** Simplify space using the laws, there's no nested union after simplify */ + def simplify(space: Space): Space = space match { + case Kon(tp, spaces) => + val sp = Kon(tp, spaces.map(simplify _)) + if (sp.params.contains(Empty)) Empty + else sp + case Or(spaces) => + val set = spaces.map(simplify _).flatMap { + case Or(ss) => ss + case s => Seq(s) + } filter (_ != Empty) + + if (set.isEmpty) Empty + else if (set.size == 1) set.toList(0) + else Or(set) + case Typ(tp, _) => + if (canDecompose(tp) && decompose(tp).isEmpty) Empty + else space + case _ => space + } + + /** Flatten space to get rid of `Or` for pretty print */ + def flatten(space: Space): List[Space] = space match { + case Kon(tp, spaces) => + val flats = spaces.map(flatten _) + + flats.foldLeft(List[Kon]()) { (acc, flat) => + if (acc.isEmpty) flat.map(s => Kon(tp, Nil :+ s)) + else for (Kon(tp, ss) <- acc; s <- flat) yield Kon(tp, ss :+ s) + } + case Or(spaces) => + spaces.flatMap(flatten _) + case _ => List(space) + } + + /** Is `a` a subspace of `b`? Equivalent to `a - b == Empty`, but faster */ + def isSubspace(a: Space, b: Space): Boolean = { + def tryDecompose1(tp: Type) = canDecompose(tp) && isSubspace(Or(decompose(tp)), b) + def tryDecompose2(tp: Type) = canDecompose(tp) && isSubspace(a, Or(decompose(tp))) + + (a, b) match { + case (Empty, _) => true + case (_, Empty) => false + case (Or(ss), _) => ss.forall(isSubspace(_, b)) + case (Typ(tp1, _), Typ(tp2, _)) => + isSubType(tp1, tp2) || tryDecompose1(tp1) || tryDecompose2(tp2) + case (Typ(tp1, _), Or(ss)) => + ss.exists(isSubspace(a, _)) || tryDecompose1(tp1) + case (Typ(tp1, _), Kon(tp2, ss)) => + isSubType(tp1, tp2) && isSubspace(Kon(tp2, signature(tp2).map(Typ(_, false))), b) || + tryDecompose1(tp1) + case (Kon(tp1, ss), Typ(tp2, _)) => + isSubType(tp1, tp2) || + simplify(a) == Empty || + (isSubType(tp2, tp1) && tryDecompose1(tp1)) || + tryDecompose2(tp2) + case (Kon(_, _), Or(_)) => + simplify(minus(a, b)) == Empty + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + isEqualType(tp1, tp2) && ss1.zip(ss2).forall((isSubspace _).tupled) + case (Const(v1, _), Const(v2, _)) => v1 == v2 + case (Const(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) || tryDecompose2(tp2) + case (Const(_, _), Or(ss)) => ss.exists(isSubspace(a, _)) + case (Const(_, _), _) => false + case (_, Const(_, _)) => false + case (Var(x, _), Var(y, _)) => x == y + case (Var(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) || tryDecompose2(tp2) + case (Var(_, _), Or(ss)) => ss.exists(isSubspace(a, _)) + case (Var(_, _), _) => false + case (_, Var(_, _)) => false + } + } + + /** Intersection of two spaces */ + def intersect(a: Space, b: Space): Space = { + def tryDecompose1(tp: Type) = intersect(Or(decompose(tp)), b) + def tryDecompose2(tp: Type) = intersect(a, Or(decompose(tp))) + + (a, b) match { + case (Empty, _) | (_, Empty) => Empty + case (_, Or(ss)) => Or(ss.map(intersect(a, _)).filterConserve(_ ne Empty)) + case (Or(ss), _) => Or(ss.map(intersect(_, b)).filterConserve(_ ne Empty)) + case (Typ(tp1, _), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (isSubType(tp2, tp1)) b + else if (canDecompose(tp1)) tryDecompose1(tp1) + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Typ(tp1, _), Kon(tp2, ss)) => + if (isSubType(tp2, tp1)) b + else if (isSubType(tp1, tp2)) a // problematic corner case: inheriting a case class + else if (canDecompose(tp1)) tryDecompose1(tp1) + else Empty + case (Kon(tp1, ss), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (isSubType(tp2, tp1)) a // problematic corner case: inheriting a case class + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + if (!isEqualType(tp1, tp2)) Empty + else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) Empty + else Kon(tp1, ss1.zip(ss2).map((intersect _).tupled)) + case (Const(v1, _), Const(v2, _)) => + if (v1 == v2) a else Empty + case (Const(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Const(_, _), _) => Empty + case (Typ(tp1, _), Const(_, tp2)) => + if (isSubType(tp2, tp1)) b + else if (canDecompose(tp1)) tryDecompose1(tp1) + else Empty + case (_, Const(_, _)) => Empty + case (Var(x, _), Var(y, _)) => + if (x == y) a else Empty + case (Var(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Var(_, _), _) => Empty + case (Typ(tp1, _), Var(_, tp2)) => + if (isSubType(tp2, tp1)) b + else if (canDecompose(tp1)) tryDecompose1(tp1) + else Empty + case (_, Var(_, _)) => Empty + } + } + + /** The space of a not covered by b */ + def minus(a: Space, b: Space): Space = { + def tryDecompose1(tp: Type) = minus(Or(decompose(tp)), b) + def tryDecompose2(tp: Type) = minus(a, Or(decompose(tp))) + + (a, b) match { + case (Empty, _) => Empty + case (_, Empty) => a + case (Typ(tp1, _), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (canDecompose(tp1)) tryDecompose1(tp1) + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Typ(tp1, _), Kon(tp2, ss)) => + // corner case: inheriting a case class + // rationale: every instance of `tp1` is covered by `tp2(_)` + if (isSubType(tp1, tp2)) minus(Kon(tp2, signature(tp2).map(Typ(_, false))), b) + else if (canDecompose(tp1)) tryDecompose1(tp1) + else a + case (_, Or(ss)) => + ss.foldLeft(a)(minus) + case (Or(ss), _) => + Or(ss.map(minus(_, b))) + case (Kon(tp1, ss), Typ(tp2, _)) => + // uncovered corner case: tp2 :< tp1 + if (isSubType(tp1, tp2)) Empty + else if (simplify(a) == Empty) Empty + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + if (!isEqualType(tp1, tp2)) a + else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) a + else if (ss1.zip(ss2).forall((isSubspace _).tupled)) Empty + else + // `(_, _, _) - (Some, None, _)` becomes `(None, _, _) | (_, Some, _) | (_, _, Empty)` + Or(ss1.zip(ss2).map((minus _).tupled).zip(0 to ss2.length - 1).map { + case (ri, i) => Kon(tp1, ss1.updated(i, ri)) + }) + case (Const(v1, _), Const(v2, _)) => + if (v1 == v2) Empty else a + case (Const(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Const(_, _), _) => a + case (Typ(tp1, _), Const(_, tp2)) => // Boolean & Java enum + if (canDecompose(tp1)) tryDecompose1(tp1) + else a + case (_, Const(_, _)) => a + case (Var(x, _), Var(y, _)) => + if (x == y) Empty else a + case (Var(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Var(_, _), _) => a + case (_, Var(_, _)) => a + } + } +} + +/** Scala implementation of space logic */ +class SpaceEngine(implicit ctx: Context) extends SpaceLogic { + import tpd._ + + /** Return the space that represents the pattern `pat` + * + * If roundUp is true, approximate extractors to its type, + * otherwise approximate extractors to Empty + */ + def project(pat: Tree, roundUp: Boolean = true)(implicit ctx: Context): Space = pat match { + case Literal(c) => Const(c, c.tpe) + case _: BackquotedIdent => Var(pat.symbol, pat.tpe) + case Ident(_) | Select(_, _) => + pat.tpe.stripAnnots match { + case tp: TermRef => + if (pat.symbol.is(Enum)) + Const(Constant(pat.symbol), tp) + else if (tp.underlyingIterator.exists(_.classSymbol.is(Module))) + Typ(tp.widenTermRefExpr.stripAnnots, false) + else + Var(pat.symbol, tp) + case tp => Typ(tp, false) + } + case Alternative(trees) => Or(trees.map(project(_, roundUp))) + case Bind(_, pat) => project(pat) + case UnApply(_, _, pats) => + if (pat.tpe.classSymbol.is(CaseClass)) + Kon(pat.tpe.stripAnnots, pats.map(pat => project(pat, roundUp))) + else if (roundUp) Typ(pat.tpe.stripAnnots, false) + else Empty + case Typed(pat @ UnApply(_, _, _), _) => project(pat) + case Typed(expr, _) => Typ(expr.tpe.stripAnnots, true) + case _ => + Empty + } + + /* Erase a type binding according to erasure semantics in pattern matching */ + def erase(tp: Type): Type = { + def doErase(tp: Type): Type = tp match { + case tp: HKApply => erase(tp.superType) + case tp: RefinedType => erase(tp.parent) + case _ => tp + } + + tp match { + case OrType(tp1, tp2) => + OrType(erase(tp1), erase(tp2)) + case AndType(tp1, tp2) => + AndType(erase(tp1), erase(tp2)) + case _ => + val origin = doErase(tp) + if (origin =:= defn.ArrayType) tp else origin + } + } + + /** Is `tp1` a subtype of `tp2`? */ + def isSubType(tp1: Type, tp2: Type): Boolean = { + // check SI-9657 and tests/patmat/gadt.scala + erase(tp1) <:< erase(tp2) + } + + def isEqualType(tp1: Type, tp2: Type): Boolean = tp1 =:= tp2 + + /** Parameter types of the case class type `tp` */ + def signature(tp: Type): List[Type] = { + val ktor = tp.classSymbol.primaryConstructor.info + + val meth = ktor match { + case ktor: PolyType => + ktor.instantiate(tp.classSymbol.typeParams.map(_.typeRef)).asSeenFrom(tp, tp.classSymbol) + case _ => ktor + } + + // refine path-dependent type in params. refer to t9672 + meth.firstParamTypes.map(_.asSeenFrom(tp, tp.classSymbol)) + } + + /** Decompose a type into subspaces -- assume the type can be decomposed */ + def decompose(tp: Type): List[Space] = { + val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot => + // refer to definition of Annotation.makeChild + annot.tree match { + case Apply(TypeApply(_, List(tpTree)), _) => tpTree.symbol + } + } + + tp match { + case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true)) + case _ if tp =:= ctx.definitions.BooleanType => + List( + Const(Constant(true), ctx.definitions.BooleanType), + Const(Constant(false), ctx.definitions.BooleanType) + ) + case _ if tp.classSymbol.is(Enum) => + children.map(sym => Const(Constant(sym), tp)) + case _ => + val parts = children.map { sym => + if (sym.is(ModuleClass)) + sym.asClass.classInfo.selfType + else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef]) + refine(tp, sym.typeRef) + else + sym.typeRef + } filter { tpe => + // Child class may not always be subtype of parent: + // GADT & path-dependent types + tpe <:< expose(tp) + } + + parts.map(Typ(_, true)) + } + } + + /** Refine tp2 based on tp1 + * + * E.g. if `tp1` is `Option[Int]`, `tp2` is `Some`, then return + * `Some[Int]`. + * + * If `tp1` is `path1.A`, `tp2` is `path2.B`, and `path1` is subtype of + * `path2`, then return `path1.B`. + */ + def refine(tp1: Type, tp2: Type): Type = (tp1, tp2) match { + case (tp1: RefinedType, _) => tp1.wrapIfMember(refine(tp1.parent, tp2)) + case (tp1: HKApply, _) => refine(tp1.superType, tp2) + case (TypeRef(ref1: TypeProxy, _), tp2 @ TypeRef(ref2: TypeProxy, name)) => + if (ref1.underlying <:< ref2.underlying) TypeRef(ref1, name) else tp2 + case _ => tp2 + } + + /** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */ + def canDecompose(tp: Type): Boolean = { + tp.classSymbol.is(allOf(Abstract, Sealed)) || + tp.classSymbol.is(allOf(Trait, Sealed)) || + tp.isInstanceOf[OrType] || + tp =:= ctx.definitions.BooleanType || + tp.classSymbol.is(Enum) + } + + /** Show friendly type name with current scope in mind + * + * E.g. C.this.B --> B if current owner is C + * C.this.x.T --> x.T if current owner is C + * X[T] --> X + * C --> C if current owner is C !!! + * + */ + def showType(tp: Type): String = { + val enclosingCls = ctx.owner.enclosingClass.asClass.classInfo.symbolicTypeRef + + def isOmittable(sym: Symbol) = + sym.isEffectiveRoot || sym.isAnonymousClass || sym.name.isReplWrapperName || + ctx.definitions.UnqualifiedOwnerTypes.exists(_.symbol == sym) || + sym.showFullName.startsWith("scala.") || + sym == enclosingCls.typeSymbol + + def refinePrefix(tp: Type): String = tp match { + case NoPrefix => "" + case tp: NamedType if isOmittable(tp.symbol) => "" + case tp: ThisType => refinePrefix(tp.tref) + case tp: RefinedType => refinePrefix(tp.parent) + case tp: NamedType => tp.name.show.stripSuffix("$") + } + + def refine(tp: Type): String = tp match { + case tp: RefinedType => refine(tp.parent) + case tp: ThisType => refine(tp.tref) + case tp: NamedType => + val pre = refinePrefix(tp.prefix) + if (tp.name == tpnme.higherKinds) pre + else if (pre.isEmpty) tp.name.show.stripSuffix("$") + else pre + "." + tp.name.show.stripSuffix("$") + case _ => tp.show.stripSuffix("$") + } + + val text = tp.stripAnnots match { + case tp: OrType => showType(tp.tp1) + " | " + showType(tp.tp2) + case tp => refine(tp) + } + + if (text.isEmpty) enclosingCls.show.stripSuffix("$") + else text + } + + /** Display spaces */ + def show(s: Space): String = { + def doShow(s: Space, mergeList: Boolean = false): String = s match { + case Empty => "" + case Const(v, _) => v.show + case Var(x, _) => x.show + case Typ(tp, decomposed) => + val sym = tp.widen.classSymbol + + if (sym.is(ModuleClass)) + showType(tp) + else if (ctx.definitions.isTupleType(tp)) + signature(tp).map(_ => "_").mkString("(", ", ", ")") + else if (sym.showFullName == "scala.collection.immutable.::") + if (mergeList) "_" else "List(_)" + else if (tp.classSymbol.is(CaseClass)) + // use constructor syntax for case class + showType(tp) + signature(tp).map(_ => "_").mkString("(", ", ", ")") + else if (signature(tp).nonEmpty) + tp.classSymbol.name + signature(tp).map(_ => "_").mkString("(", ", ", ")") + else if (decomposed) "_: " + showType(tp) + else "_" + case Kon(tp, params) => + if (ctx.definitions.isTupleType(tp)) + "(" + params.map(doShow(_)).mkString(", ") + ")" + else if (tp.widen.classSymbol.showFullName == "scala.collection.immutable.::") + if (mergeList) params.map(doShow(_, mergeList)).mkString(", ") + else params.map(doShow(_, true)).filter(_ != "Nil").mkString("List(", ", ", ")") + else + showType(tp) + params.map(doShow(_)).mkString("(", ", ", ")") + case Or(_) => + throw new Exception("incorrect flatten result " + s) + } + + flatten(s).map(doShow(_, false)).distinct.mkString(", ") + } + + def checkable(tree: Match): Boolean = { + def isCheckable(tp: Type): Boolean = tp match { + case AnnotatedType(tp, annot) => + (ctx.definitions.UncheckedAnnot != annot.symbol) && isCheckable(tp) + case _ => + // Possible to check everything, but be compatible with scalac by default + ctx.settings.YcheckAllPatmat.value || + tp.typeSymbol.is(Sealed) || + tp.isInstanceOf[OrType] || + tp.typeSymbol == ctx.definitions.BooleanType.typeSymbol || + tp.typeSymbol.is(Enum) || + canDecompose(tp) || + (defn.isTupleType(tp) && tp.dealias.argInfos.exists(isCheckable(_))) + } + + val Match(sel, cases) = tree + isCheckable(sel.tpe.widen.deAnonymize.dealiasKeepAnnots) + } + + + /** Expose refined type to eliminate reference to type variables + * + * A = B M { type T = A } ~~> M { type T = B } + * + * A <: X :> Y M { type T = A } ~~> M { type T <: X :> Y } + * + * A <: X :> Y B <: U :> V M { type T <: A :> B } ~~> M { type T <: X :> V } + * + * A = X B = Y M { type T <: A :> B } ~~> M { type T <: X :> Y } + */ + def expose(tp: Type): Type = { + def follow(tp: Type, up: Boolean): Type = tp match { + case tp: TypeProxy => + tp.underlying match { + case TypeBounds(lo, hi) => + follow(if (up) hi else lo, up) + case _ => + tp + } + case OrType(tp1, tp2) => + OrType(follow(tp1, up), follow(tp2, up)) + case AndType(tp1, tp2) => + AndType(follow(tp1, up), follow(tp2, up)) + } + + tp match { + case tp: RefinedType => + tp.refinedInfo match { + case tpa : TypeAlias => + val hi = follow(tpa.alias, true) + val lo = follow(tpa.alias, false) + val refined = if (hi =:= lo) + tpa.derivedTypeAlias(hi) + else + tpa.derivedTypeBounds(lo, hi) + + tp.derivedRefinedType( + expose(tp.parent), + tp.refinedName, + refined + ) + case tpb @ TypeBounds(lo, hi) => + tp.derivedRefinedType( + expose(tp.parent), + tp.refinedName, + tpb.derivedTypeBounds(follow(lo, false), follow(hi, true)) + ) + } + case _ => tp + } + } + + def checkExhaustivity(_match: Match): Unit = { + val Match(sel, cases) = _match + val selTyp = sel.tpe.widen.deAnonymize.dealias + + + val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or(List(a, b))) + val uncovered = simplify(minus(Typ(selTyp, true), patternSpace)) + + if (uncovered != Empty) + ctx.warning(PatternMatchExhaustivity(show(uncovered)), _match.pos) + } + + def checkRedundancy(_match: Match): Unit = { + val Match(sel, cases) = _match + // ignore selector type for now + // val selTyp = sel.tpe.widen.deAnonymize.dealias + + // starts from the second, the first can't be redundant + (1 until cases.length).foreach { i => + // in redundancy check, take guard as false, take extractor as match + // nothing in order to soundly approximate + val prevs = cases.take(i).map { x => + if (x.guard.isEmpty) project(x.pat, false) + else Empty + }.reduce((a, b) => Or(List(a, b))) + + val curr = project(cases(i).pat) + + if (isSubspace(curr, prevs)) { + ctx.warning(MatchCaseUnreachable(), cases(i).body.pos) + } + } + } +} |