aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2016-11-02 11:08:28 +0100
committerGuillaume Martres <smarter@ubuntu.com>2016-11-22 01:35:07 +0100
commit8a61ff432543a29234193cd1f7c14abd3f3d31a0 (patch)
treea8147561d307af862c295cfc8100d271063bb0dd /compiler/src/dotty/tools/dotc/transform
parent6a455fe6da5ff9c741d91279a2dc6fe2fb1b472f (diff)
downloaddotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.gz
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.bz2
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.zip
Move compiler and compiler tests to compiler dir
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform')
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala59
-rw-r--r--compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala101
-rw-r--r--compiler/src/dotty/tools/dotc/transform/CapturedVars.scala149
-rw-r--r--compiler/src/dotty/tools/dotc/transform/CheckReentrant.scala95
-rw-r--r--compiler/src/dotty/tools/dotc/transform/CheckStatic.scala96
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ClassOf.scala30
-rw-r--r--compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala116
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Constructors.scala261
-rw-r--r--compiler/src/dotty/tools/dotc/transform/CrossCastAnd.scala30
-rw-r--r--compiler/src/dotty/tools/dotc/transform/CtxLazy.scala23
-rw-r--r--compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled98
-rw-r--r--compiler/src/dotty/tools/dotc/transform/DropInlined.scala15
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ElimByName.scala129
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala84
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala135
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ElimStaticThis.scala40
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Erasure.scala664
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala111
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala86
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala362
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ExplicitSelf.scala47
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala243
-rw-r--r--compiler/src/dotty/tools/dotc/transform/FirstTransform.scala193
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Flatten.scala47
-rw-r--r--compiler/src/dotty/tools/dotc/transform/FullParameterization.scala263
-rw-r--r--compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala83
-rw-r--r--compiler/src/dotty/tools/dotc/transform/GetClass.scala34
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Getters.scala76
-rw-r--r--compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala131
-rw-r--r--compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala168
-rw-r--r--compiler/src/dotty/tools/dotc/transform/LambdaLift.scala548
-rw-r--r--compiler/src/dotty/tools/dotc/transform/LazyVals.scala418
-rw-r--r--compiler/src/dotty/tools/dotc/transform/LiftTry.scala66
-rw-r--r--compiler/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala62
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled95
-rw-r--r--compiler/src/dotty/tools/dotc/transform/MacroTransform.scala70
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Memoize.scala129
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Mixin.scala257
-rw-r--r--compiler/src/dotty/tools/dotc/transform/MixinOps.scala68
-rw-r--r--compiler/src/dotty/tools/dotc/transform/MoveStatics.scala77
-rw-r--r--compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala92
-rw-r--r--compiler/src/dotty/tools/dotc/transform/NormalizeFlags.scala25
-rw-r--r--compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala140
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala94
-rw-r--r--compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala1989
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Pickler.scala108
-rw-r--r--compiler/src/dotty/tools/dotc/transform/PostTyper.scala286
-rw-r--r--compiler/src/dotty/tools/dotc/transform/PrivateToStatic.scala.disabled94
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala115
-rw-r--r--compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala67
-rw-r--r--compiler/src/dotty/tools/dotc/transform/SelectStatic.scala56
-rw-r--r--compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala48
-rw-r--r--compiler/src/dotty/tools/dotc/transform/Splitter.scala121
-rw-r--r--compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala424
-rw-r--r--compiler/src/dotty/tools/dotc/transform/SymUtils.scala117
-rw-r--r--compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala198
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TailRec.scala384
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TreeChecker.scala452
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TreeExtractors.scala48
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TreeGen.scala26
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TreeTransform.scala1221
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala99
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala124
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TypeUtils.scala34
-rw-r--r--compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala41
-rw-r--r--compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala104
-rw-r--r--compiler/src/dotty/tools/dotc/transform/ValueClasses.scala56
-rw-r--r--compiler/src/dotty/tools/dotc/transform/patmat/Space.scala615
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)
+ }
+ }
+ }
+}