From 00175e15b7bd6ef456dc8884002cae28a09957e5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 17 Sep 2014 09:55:21 +0200 Subject: [PATCH 01/70] Replace call to unimplemented method runtimeCall Use ref and appliedTo instead. --- src/dotty/tools/dotc/ast/tpd.scala | 4 +--- src/dotty/tools/dotc/transform/TypeTestsCasts.scala | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 3fa08b9c6a6f..216b1c0c5fca 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -358,7 +358,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else if (tpw isRef defn.ShortClass) Literal(Constant(0.toShort)) else Literal(Constant(null)).select(defn.Any_asInstanceOf).appliedToType(tpe) } - private class FindLocalDummyAccumulator(cls: ClassSymbol)(implicit ctx: Context) extends TreeAccumulator[Symbol] { def apply(sym: Symbol, tree: Tree) = if (sym.exists) sym @@ -565,7 +564,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def appliedToArgss(argss: List[List[Tree]])(implicit ctx: Context): Tree = ((tree: Tree) /: argss)(Apply(_, _)) - def appliedToNone(implicit ctx: Context): Tree = appliedToArgs(Nil) + def appliedToNone(implicit ctx: Context): Apply = appliedToArgs(Nil) def appliedIfMethod(implicit ctx: Context): Tree = { tree.tpe.widen match { @@ -574,7 +573,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - def appliedToType(targ: Type)(implicit ctx: Context): Tree = appliedToTypes(targ :: Nil) diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 7aaf7d86d73e..6f5f29b6034c 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -69,7 +69,7 @@ trait TypeTestsCasts { } case defn.MultiArrayType(elem, ndims) if isUnboundedGeneric(elem) => def isArrayTest(arg: Tree) = - runtimeCall(nme.isArray, arg :: Literal(Constant(ndims)) :: Nil) + ref(defn.runtimeMethod(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) From 70087826a64e027b72fc8235172bf2eee04b9d20 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 17 Sep 2014 10:35:09 +0200 Subject: [PATCH 02/70] Facturing out EnclosingMethodTraverser from CapturedVars The idea to traverse with currently enclosing methid is also used in LambdaLift and could be used elsewhere. --- src/dotty/tools/dotc/ast/tpd.scala | 16 ++++++++++++ .../tools/dotc/transform/CapturedVars.scala | 25 ++++++++----------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 216b1c0c5fca..45e0aff541eb 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -653,6 +653,22 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Ident(defn.ScalaRuntimeModule.requiredMethod(name).termRef).appliedToArgs(args) } + /** A traverser that passes the enlcosing class or method as an argumenr + * to the traverse method. + */ + abstract class EnclosingMethodTraverser(implicit ctx: Context) extends TreeAccumulator[Symbol] { + def traverse(enclMeth: Symbol, tree: Tree): Unit + def apply(enclMeth: Symbol, tree: Tree) = { + tree match { + case _: DefTree if tree.symbol.exists => + traverse(tree.symbol.enclosingMethod, tree) + case _ => + traverse(enclMeth, tree) + } + enclMeth + } + } + // ensure that constructors are fully applied? // ensure that normal methods are fully applied? diff --git a/src/dotty/tools/dotc/transform/CapturedVars.scala b/src/dotty/tools/dotc/transform/CapturedVars.scala index d5bd56bc365e..f13cd56907bb 100644 --- a/src/dotty/tools/dotc/transform/CapturedVars.scala +++ b/src/dotty/tools/dotc/transform/CapturedVars.scala @@ -27,21 +27,16 @@ class CapturedVars extends MiniPhaseTransform with SymTransformer { thisTransfor private var captured: mutable.HashSet[Symbol] = _ - private class CollectCaptured(implicit ctx: Context) extends TreeAccumulator[Symbol] { - def apply(enclMeth: Symbol, tree: Tree) = { - 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 tree: DefTree if tree.symbol.exists => - foldOver(tree.symbol.enclosingMethod, tree) - case _ => - foldOver(enclMeth, tree) - } - enclMeth + private class CollectCaptured(implicit ctx: Context) extends EnclosingMethodTraverser { + def traverse(enclMeth: Symbol, tree: Tree) = 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) = { captured = mutable.HashSet() From 8918bd94813c254dcd0e19d0c16726bb5450e27a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 17 Sep 2014 10:01:48 +0200 Subject: [PATCH 03/70] Make enteredAfter, installAfter more forgiving as to when they are called. Instead of requiring to be called a given phase, change the context if that is not the case. --- src/dotty/tools/dotc/core/Denotations.scala | 36 +++++++++---------- .../tools/dotc/core/SymDenotations.scala | 18 +++++----- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 9970c5948274..09b67d08aaa4 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -603,26 +603,26 @@ object Denotations { */ protected def installAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = { val targetId = phase.next.id - assert(ctx.phaseId == targetId, - s"denotation update for $this called in phase ${ctx.phase}, expected was ${phase.next}") - val current = symbol.current - // println(s"installing $this after $phase/${phase.id}, valid = ${current.validFor}") - // printPeriods(current) - this.nextInRun = current.nextInRun - this.validFor = Period(ctx.runId, targetId, current.validFor.lastPhaseId) - if (current.validFor.firstPhaseId == targetId) { - // replace current with this denotation - var prev = current - while (prev.nextInRun ne current) prev = prev.nextInRun - prev.nextInRun = this - current.validFor = Nowhere - } + if (ctx.phaseId != targetId) installAfter(phase)(ctx.withPhase(phase.next)) else { - // insert this denotation after current - current.validFor = Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) - current.nextInRun = this - } + val current = symbol.current + // println(s"installing $this after $phase/${phase.id}, valid = ${current.validFor}") + // printPeriods(current) + this.nextInRun = current.nextInRun + this.validFor = Period(ctx.runId, targetId, current.validFor.lastPhaseId) + if (current.validFor.firstPhaseId == targetId) { + // replace current with this denotation + var prev = current + while (prev.nextInRun ne current) prev = prev.nextInRun + prev.nextInRun = this + current.validFor = Nowhere + } else { + // insert this denotation after current + current.validFor = Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) + current.nextInRun = this + } // printPeriods(this) + } } def staleSymbolError(implicit ctx: Context) = { diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 073c9112d3d6..1316e787273e 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1425,19 +1425,17 @@ object SymDenotations { /** If this class has the same `decls` scope reference in `phase` and * `phase.next`, install a new denotation with a cloned scope in `phase.next`. - * @pre Can only be called in `phase.next`. */ - def ensureFreshScopeAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = { - assert(ctx.phaseId == phase.next.id) - val prevCtx = ctx.withPhase(phase) - val ClassInfo(pre, _, ps, decls, selfInfo) = classInfo - if (classInfo(prevCtx).decls eq decls) { - copySymDenotation( - info = ClassInfo(pre, classSymbol, ps, decls.cloneScope, selfInfo), - initFlags = this.flags &~ Frozen).installAfter(phase) + def ensureFreshScopeAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = + if (ctx.phaseId != phase.next.id) ensureFreshScopeAfter(phase)(ctx.withPhase(phase.next)) + else { + val prevCtx = ctx.withPhase(phase) + val ClassInfo(pre, _, ps, decls, selfInfo) = classInfo + if (classInfo(prevCtx).decls eq decls) + copySymDenotation(info = ClassInfo(pre, classSymbol, ps, decls.cloneScope, selfInfo)) + .installAfter(phase) } } - } private case class Uncachable(tp: Type) extends UncachedGroundType From 02ab2e85f4570b53e4eab251f6f6a047fd8d168b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 17 Sep 2014 10:03:14 +0200 Subject: [PATCH 04/70] Add isProperlyContainedIn method --- src/dotty/tools/dotc/core/SymDenotations.scala | 5 ++++- src/dotty/tools/dotc/transform/ExplicitOuter.scala | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 1316e787273e..ed123a384bc8 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -372,6 +372,9 @@ object SymDenotations { recur(symbol) } + final def isProperlyContainedIn(boundary: Symbol)(implicit ctx: Context): Boolean = + symbol != boundary && isContainedIn(boundary) + /** Is this denotation static (i.e. with no outer instance)? */ final def isStatic(implicit ctx: Context) = (this is Static) || this.exists && owner.isStaticOwner @@ -916,7 +919,7 @@ object SymDenotations { privateWithin: Symbol = null, annotations: List[Annotation] = null)(implicit ctx: Context) = { // simulate default parameters, while also passing implicit context ctx to the default values - val initFlags1 = if (initFlags != UndefinedFlags) initFlags else this.flags &~ Frozen + val initFlags1 = (if (initFlags != UndefinedFlags) initFlags else this.flags) &~ Frozen val info1 = if (info != null) info else this.info val privateWithin1 = if (privateWithin != null) privateWithin else this.privateWithin val annotations1 = if (annotations != null) annotations else this.annotations diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index aeda6a1d30e3..e07be8603d1b 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -190,7 +190,7 @@ object ExplicitOuter { */ def referencesOuter(cls: Symbol, tree: Tree)(implicit ctx: Context): Boolean = { def isOuter(sym: Symbol) = - sym != cls && !sym.isStaticOwner && cls.isContainedIn(sym) + !sym.isStaticOwner && cls.isProperlyContainedIn(sym) tree match { case thisTree @ This(_) => isOuter(thisTree.symbol) From 117b643d0c20aebac6363057d4043ac2cbb817fe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Sep 2014 17:21:39 +0200 Subject: [PATCH 05/70] Fix lifting of application: Lifting an application `f(arg1).f(args)` requires lifting of the whole prefix `f(arg1)`, because `f` might have a side effect. --- src/dotty/tools/dotc/typer/EtaExpansion.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/EtaExpansion.scala b/src/dotty/tools/dotc/typer/EtaExpansion.scala index 7c1130b83378..394accd03ae3 100644 --- a/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -84,7 +84,7 @@ object EtaExpansion { case TypeApply(fn, targs) => cpy.TypeApply(tree)(liftApp(defs, fn), targs) case Select(pre, name) if isPureRef(tree) => - cpy.Select(tree)(liftApp(defs, pre), name) + cpy.Select(tree)(liftPrefix(defs, pre), name) case Block(stats, expr) => liftApp(defs ++= stats, expr) case New(tpt) => @@ -93,6 +93,18 @@ object EtaExpansion { lift(defs, tree) } + /** Lift prefix `pre` of an application `pre.f(...)` to + * + * val x0 = pre + * x0.f(...) + * + * unless `pre` is a `New` or `pre` is idempotent. + */ + def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = tree match { + case New(_) => tree + case _ => if (isIdempotentExpr(tree)) tree else lift(defs, tree) + } + /** Eta-expanding a tree means converting a method reference to a function value. * @param tree The tree to expand * @param mt The type of the method reference From 12a624a75d05372002e4308f4b2830847ca88d3e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Sep 2014 17:30:04 +0200 Subject: [PATCH 06/70] Replace tree.withName with {tpd|untpd}.rename.tree Advantage: Can rename typed as well as untyped trees. --- src/dotty/tools/dotc/ast/Trees.scala | 21 ++++++++++++------- src/dotty/tools/dotc/ast/untpd.scala | 10 ++++++--- .../tools/dotc/transform/CapturedVars.scala | 8 +++---- src/dotty/tools/dotc/typer/Applications.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 5 +++-- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index b63f0ad8cc03..19f52b08fdaa 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -408,7 +408,6 @@ object Trees { abstract class NameTree[-T >: Untyped] extends DenotingTree[T] { type ThisTree[-T >: Untyped] <: NameTree[T] def name: Name - def withName(name1: Name)(implicit ctx: Context): untpd.NameTree } /** Tree refers by name to a denotation */ @@ -449,7 +448,6 @@ object Trees { case class Ident[-T >: Untyped] private[ast] (name: Name) extends RefTree[T] { type ThisTree[-T >: Untyped] = Ident[T] - def withName(name: Name)(implicit ctx: Context): untpd.Ident = untpd.cpy.Ident(this)(name) def qualifier: Tree[T] = genericEmptyTree } @@ -460,7 +458,6 @@ object Trees { case class Select[-T >: Untyped] private[ast] (qualifier: Tree[T], name: Name) extends RefTree[T] { type ThisTree[-T >: Untyped] = Select[T] - def withName(name: Name)(implicit ctx: Context): untpd.Select = untpd.cpy.Select(this)(qualifier, name) } class SelectWithSig[-T >: Untyped] private[ast] (qualifier: Tree[T], name: Name, val sig: Signature) @@ -656,7 +653,6 @@ object Trees { case class SelectFromTypeTree[-T >: Untyped] private[ast] (qualifier: Tree[T], name: Name) extends RefTree[T] { type ThisTree[-T >: Untyped] = SelectFromTypeTree[T] - def withName(name: Name)(implicit ctx: Context): untpd.SelectFromTypeTree = untpd.cpy.SelectFromTypeTree(this)(qualifier, name) } /** left & right */ @@ -702,7 +698,6 @@ object Trees { extends NameTree[T] with DefTree[T] with PatternTree[T] { type ThisTree[-T >: Untyped] = Bind[T] override def envelope: Position = pos union initialPos - def withName(name: Name)(implicit ctx: Context): untpd.Bind = untpd.cpy.Bind(this)(name, body) } /** tree_1 | ... | tree_n */ @@ -734,7 +729,6 @@ object Trees { case class ValDef[-T >: Untyped] private[ast] (mods: Modifiers[T], name: TermName, tpt: Tree[T], rhs: Tree[T]) extends ValOrDefDef[T] { type ThisTree[-T >: Untyped] = ValDef[T] - def withName(name: Name)(implicit ctx: Context): untpd.ValDef = untpd.cpy.ValDef(this)(name = name.toTermName) assert(isEmpty || tpt != genericEmptyTree) } @@ -742,7 +736,6 @@ object Trees { case class DefDef[-T >: Untyped] private[ast] (mods: Modifiers[T], name: TermName, tparams: List[TypeDef[T]], vparamss: List[List[ValDef[T]]], tpt: Tree[T], rhs: Tree[T]) extends ValOrDefDef[T] { type ThisTree[-T >: Untyped] = DefDef[T] - def withName(name: Name)(implicit ctx: Context): untpd.DefDef = untpd.cpy.DefDef(this)(name = name.toTermName) assert(tpt != genericEmptyTree) } @@ -754,7 +747,6 @@ object Trees { case class TypeDef[-T >: Untyped] private[ast] (mods: Modifiers[T], name: TypeName, rhs: Tree[T]) extends MemberDef[T] { type ThisTree[-T >: Untyped] = TypeDef[T] - def withName(name: Name)(implicit ctx: Context): untpd.TypeDef = untpd.cpy.TypeDef(this)(name = name.toTypeName) /** Is this a definition of a class? */ def isClassDef = rhs.isInstanceOf[Template[_]] @@ -1361,6 +1353,19 @@ object Trees { else foldOver(x1, tree) } } + + def rename(tree: NameTree, newName: Name)(implicit ctx: Context): tree.ThisTree[T] = { + tree match { + case tree: Ident => cpy.Ident(tree)(newName) + case tree: Select => cpy.Select(tree)(tree.qualifier, newName) + case tree: Bind => cpy.Bind(tree)(newName, tree.body) + case tree: ValDef => cpy.ValDef(tree)(name = newName.asTermName) + case tree: DefDef => cpy.DefDef(tree)(name = newName.asTermName) + case tree: TypeDef => cpy.TypeDef(tree)(name = newName.asTypeName) + case tree: SelectFromTypeTree => cpy.SelectFromTypeTree(tree)(tree.qualifier, newName) + case tree: untpd.PolyTypeDef => untpd.cpy.PolyTypeDef(tree)(tree.mods, newName.asTypeName, tree.tparams, tree.rhs) + } + }.asInstanceOf[tree.ThisTree[T]] } } // ----- Helper functions and classes --------------------------------------- diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 9ac01df9cc93..731917c42bea 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -58,9 +58,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree) extends DefTree class PolyTypeDef(mods: Modifiers, name: TypeName, override val tparams: List[TypeDef], rhs: Tree) - extends TypeDef(mods, name, rhs) { - override def withName(name: Name)(implicit ctx: Context) = cpy.PolyTypeDef(this)(mods, name.toTypeName, tparams, rhs) - } + extends TypeDef(mods, name, rhs) // ----- TypeTrees that refer to other tree's symbols ------------------- @@ -414,4 +412,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { super.foldOver(x, tree) } } + + override def rename(tree: NameTree, newName: Name)(implicit ctx: Context): tree.ThisTree[Untyped] = tree match { + case t: PolyTypeDef => + cpy.PolyTypeDef(t)(t.mods, newName.asTypeName, t.tparams, t.rhs).asInstanceOf[tree.ThisTree[Untyped]] + case _ => super.rename(tree, newName) + } } diff --git a/src/dotty/tools/dotc/transform/CapturedVars.scala b/src/dotty/tools/dotc/transform/CapturedVars.scala index f13cd56907bb..59a7668193ad 100644 --- a/src/dotty/tools/dotc/transform/CapturedVars.scala +++ b/src/dotty/tools/dotc/transform/CapturedVars.scala @@ -17,13 +17,13 @@ import SymUtils._ import collection.{ mutable, immutable } import collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet } -class CapturedVars extends MiniPhaseTransform with SymTransformer { thisTransformer => +class CapturedVars extends MiniPhaseTransform with SymTransformer { thisTransform => import ast.tpd._ /** the following two members override abstract members in Transform */ val phaseName: String = "capturedVars" - override def treeTransformPhase = thisTransformer.next + override def treeTransformPhase = thisTransform.next private var captured: mutable.HashSet[Symbol] = _ @@ -64,7 +64,7 @@ class CapturedVars extends MiniPhaseTransform with SymTransformer { thisTransfor } def capturedType(vble: Symbol)(implicit ctx: Context): Type = { - val oldInfo = vble.denot(ctx.withPhase(thisTransformer)).info + val oldInfo = vble.denot(ctx.withPhase(thisTransform)).info refCls(oldInfo.classSymbol, vble.isVolatile).typeRef } @@ -86,7 +86,7 @@ class CapturedVars extends MiniPhaseTransform with SymTransformer { thisTransfor 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(thisTransformer)).info) + (id select nme.elem).ensureConforms(vble.denot(ctx.withPhase(thisTransform)).info) else id } diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 2ff116f461b5..77298e61039a 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -594,7 +594,7 @@ trait Applications extends Compatibility { self: Typer => def followTypeAlias(tree: untpd.Tree): untpd.Tree = { tree match { case tree: untpd.RefTree => - val ttree = typedType(tree.withName(tree.name.toTypeName)) + val ttree = typedType(untpd.rename(tree, tree.name.toTypeName)) ttree.tpe match { case alias: TypeRef if alias.info.isAlias => companionRef(alias) match { diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 9066012f035a..9c1110dd1c40 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -390,7 +390,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case lhsCore: RefTree if setter.exists => val setterTypeRaw = pre select (setterName, setter) val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.pos) - val lhs2 = lhsCore.withName(setterName).withType(setterType) + val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType) typed(cpy.Apply(tree)(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) case _ => reassignmentToVal @@ -1009,7 +1009,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - protected def encodeName(tree: untpd.NameTree)(implicit ctx: Context) = tree withName tree.name.encode + protected def encodeName(tree: untpd.NameTree)(implicit ctx: Context): untpd.NameTree = + untpd.rename(tree, tree.name.encode) def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ ctx.traceIndented (i"typing $tree", typr, show = true) /*<|<*/ { assertPositioned(tree) From 14b539e23c476e8025404666d933d9cc78d2b300 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Sep 2014 17:36:12 +0200 Subject: [PATCH 07/70] Take receiver into account when selecting default getters. Previously, we determined the default getters solely from the method TermRef type. This is wrong if the method is prefix is not a path -- the prefix of the term ref will be a TypeRef and the getter selection will be a SelectFromType tree, which makes no sense. --- src/dotty/tools/dotc/typer/Applications.scala | 96 ++++++++++--------- 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 77298e61039a..035f19028d7c 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -132,8 +132,7 @@ trait Applications extends Compatibility { self: Typer => protected def appPos: Position - /** If constructing trees, the current function part, which might be - * affected by lifting. EmptyTree otherwise. + /** The current function part, which might be affected by lifting. */ protected def normalizedFun: Tree @@ -251,40 +250,52 @@ trait Applications extends Compatibility { self: Typer => /** Find reference to default parameter getter for parameter #n in current * parameter list, or NoType if none was found */ - def findDefaultGetter(n: Int)(implicit ctx: Context): Type = { - val meth = methRef.symbol - val prefix = - if ((meth is Synthetic) && meth.name == nme.apply) nme.CONSTRUCTOR else methRef.name - def getterName = prefix.defaultGetterName(n) - def ref(pre: Type, sym: Symbol): Type = - if (pre.exists && sym.isTerm) pre select sym else NoType - if (meth.hasDefaultParams) - methRef.prefix match { - case NoPrefix => - def findDefault(cx: Context): Type = { - if (cx eq NoContext) NoType - else if (cx.scope != cx.outer.scope && - cx.denotNamed(methRef.name).hasAltWith(_.symbol == meth)) { - val denot = cx.denotNamed(getterName) - assert(denot.exists, s"non-existent getter denotation ($denot) for getter($getterName)") - cx.owner.thisType.select(getterName, denot) - } else findDefault(cx.outer) - } - findDefault(ctx) - case mpre => - val cls = meth.owner - val pre = - if (meth.isClassConstructor) { - // default getters for class constructors are found in the companion object - mpre.baseTypeRef(cls) match { - case tp: TypeRef => ref(tp.prefix, cls.companionModule) - case _ => NoType - } - } else mpre - val getter = pre.member(getterName) - ref(pre, getter.symbol) + def findDefaultGetter(n: Int)(implicit ctx: Context): Tree = { + val meth = methRef.symbol.asTerm + val receiver: Tree = methPart(normalizedFun) match { + case Select(receiver, _) => receiver + case mr => mr.tpe.normalizedPrefix match { + case mr: TermRef => ref(mr) + case _ => EmptyTree + } + } + val getterPrefix = + if ((meth is Synthetic) && meth.name == nme.apply) nme.CONSTRUCTOR else meth.name + def getterName = getterPrefix.defaultGetterName(n) + if (!meth.hasDefaultParams) + EmptyTree + else if (receiver.isEmpty) { + def findGetter(cx: Context): Tree = { + if (cx eq NoContext) EmptyTree + else if (cx.scope != cx.outer.scope && + cx.denotNamed(meth.name).hasAltWith(_.symbol == meth)) { + val denot = cx.denotNamed(getterName) + assert(denot.exists, s"non-existent getter denotation ($denot) for getter($getterName)") + ref(TermRef(cx.owner.thisType, getterName, denot)) + } else findGetter(cx.outer) + } + findGetter(ctx) + } + else { + def selectGetter(qual: Tree): Tree = { + val getterDenot = qual.tpe.member(getterName) + if (getterDenot.exists) qual.select(TermRef(qual.tpe, getterName, getterDenot)) + else EmptyTree + } + if (!meth.isClassConstructor) + selectGetter(receiver) + else { + // default getters for class constructors are found in the companion object + val cls = meth.owner + val companion = cls.companionModule + receiver.tpe.baseTypeRef(cls) match { + case tp: TypeRef if companion.isTerm => + selectGetter(ref(TermRef(tp.prefix, companion.asTerm))) + case _ => + EmptyTree + } } - else NoType + } } /** Match re-ordered arguments against formal parameters @@ -305,13 +316,12 @@ trait Applications extends Compatibility { self: Typer => } def tryDefault(n: Int, args1: List[Arg]): Unit = { - findDefaultGetter(n + numArgs(normalizedFun)) match { - case dref: NamedType => - liftFun() - addTyped(treeToArg(spliceMeth(ref(dref) withPos appPos, normalizedFun)), formal) - matchArgs(args1, formals1, n + 1) - case _ => - missingArg(n) + liftFun() + val getter = findDefaultGetter(n + numArgs(normalizedFun)) + if (getter.isEmpty) missingArg(n) + else { + addTyped(treeToArg(spliceMeth(getter withPos appPos, normalizedFun)), formal) + matchArgs(args1, formals1, n + 1) } } @@ -364,7 +374,7 @@ trait Applications extends Compatibility { self: Typer => def fail(msg: => String) = ok = false def appPos = NoPosition - def normalizedFun = EmptyTree + lazy val normalizedFun = ref(methRef) init() } From 59ae5e6443980569877b8072f23947493463f1ba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Sep 2014 17:36:54 +0200 Subject: [PATCH 08/70] Treat @static as a source flag. It changes meaning drastically so should always be visible. --- src/dotty/tools/dotc/core/Flags.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index e34483f8f75f..64c87ecb865e 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -421,7 +421,7 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = commonFlags(Private, Protected, Abstract, Final, - Sealed, Case, Implicit, Override, AbsOverride, Lazy) + Sealed, Case, Implicit, Override, AbsOverride, Lazy, Static) /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = From 8f18250f4fe494e0b6314fc2cb8e501753e79064 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Sep 2014 17:38:43 +0200 Subject: [PATCH 09/70] Allow relaxed typing of applications Some transformations encounter applications where new arguments ahve to be supplied. The method type already accounts for these argument but the Application node passed into TreeTransform#transformApply is constructed with a cpy.Apply operation which does a type assignment. That type assignment fails with a parameter mismatch unless relaxedTyping is on. --- src/dotty/tools/dotc/core/Phases.scala | 5 +++++ src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 5eb8cd920b18..53c193994457 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -200,6 +200,11 @@ object Phases { */ def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = () + /** If set, allow missing or superfluous arguments in applications + * and type applications. + */ + def relaxedTyping: Boolean = false + def exists: Boolean = true private var myPeriod: Period = Periods.InvalidPeriod diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index c23b820e4a57..e35dd6c920d7 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -246,7 +246,7 @@ trait TypeAssigner { def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(implicit ctx: Context) = { val ownType = fn.tpe.widen match { case fntpe @ MethodType(_, ptypes) => - if (sameLength(ptypes, args)) fntpe.instantiate(args.tpes) + if (sameLength(ptypes, args) || ctx.phase.prev.relaxedTyping) fntpe.instantiate(args.tpes) else errorType(i"wrong number of parameters for ${fn.tpe}; expected: ${ptypes.length}", tree.pos) case t => errorType(i"${err.exprStr(fn)} does not take parameters", tree.pos) @@ -258,7 +258,7 @@ trait TypeAssigner { val ownType = fn.tpe.widen match { case pt: PolyType => val argTypes = args.tpes - if (sameLength(argTypes, pt.paramNames)) pt.instantiate(argTypes) + if (sameLength(argTypes, pt.paramNames)|| ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) else errorType(d"wrong number of type parameters for ${fn.tpe}; expected: ${pt.paramNames.length}", tree.pos) case _ => errorType(i"${err.exprStr(fn)} does not take type parameters", tree.pos) From 67e0313c046f0cfea7dab8ef6e6c6136d12e61f1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Sep 2014 19:52:07 +0200 Subject: [PATCH 10/70] Take environment into account when typing a closure. Environment parameters do not count in th eresult type. --- src/dotty/tools/dotc/core/Types.scala | 8 ++++++-- src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 +++- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 529452c733ac..6145bdffb809 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -906,10 +906,14 @@ object Types { /** Turn type into a function type. * @pre this is a non-dependent method type. + * @param drop The number of trailing parameters that should be dropped + * when forming the function type. */ - def toFunctionType(implicit ctx: Context): Type = this match { + def toFunctionType(dropLast: Int = 0)(implicit ctx: Context): Type = this match { case mt @ MethodType(_, formals) if !mt.isDependent => - defn.FunctionType(formals mapConserve (_.underlyingIfRepeated(mt.isJava)), mt.resultType) + val formals1 = if (dropLast == 0) formals else formals dropRight dropLast + defn.FunctionType( + formals1 mapConserve (_.underlyingIfRepeated(mt.isJava)), mt.resultType) } /** The signature of this type. This is by default NotAMethod, diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index e35dd6c920d7..5604a607c9e7 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -285,7 +285,9 @@ trait TypeAssigner { tree.withType(thenp.tpe | elsep.tpe) def assignType(tree: untpd.Closure, meth: Tree, target: Tree)(implicit ctx: Context) = - tree.withType(if (target.isEmpty) meth.tpe.widen.toFunctionType else target.tpe) + tree.withType( + if (target.isEmpty) meth.tpe.widen.toFunctionType(tree.env.length) + else target.tpe) def assignType(tree: untpd.CaseDef, body: Tree)(implicit ctx: Context) = tree.withType(body.tpe) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 9c1110dd1c40..ebd33a6ffce6 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1273,7 +1273,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) => pt match { case SAMType(meth) - if wtp <:< meth.info.toFunctionType => + if wtp <:< meth.info.toFunctionType() => // was ... && isFullyDefined(pt, ForceDegree.noBottom) // but this prevents case blocks from implementing polymorphic partial functions, // since we do not know the result parameter a priori. Have to wait until the From c93f545dc344d4acebdb84dc76eae062eaf67ce0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Sep 2014 19:54:32 +0200 Subject: [PATCH 11/70] Erasure should keep prefix in termrefs. The current formulation of lambda lift is easier to do if that's the case. --- src/dotty/tools/dotc/TypeErasure.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index 1212c9e4dac8..1e3bc5ca6841 100644 --- a/src/dotty/tools/dotc/TypeErasure.scala +++ b/src/dotty/tools/dotc/TypeErasure.scala @@ -8,8 +8,8 @@ import util.DotClass /** Erased types are: * - * TypeRef(prefix is ignored, denot is ClassDenotation) - * TermRef(prefix is ignored, denot is SymDenotation) + * TypeRefWithFixedSym(denot is ClassDenotation) + * TermRefWithFixedSym(denot is SymDenotation) * JavaArrayType * AnnotatedType * MethodType @@ -237,7 +237,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild else this(parent) case tp: TermRef => assert(tp.symbol.exists, tp) - TermRef(NoPrefix, tp.symbol.asTerm) + TermRef(this(tp.prefix), tp.symbol.asTerm) case ThisType(_) | SuperType(_, _) => tp case ExprType(rt) => From 8975b85958e4d600eebb843c4847e52486c33f46 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:13:09 +0200 Subject: [PATCH 12/70] Drop type declarations from erased ClassInfo Need to drop all non-class type declarations. --- src/dotty/tools/dotc/TypeErasure.scala | 3 ++- src/dotty/tools/dotc/core/Scopes.scala | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index 1e3bc5ca6841..f6c62cf30b8b 100644 --- a/src/dotty/tools/dotc/TypeErasure.scala +++ b/src/dotty/tools/dotc/TypeErasure.scala @@ -269,7 +269,8 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild val parents: List[TypeRef] = if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil else removeLaterObjects(classParents.mapConserve(eraseTypeRef)) - tp.derivedClassInfo(NoPrefix, parents, decls, this(tp.selfType)) + val erasedDecls = decls.filteredScope(d => !d.isType || d.isClass) + tp.derivedClassInfo(NoPrefix, parents, erasedDecls, this(tp.selfType)) // can't replace selftype by NoType because this would lose the sourceModule link } case NoType | NoPrefix | ErrorType | JavaArrayType(_) => diff --git a/src/dotty/tools/dotc/core/Scopes.scala b/src/dotty/tools/dotc/core/Scopes.scala index 54317a4962c8..3f7a4cb2ca20 100644 --- a/src/dotty/tools/dotc/core/Scopes.scala +++ b/src/dotty/tools/dotc/core/Scopes.scala @@ -106,7 +106,10 @@ object Scopes { def next(): Symbol = { val r = e.sym; e = lookupNextEntry(e); r } } - /** The denotation set of all the symbols with given name in this scope */ + /** The denotation set of all the symbols with given name in this scope + * Symbols occur in the result in reverse order relative to their occurrence + * in `this.toList`. + */ final def denotsNamed(name: Name, select: SymDenotation => Boolean = selectAll)(implicit ctx: Context): PreDenotation = { var syms: PreDenotation = NoDenotation var e = lookupEntry(name) @@ -118,6 +121,20 @@ object Scopes { syms } + /** The scope that keeps only those symbols from this scope that match the + * given predicates. If all symbols match, returns the scope itself, otherwise + * a copy with the matching symbols. + */ + final def filteredScope(p: Symbol => Boolean)(implicit ctx: Context): Scope = { + var result: MutableScope = null + for (sym <- iterator) + if (!p(sym)) { + if (result == null) result = cloneScope + result.unlink(sym) + } + if (result == null) this else result + } + def implicitDecls(implicit ctx: Context): List[TermRef] = Nil final def toText(printer: Printer): Text = printer.toText(this) From d9a911c43bbedcae8a787eafb91bb6889a8ff461 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:39:33 +0200 Subject: [PATCH 13/70] Uncurry also in DefDef trees, not just in infos. --- src/dotty/tools/dotc/transform/Erasure.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index aae1ff1baf79..e99a3a91ce35 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -364,7 +364,7 @@ object Erasure extends TypeTestsCasts{ override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = { val ddef1 = untpd.cpy.DefDef(ddef)( tparams = Nil, - vparamss = if (ddef.vparamss.isEmpty) Nil :: Nil else ddef.vparamss, + vparamss = ddef.vparamss.flatten :: Nil, tpt = // keep UnitTypes intact in result position if (ddef.tpt.typeOpt isRef defn.UnitClass) untpd.TypeTree(defn.UnitType) withPos ddef.tpt.pos else ddef.tpt) From ce81244e6c4fc7b5b8ccc70ad32290045a591739 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:40:41 +0200 Subject: [PATCH 14/70] Fix handling of type params in secondary constructors Params are already added by Desugar. No special treatment needed here. Besides primaryConstructor.typeParams is always empty, because term symbols do not have type parameters. The fix turns t2660.scala into an error. I believe the error is correct, hence the test was moved with a comment to neg. --- src/dotty/tools/dotc/typer/Namer.scala | 5 +-- tests/neg/t2660.scala | 47 ++++++++++++++++++++++++++ tests/pos/t2660.scala | 25 -------------- 3 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 tests/neg/t2660.scala delete mode 100644 tests/pos/t2660.scala diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 5ceab475ebb5..3a1f0a98b461 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -632,10 +632,7 @@ class Namer { typer: Typer => completeParams(tparams) vparamss foreach completeParams val isConstructor = name == nme.CONSTRUCTOR - val isSecondaryConstructor = isConstructor && sym != sym.owner.primaryConstructor - def typeParams = - if (isSecondaryConstructor) sym.owner.primaryConstructor.typeParams - else tparams map symbolOfTree + def typeParams = tparams map symbolOfTree def wrapMethType(restpe: Type): Type = { var paramSymss = vparamss.nestedMap(symbolOfTree) // Make sure constructor has one non-implicit parameter list diff --git a/tests/neg/t2660.scala b/tests/neg/t2660.scala new file mode 100644 index 000000000000..85e318915db5 --- /dev/null +++ b/tests/neg/t2660.scala @@ -0,0 +1,47 @@ +// Dotty deviation. The calls here now are classified as ambiguous. + +package hoho + +class G + +class H extends G + +class A[T](x: T) { + + def this(y: G, z: T) = { + this(z) + print(1) + } + + def this(z: H, h: T) = { + this(h) + print(2) + } +} + +object T { + def main(args: Array[String]): Unit = { + implicit def g2h(g: G): H = new H + new A[Int](new H, 23) + // in the context here, either secondary constructor is applicable + // to the other, due to the implicit in scope. So the call is ambiguous. + } +} + + +// A version of t2660 which does not use constructors + +object X { + def f[T](x: T) = ??? + def f[T](y: G, z: T) = ??? + def f[T](z: H, h: T) = ??? +} + +object T2 { + def main(args: Array[String]): Unit = { + implicit def g2h(g: G): H = new H + X.f(new H, 23) + } +} + + diff --git a/tests/pos/t2660.scala b/tests/pos/t2660.scala deleted file mode 100644 index 94a40f7403e8..000000000000 --- a/tests/pos/t2660.scala +++ /dev/null @@ -1,25 +0,0 @@ -package hoho - -class G - -class H extends G - -class A[T](x: T) { - - def this(y: G, z: T) = { - this(z) - print(1) - } - - def this(z: H, h: T) = { - this(h) - print(2) - } -} - -object T { - def main(args: Array[String]): Unit = { - implicit def g2h(g: G): H = new H - new A[Int](new H, 23) - } -} From d288981f42cd8fde42340264a73d30e037bffec5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:45:36 +0200 Subject: [PATCH 15/70] Fix tree typing to account for trees after constructors is run. --- .../tools/dotc/transform/TreeChecker.scala | 10 +++---- src/dotty/tools/dotc/typer/ReTyper.scala | 3 ++ src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 29 ++++++++++--------- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index ef5baca0729f..5412b66b8bc6 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -46,7 +46,6 @@ class TreeChecker { case _ => Nil } - def check(phasesToRun: Seq[Phase], ctx: Context) = { println(s"checking ${ctx.compilationUnit} after phase ${ctx.phase.prev}") val checkingCtx = ctx.fresh @@ -99,7 +98,8 @@ class TreeChecker { 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) + ctxOwner.isWeakOwner && !(ctxOwner is Method | Lazy | Mutable) && + ownerMatches(symOwner, ctxOwner.owner) if(!ownerMatches(tree.symbol.owner, ctx.owner)) { assert(ownerMatches(tree.symbol.owner, ctx.owner), i"bad owner; ${tree.symbol} has owner ${tree.symbol.owner}, expected was ${ctx.owner}\n" + @@ -130,10 +130,10 @@ class TreeChecker { super.typedStats(trees, exprOwner) } - override def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = { - if (ctx.mode.isExpr) - if(!(tree.tpe <:< pt)) + def isPrimaryConstructorReturn = + ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass) + if (ctx.mode.isExpr && !isPrimaryConstructorReturn) assert(tree.tpe <:< pt, s"error at ${sourcePos(tree.pos)}\n" + err.typeMismatchStr(tree.tpe, pt)) diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 31a776fc98f8..6895f997874a 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -71,6 +71,9 @@ class ReTyper extends Typer { override def addTypedModifiersAnnotations(mods: untpd.Modifiers, sym: Symbol)(implicit ctx: Context): Modifiers = typedModifiers(mods, sym) + override def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(implicit ctx: Context): List[Tree] = + parents + override def encodeName(tree: untpd.NameTree)(implicit ctx: Context) = tree override def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree = fun.tpe match { diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 5604a607c9e7..79d86de06789 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -235,7 +235,7 @@ trait TypeAssigner { } val owntype = if (!mix.isEmpty) findMixinSuper(cls.info) - else if (inConstrCall) cls.info.firstParent + else if (inConstrCall || ctx.erasedTypes) cls.info.firstParent else { val ps = cls.info.parents if (ps.isEmpty) defn.AnyType else ps.reduceLeft((x: Type, y: Type) => x & y) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index ebd33a6ffce6..46fd17d15822 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -374,8 +374,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case lhs => val lhsCore = typedUnadapted(lhs) def lhs1 = typed(untpd.TypedSplice(lhsCore)) + def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields + sym.is(Mutable, butNot = Accessor) || + ctx.owner.isPrimaryConstructor && !sym.is(Method) && sym.owner == ctx.owner.owner lhsCore.tpe match { - case ref: TermRef if ref.symbol is (Mutable, butNot = Accessor) => + case ref: TermRef if canAssign(ref.symbol) => assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, ref.info))) case _ => def reassignmentToVal = @@ -843,20 +846,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit result } - /** If this is a real class, make sure its first parent is a - * constructor call. Cannot simply use a type. - */ - def ensureConstrCall(parents: List[Tree]): List[Tree] = { - val firstParent :: otherParents = parents - if (firstParent.isType && !(cls is Trait)) - typed(untpd.New(untpd.TypedSplice(firstParent), Nil))(superCtx) :: otherParents - else parents - } - val mods1 = addTypedModifiersAnnotations(mods, cls) val constr1 = typed(constr).asInstanceOf[DefDef] - val parents1 = ensureConstrCall(ensureFirstIsClass( - parents mapconserve typedParent, cdef.pos.toSynthetic)) + val parentsWithClass = ensureFirstIsClass(parents mapconserve typedParent, cdef.pos.toSynthetic) + val parents1 = ensureConstrCall(cls, parentsWithClass)(superCtx) val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible val dummy = localDummy(cls, impl) val body1 = typedStats(body, dummy)(inClassContext(self1.symbol)) @@ -875,6 +868,16 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // 4. Polymorphic type defs override nothing. } + /** If this is a real class, make sure its first parent is a + * constructor call. Cannot simply use a type. Overridden in ReTyper. + */ + def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(implicit ctx: Context): List[Tree] = { + val firstParent :: otherParents = parents + if (firstParent.isType && !(cls is Trait)) + typed(untpd.New(untpd.TypedSplice(firstParent), Nil)) :: otherParents + else parents + } + /** Overridden in retyper */ def checkVariance(tree: Tree)(implicit ctx: Context) = VarianceChecker.check(tree) From d32ff05d101f860dc1b290330d34caa9aea6e1df Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:48:10 +0200 Subject: [PATCH 16/70] New utility methods in SymUtils --- src/dotty/tools/dotc/ast/TreeTypeMap.scala | 9 ++----- src/dotty/tools/dotc/transform/SymUtils.scala | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 56602cca91e1..33be848c1ed1 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -6,6 +6,7 @@ import core._ import Types._, Contexts._, Constants._, Names._, Flags._ import SymDenotations._, Symbols._, Annotations._, Trees._, Symbols._ import Denotations._, Decorators._ +import dotty.tools.dotc.transform.SymUtils._ /** A map that applies three functions and a substitution together to a tree and * makes sure they are coordinated so that the result is well-typed. The functions are @@ -42,13 +43,7 @@ final class TreeTypeMap( import tpd._ /** If `sym` is one of `oldOwners`, replace by corresponding symbol in `newOwners` */ - def mapOwner(sym: Symbol) = { - def loop(from: List[Symbol], to: List[Symbol]): Symbol = - if (from.isEmpty) sym - else if (sym eq from.head) to.head - else loop(from.tail, to.tail) - loop(oldOwners, newOwners) - } + def mapOwner(sym: Symbol) = sym.subst(oldOwners, newOwners) /** Replace occurrences of `This(oldOwner)` in some prefix of a type * by the corresponding `This(newOwner)`. diff --git a/src/dotty/tools/dotc/transform/SymUtils.scala b/src/dotty/tools/dotc/transform/SymUtils.scala index df0ac59ae930..5ae9aba63217 100644 --- a/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/src/dotty/tools/dotc/transform/SymUtils.scala @@ -7,6 +7,8 @@ import Contexts._ import Symbols._ import Decorators._ import Names._ +import StdNames._ +import Flags._ import language.implicitConversions object SymUtils { @@ -17,9 +19,31 @@ object SymUtils { * that are needed in the transformer pipeline. */ class SymUtils(val self: Symbol) extends AnyVal { + import SymUtils._ def isTypeTestOrCast(implicit ctx: Context): Boolean = self == defn.Any_asInstanceOf || self == defn.Any_isInstanceOf def isVolatile(implicit ctx: Context) = self.hasAnnotation(defn.VolatileAnnot) + + /** If this is a constructor, its owner: otherwise this. */ + final def skipConstructor(implicit ctx: Context): Symbol = + if (self.isConstructor) self.owner else self + + final def isAnonymousFunction(implicit ctx: Context): Boolean = + self.is(Method) && (self.denot.initial.asSymDenotation.name startsWith nme.ANON_FUN) + + /** The logically enclosing method or class for this symbol. + * Instead of constructors one always picks the enclosing class. + */ + final def enclosure(implicit ctx: Context) = self.owner.enclosingMethod.skipConstructor + + /** 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) + } } From 3f542aabf7944cc36302753d6126bb06e571d218 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:50:45 +0200 Subject: [PATCH 17/70] Account for PolyProto and IgnoredProto types in expectedTypeStr --- src/dotty/tools/dotc/typer/ErrorReporting.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 1f55df2bcb64..e96e04b1a449 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -56,10 +56,12 @@ object ErrorReporting { else "" def expectedTypeStr(tp: Type): String = tp match { + case tp: PolyProto => + d"type arguments [${tp.targs}%, %] and ${expectedTypeStr(tp.resultType)}" case tp: FunProto => val result = tp.resultType match { - case tp: WildcardType => "" - case tp => d"and expected result type $tp" + case _: WildcardType | _: IgnoredProto => "" + case tp => d" and expected result type $tp" } d"arguments (${tp.typedArgs.tpes}%, %)$result" case _ => From 330773619d01b9f684676ec4253b3d76c4807222 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:52:04 +0200 Subject: [PATCH 18/70] Fix to primaryConstructor Primary constructor was picking last constructor instead of first one. This is now fixed. Also, added paramAccessors utility method. --- src/dotty/tools/dotc/core/Contexts.scala | 2 +- src/dotty/tools/dotc/core/Denotations.scala | 5 ++++- src/dotty/tools/dotc/core/SymDenotations.scala | 8 +++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 6b9b1dec7a08..3b14872b7a7e 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -282,7 +282,7 @@ object Contexts { * from constructor parameters to class paramater accessors. */ def superCallContext: Context = { - val locals = newScopeWith(owner.decls.filter(_ is ParamAccessor).toList: _*) + val locals = newScopeWith(owner.asClass.paramAccessors: _*) superOrThisCallContext(owner.primaryConstructor, locals) } diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 09b67d08aaa4..82fd60fa0cef 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -681,6 +681,7 @@ object Denotations { // ------ PreDenotation ops ---------------------------------------------- final def first = this + final def last = this final def toDenot(pre: Type)(implicit ctx: Context): Denotation = this final def containsSym(sym: Symbol): Boolean = hasUniqueSym && (symbol eq sym) final def containsSig(sig: Signature)(implicit ctx: Context) = @@ -766,8 +767,9 @@ object Denotations { /** A denotation in the group exists */ def exists: Boolean - /** First denotation in the group */ + /** First/last denotation in the group */ def first: Denotation + def last: Denotation /** Convert to full denotation by &-ing all elements */ def toDenot(pre: Type)(implicit ctx: Context): Denotation @@ -832,6 +834,7 @@ object Denotations { assert(denots1.exists && denots2.exists, s"Union of non-existing denotations ($denots1) and ($denots2)") def exists = true def first = denots1.first + def last = denots2.last def toDenot(pre: Type)(implicit ctx: Context) = (denots1 toDenot pre) & (denots2 toDenot pre, pre) def containsSym(sym: Symbol) = diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ed123a384bc8..378d95b81b87 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1423,9 +1423,15 @@ object SymDenotations { override def primaryConstructor(implicit ctx: Context): Symbol = { val cname = if (this is ImplClass) nme.IMPLCLASS_CONSTRUCTOR else nme.CONSTRUCTOR - decls.denotsNamed(cname).first.symbol + decls.denotsNamed(cname).last.symbol // denotsNamed returns Symbols in reverse order of occurrence } + /** The parameter accessors of this class. Term and type accessors, + * getters and setters are all returned int his list + */ + def paramAccessors(implicit ctx: Context): List[Symbol] = + decls.filter(_ is ParamAccessor).toList + /** If this class has the same `decls` scope reference in `phase` and * `phase.next`, install a new denotation with a cloned scope in `phase.next`. */ From 96f754c92713d1a199b844fe5501dafaad45d7cb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 15:03:29 +0200 Subject: [PATCH 19/70] Add constructors phase --- src/dotty/tools/dotc/Compiler.scala | 4 +- .../tools/dotc/printing/RefinedPrinter.scala | 7 +- .../tools/dotc/transform/Constructors.scala | 131 ++++++++++++++++-- test/dotc/tests.scala | 8 +- 4 files changed, 124 insertions(+), 26 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 258cab36889a..87cac9799612 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -63,7 +63,9 @@ class Compiler { new InterceptedMethods, new Literalize), List(new Erasure), - List(new CapturedVars) + List(new CapturedVars), + List(new Constructors)/*, + List(new LambdaLift)*/ ) var runId = 1 diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d66d93a16f80..ae75bcb2d365 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -326,10 +326,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { typeDefText(optText(rhs)(" = " ~ _)) } } - case Template(DefDef(mods, _, tparams, vparamss, _, _), parents, self, stats) => + case Template(constr @ DefDef(mods, _, tparams, vparamss, _, rhs), parents, self, stats) => val tparamsTxt = tparamsText(tparams) + val primaryConstrs = if (rhs.isEmpty) Nil else constr :: Nil val prefix: Text = - if (vparamss.isEmpty) tparamsTxt + if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt else { var modsText = modText(mods, "") if (mods.hasAnnotations && !mods.hasFlags) modsText = modsText ~~ " this" @@ -340,7 +341,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val selfName = if (self.name == nme.WILDCARD) "this" else self.name.toString (selfName ~ optText(self.tpt)(": " ~ _) ~ " =>").close } provided !self.isEmpty - val bodyText = "{" ~~ selfText ~~ toTextGlobal(stats, "\n") ~ "}" + val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: stats, "\n") ~ "}" prefix ~~ (" extends" provided ownerIsClass) ~~ parentsText ~~ bodyText case Import(expr, selectors) => def selectorText(sel: Tree): Text = sel match { diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index 34c90fdc2d31..d03b90a27ecd 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -1,27 +1,128 @@ -package dotty.tools.dotc.transform +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 ExplicitOuter.outerParamAccessor /** This transform moves initializers from body to constructor. - * Right now it's a dummy. - * Awaiting for real implemetation */ -class Constructors extends MiniPhaseTransform { +class Constructors extends MiniPhaseTransform with SymTransformer { thisTransform => + import tpd._ override def phaseName: String = "constructors" - override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { - if(tree.symbol.isClassConstructor) { - val claz = tree.symbol.enclosingClass.asClass - val zuper = claz.info.parents.head.typeSymbol - cpy.DefDef(tree)(rhs = { - val parentCall = - Super(This(claz), tpnme.EMPTY, true).select(zuper.primaryConstructor).appliedToNone - if(tree.rhs.isEmpty) parentCall - else Block(List(parentCall), tree.rhs) - }) - } else tree + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) + + override def treeTransformPhase = thisTransform.next + + override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { + def ownerBecomesConstructor(owner: Symbol): Boolean = + (owner.isLocalDummy || + owner.isTerm && !owner.is(Method) && owner.owner.isClass) && + !owner.enclosingClass.is(Trait) // TODO: Remove qualification once Mixin is operational + if (ownerBecomesConstructor(sym.owner)) + sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor) + else sym + } + + private def intoConstr(accessors: List[Symbol], params: List[Symbol]) = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case Ident(_) | Select(This(_), _) => + val sym = tree.symbol + if (sym is ParamAccessor) { + val param = sym.subst(accessors, params) + if (param ne sym) ref(param).withPos(tree.pos) + else tree + } + else tree + case Apply(fn, Nil) => + val fn1 = transform(fn) + if ((fn1 ne fn) && + fn1.symbol.is(Param) && + fn1.symbol.owner.isPrimaryConstructor) { + // Two possible cases, which each need their adaptation: + if (fn1.symbol.initial.info.isInstanceOf[ExprType]) + // it's either a call-by-name parameter, which is erased to Function0, + // then we need to insert an apply. + cpy.Apply(tree)(Select(fn1, nme.apply), Nil).ensureConforms(tree.tpe) + else + // or original accessor was an alias accessor, then we need to drop the () + fn1 + } + else cpy.Apply(tree)(fn1, Nil) + case _ => + super.transform(tree) + } + } + + private def splitStats(stats: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) = stats match { + case stat :: stats1 => + val (constrStats, clsStats) = splitStats(stats1) + stat match { + case stat @ ValDef(mods, name, tpt, rhs) if !rhs.isEmpty => + val inits = + if (isWildcardArg(rhs)) Nil + else Assign(ref(stat.symbol), rhs).withPos(stat.pos) :: Nil + (inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats) + case _: DefTree => + (constrStats, stat :: clsStats) + case _ => + (stat :: constrStats, clsStats) + } + case Nil => + (Nil, Nil) + } + + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { + val cls = ctx.owner.asClass + if (cls is Trait) tree + else { + val constr @ DefDef(_, nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr + val (superApp @ Apply( + superSel @ Select( + superNew @ New(superType), + nme.CONSTRUCTOR), + superArgs)) :: traitParents = tree.parents + var accessors = cls.paramAccessors.filterNot(_.isSetter) + var vparamsWithOuter = vparams + if (!accessors.hasSameLengthAs(vparams)) { + accessors.reverse match { + case last :: _ if (last.name == nme.OUTER) => + accessors = last :: accessors.init + vparamsWithOuter = ValDef(last.asTerm) :: vparams + case _ => + } + assert(accessors.hasSameLengthAs(vparamsWithOuter), + i"lengths differ for $cls, param accs = $accessors, params = $vparamsWithOuter") + } + val mappedArgs = superArgs.map( + intoConstr(accessors, vparamsWithOuter.map(_.symbol)).transform) + val superCall = + cpy.Apply(superApp)( + cpy.Select(superSel)( + Super(This(cls), tpnme.EMPTY, inConstrCall = true).withPos(superNew.pos), + nme.CONSTRUCTOR), + mappedArgs) + val (constrStats, clsStats) = splitStats(tree.body) + def normalizeOwner(stat: Tree) = { + } + cpy.Template(tree)( + constr = cpy.DefDef(constr)(rhs = Block(superCall :: constrStats, unitLiteral)), + parents = superType :: traitParents, + body = clsStats) + } } } \ No newline at end of file diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 02184599729a..aa8cc05524d7 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -31,7 +31,6 @@ class tests extends CompilerTest { val dotcDir = "./src/dotty/" - @Test def pos_t2168_pat = compileFile(posDir, "t2168", doErase) @Test def pos_erasure = compileFile(posDir, "erasure", doErase) @Test def pos_Coder() = compileFile(posDir, "Coder", doErase) @Test def pos_blockescapes() = compileFile(posDir, "blockescapes", doErase) @@ -49,7 +48,6 @@ class tests extends CompilerTest { @Test def pos_desugar() = compileFile(posDir, "desugar", doErase) @Test def pos_sigs() = compileFile(posDir, "sigs", doErase) @Test def pos_typers() = compileFile(posDir, "typers", doErase) - @Test def pos_typedidents() = compileFile(posDir, "typedIdents", doErase) @Test def pos_assignments() = compileFile(posDir, "assignments", doErase) @Test def pos_packageobject() = compileFile(posDir, "packageobject", doErase) @@ -97,6 +95,7 @@ class tests extends CompilerTest { @Test def nef_t1279a = compileFile(negDir, "t1279a", xerrors = 1) @Test def neg_t1843 = compileFile(negDir, "t1843", xerrors = 1) @Test def neg_t1843_variances = compileFile(negDir, "t1843-variances", xerrors = 1) + @Test def neg_t2660_ambi = compileFile(negDir, "t2660", xerrors = 2) @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 2) @Test def neg_subtyping = compileFile(negDir, "subtyping", xerrors = 1) @Test def neg_variances = compileFile(negDir, "variances", xerrors = 2) @@ -108,15 +107,11 @@ class tests extends CompilerTest { @Test def dotc_config = compileDir(dotcDir + "tools/dotc/config", twice) @Test def dotc_core = compileDir(dotcDir + "tools/dotc/core", twice)(allowDeepSubtypes) @Test def dotc_core_pickling = compileDir(dotcDir + "tools/dotc/core/pickling", twice)(allowDeepSubtypes) - @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", twice) - @Test def dotc_parsing = compileDir(dotcDir + "tools/dotc/parsing", twice) @Test def dotc_printing = compileDir(dotcDir + "tools/dotc/printing", twice) - @Test def dotc_reporting = compileDir(dotcDir + "tools/dotc/reporting", twice) @Test def dotc_typer = compileDir(dotcDir + "tools/dotc/typer", twice) - @Test def dotc_util = compileDir(dotcDir + "tools/dotc/util", twice) @Test def tools_io = compileDir(dotcDir + "tools/io", twice) @Test def tools = compileDir(dotcDir + "tools", twice)(allowDeepSubtypes) @@ -129,7 +124,6 @@ class tests extends CompilerTest { "-Xprompt", "#runs", "2")) - @Test def testIssue_34 = compileArgs(Array( dotcDir + "tools/dotc/config/Properties.scala", dotcDir + "tools/dotc/config/PathResolver.scala", From 88d11fa21761d798baeb24b8002ddacade601b23 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 16:10:04 +0200 Subject: [PATCH 20/70] Combine captured vars and constructors into one macro phase. Needed to fix a problem in CapturedVars to make this work. --- src/dotty/tools/dotc/Compiler.scala | 3 +-- src/dotty/tools/dotc/transform/CapturedVars.scala | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 87cac9799612..63f79c8d391f 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -63,8 +63,7 @@ class Compiler { new InterceptedMethods, new Literalize), List(new Erasure), - List(new CapturedVars), - List(new Constructors)/*, + List(new CapturedVars, new Constructors)/*, List(new LambdaLift)*/ ) diff --git a/src/dotty/tools/dotc/transform/CapturedVars.scala b/src/dotty/tools/dotc/transform/CapturedVars.scala index 59a7668193ad..91b81fea5d0a 100644 --- a/src/dotty/tools/dotc/transform/CapturedVars.scala +++ b/src/dotty/tools/dotc/transform/CapturedVars.scala @@ -45,7 +45,7 @@ class CapturedVars extends MiniPhaseTransform with SymTransformer { thisTransfor } override def init(implicit ctx: Context, info: TransformerInfo): Unit = - (new CollectCaptured).runOver(ctx.compilationUnit.tpdTree) + (new CollectCaptured)(ctx.withPhase(thisTransform)).runOver(ctx.compilationUnit.tpdTree) override def transformSym(sd: SymDenotation)(implicit ctx: Context): SymDenotation = if (captured(sd.symbol)) { From 478b97a1cbc67af97bb8be98a36c5b8173898f2b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Sep 2014 12:43:08 +0200 Subject: [PATCH 21/70] Fix rewriting of by-name class parameter accessors. Like the corresponding parameters, these also need to be rewritten to function types. --- src/dotty/tools/dotc/transform/ElimByName.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ElimByName.scala b/src/dotty/tools/dotc/transform/ElimByName.scala index 5925ffa721ff..e656c8438958 100644 --- a/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/src/dotty/tools/dotc/transform/ElimByName.scala @@ -4,6 +4,7 @@ package transform import TreeTransforms._ import core.DenotTransformers._ import core.Symbols._ +import core.SymDenotations._ import core.Contexts._ import core.Types._ import core.Flags._ @@ -75,9 +76,12 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform cpy.Apply(tree)(tree.fun, args1) } + private def becomesFunction(symd: SymDenotation)(implicit ctx: Context) = + (symd is Param) || (symd is (ParamAccessor, butNot = Method)) + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = { val origDenot = originalDenotation(tree) - if ((origDenot is Param) && (origDenot.info.isInstanceOf[ExprType])) + if (becomesFunction(origDenot) && (origDenot.info.isInstanceOf[ExprType])) tree.select(defn.Function0_apply).appliedToNone else tree } @@ -98,6 +102,6 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform } def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = - if (sym is Param) transformParamInfo(tp) + if (becomesFunction(sym)) transformParamInfo(tp) else elimByNameParams(tp) } From 8ec91d472f28ec1bf1c957815e5ca987410b7c47 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Sep 2014 12:44:31 +0200 Subject: [PATCH 22/70] Completed constructors phase 1) Type parameters are now copied to accessors 2) Constructors also work for traits 2) makes it possible do to mixin after constructors. --- src/dotty/tools/dotc/core/Flags.scala | 7 +- .../tools/dotc/transform/Constructors.scala | 205 ++++++++++++------ 2 files changed, 141 insertions(+), 71 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 64c87ecb865e..de27282fbb44 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -543,12 +543,15 @@ object Flags { /** Labeled private[this] */ final val PrivateLocal = allOf(Private, Local) - /** A private parameter accessor */ + /** A private[this] parameter accessor */ final val PrivateLocalParamAccessor = allOf(Private, Local, ParamAccessor) - /** A private parameter */ + /** A private[this] parameter */ final val PrivateLocalParam = allOf(Private, Local, Param) + /** A private parameter accessor */ + final val PrivateParamAccessor = allOf(Private, ParamAccessor) + /** A local parameter */ final val ParamAndLocal = allOf(Param, Local) diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index d03b90a27ecd..3d2f5a0d7079 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -16,9 +16,11 @@ import SymDenotations._ import Types._ import Decorators._ import DenotTransformers._ -import ExplicitOuter.outerParamAccessor +import collection.mutable -/** This transform moves initializers from body to constructor. +/** This transform + * - moves initializers from body to constructor. + * - makes all supercalls explicit */ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransform => import tpd._ @@ -28,21 +30,37 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor override def treeTransformPhase = thisTransform.next + /** Symbols that are owned by either or a class field move into the + * primary constructor. + */ override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { def ownerBecomesConstructor(owner: Symbol): Boolean = - (owner.isLocalDummy || - owner.isTerm && !owner.is(Method) && owner.owner.isClass) && - !owner.enclosingClass.is(Trait) // TODO: Remove qualification once Mixin is operational + (owner.isLocalDummy || owner.isTerm && !owner.is(Method)) && + owner.owner.isClass if (ownerBecomesConstructor(sym.owner)) sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor) else sym } - private def intoConstr(accessors: List[Symbol], params: List[Symbol]) = new TreeMap { + /** @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) + + /** 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. + */ + class IntoConstrMap(accessors: List[Symbol], params: List[Symbol]) extends TreeMap { + private var excluded: FlagSet = _ override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case Ident(_) | Select(This(_), _) => val sym = tree.symbol - if (sym is ParamAccessor) { + if (sym is (ParamAccessor, butNot = excluded)) { val param = sym.subst(accessors, params) if (param ne sym) ref(param).withPos(tree.pos) else tree @@ -50,79 +68,128 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor else tree case Apply(fn, Nil) => val fn1 = transform(fn) - if ((fn1 ne fn) && - fn1.symbol.is(Param) && - fn1.symbol.owner.isPrimaryConstructor) { - // Two possible cases, which each need their adaptation: - if (fn1.symbol.initial.info.isInstanceOf[ExprType]) - // it's either a call-by-name parameter, which is erased to Function0, - // then we need to insert an apply. - cpy.Apply(tree)(Select(fn1, nme.apply), Nil).ensureConforms(tree.tpe) - else - // or original accessor was an alias accessor, then we need to drop the () - fn1 - } + 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 _ => - super.transform(tree) + if (noDirectRefsFrom(tree)) tree else super.transform(tree) } - } - private def splitStats(stats: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) = stats match { - case stat :: stats1 => - val (constrStats, clsStats) = splitStats(stats1) - stat match { - case stat @ ValDef(mods, name, tpt, rhs) if !rhs.isEmpty => - val inits = - if (isWildcardArg(rhs)) Nil - else Assign(ref(stat.symbol), rhs).withPos(stat.pos) :: Nil - (inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats) - case _: DefTree => - (constrStats, stat :: clsStats) - case _ => - (stat :: constrStats, clsStats) - } - case Nil => - (Nil, Nil) + def apply(tree: Tree, excluded: FlagSet)(implicit ctx: Context): Tree = { + this.excluded = excluded + transform(tree) + } } override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { val cls = ctx.owner.asClass - if (cls is Trait) tree - else { - val constr @ DefDef(_, nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr - val (superApp @ Apply( - superSel @ Select( - superNew @ New(superType), - nme.CONSTRUCTOR), - superArgs)) :: traitParents = tree.parents - var accessors = cls.paramAccessors.filterNot(_.isSetter) - var vparamsWithOuter = vparams - if (!accessors.hasSameLengthAs(vparams)) { - accessors.reverse match { - case last :: _ if (last.name == nme.OUTER) => - accessors = last :: accessors.init - vparamsWithOuter = ValDef(last.asTerm) :: vparams + 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 should come first in the constructor parameter list. + var accessors = cls.paramAccessors.filterNot(_.isSetter) + var vparamsWithOuter = vparams + if (!accessors.hasSameLengthAs(vparams)) { + accessors.reverse match { + case last :: _ if (last.name == nme.OUTER) => + accessors = last :: accessors.init // align wth calling convention + vparamsWithOuter = ValDef(last.asTerm) :: vparams + case _ => + } + assert(accessors.hasSameLengthAs(vparamsWithOuter), + i"lengths differ for $cls, param accs = $accessors, params = $vparamsWithOuter") + } + val paramSyms = vparamsWithOuter map (_.symbol) + + val intoConstr = new IntoConstrMap(accessors, paramSyms) + val superCalls = new mutable.ListBuffer[Tree] + + // If parent is a constructor call, pull out the call into a separate + // supercall constructor, which gets appended to `superCalls`, and keep + // only the type. + def normalizeParent(tree: Tree) = tree match { + case superApp @ Apply( + superSel @ Select( + superNew @ New(superType), + nme.CONSTRUCTOR), + superArgs) => + val toClass = !superType.symbol.is(Trait) + val mappedArgs = superArgs.map( + intoConstr(_, excluded = if (toClass) Mutable else EmptyFlags)) + val receiver = + if (toClass) Super(This(cls), tpnme.EMPTY, inConstrCall = true) + else This(cls) + superCalls += cpy.Apply(superApp)( + receiver.withPos(superNew.pos) + .select(superSel.symbol).withPos(superSel.pos), + mappedArgs) + superType + case tree: TypeTree => tree + } + val parentTypeTrees = tree.parents.map(normalizeParent) + + // Split class body into statements that go into constructor and + // definitions that are kept as members of the class. + def splitStats(stats: List[Tree]): (List[Tree], List[Tree]) = stats match { + case stat :: stats1 => + val (constrStats, clsStats) = splitStats(stats1) + stat match { + case stat @ ValDef(mods, name, tpt, rhs) => + val inits = + if (rhs.isEmpty || isWildcardArg(rhs)) Nil + else Assign(ref(stat.symbol), intoConstr(rhs, excluded = Mutable)) + .withPos(stat.pos) :: Nil + (inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats) + case _: DefTree => + (constrStats, stat :: clsStats) case _ => + (intoConstr(stat, excluded = Mutable) :: constrStats, clsStats) } - assert(accessors.hasSameLengthAs(vparamsWithOuter), - i"lengths differ for $cls, param accs = $accessors, params = $vparamsWithOuter") - } - val mappedArgs = superArgs.map( - intoConstr(accessors, vparamsWithOuter.map(_.symbol)).transform) - val superCall = - cpy.Apply(superApp)( - cpy.Select(superSel)( - Super(This(cls), tpnme.EMPTY, inConstrCall = true).withPos(superNew.pos), - nme.CONSTRUCTOR), - mappedArgs) - val (constrStats, clsStats) = splitStats(tree.body) - def normalizeOwner(stat: Tree) = { + case Nil => + (Nil, Nil) + } + val (constrStats, clsStats) = splitStats(tree.body) + + // Collect all private parameter accessors that need to be retained + // because they are accessed after the constructor has finished. + val collectRetained = new TreeAccumulator[Set[Symbol]] { + override def apply(retained: Set[Symbol], tree: Tree) = tree match { + case tree: RefTree => + val sym = tree.symbol + foldOver( + if (sym.is(PrivateParamAccessor) && sym.owner == cls) retained + sym else retained, + tree) + case _ => + if (noDirectRefsFrom(tree)) retained else foldOver(retained, tree) } - cpy.Template(tree)( - constr = cpy.DefDef(constr)(rhs = Block(superCall :: constrStats, unitLiteral)), - parents = superType :: traitParents, - body = clsStats) } + val retainedPrivate = collectRetained(collectRetained(Set[Symbol](), constrStats), clsStats) + def isRetained(acc: Symbol) = + (!acc.is(Private) || acc.is(NotJavaPrivate) || retainedPrivate(acc)) + + val accessorFields = accessors.filterNot(_ is Method) + val (retainedAccessors, droppedAccessors) = accessorFields.partition(isRetained) + + // The initializers for the retained accessors */ + val copyParams = retainedAccessors.map(acc => + Assign(ref(acc), ref(acc.subst(accessors, paramSyms))).withPos(tree.pos)) + + // Drop accessors that are not retained from class scope + if (droppedAccessors.nonEmpty) { + val clsInfo = cls.classInfo // TODO investigate: expand clsInfo to cls.info => dotty type error + cls.copy( + info = clsInfo.derivedClassInfo( + decls = clsInfo.decls.filteredScope(!droppedAccessors.contains(_)))) + } + + cpy.Template(tree)( + constr = cpy.DefDef(constr)( + rhs = Block(superCalls.toList ::: copyParams ::: constrStats, unitLiteral)), + parents = parentTypeTrees, + body = clsStats filter { + case vdef: ValDef => !droppedAccessors.contains(vdef.symbol) + case _ => true + }) } } \ No newline at end of file From a64e0a2fc8883e5a6358a04d706eb72e896f67c6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Sep 2014 14:05:14 +0200 Subject: [PATCH 23/70] Made TypeVars uncahable keys TypeVars can appear as keys in baseType caches. The problem is that their base types depend on their instantiation, which is not always know yet when the test is performed. So results of baseType on type variables should never be cached. Todo: check whether there are any other caching problems involving typevars. --- src/dotty/tools/dotc/core/SymDenotations.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 378d95b81b87..55611211487c 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1445,7 +1445,6 @@ object SymDenotations { .installAfter(phase) } } - private case class Uncachable(tp: Type) extends UncachedGroundType /** The denotation of a package class. From 5289a3719c0767c1874ff1e34b825ccf3b314257 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Sep 2014 15:42:55 +0200 Subject: [PATCH 24/70] Fix caching logic in baseTypeRef Typevars can be parts of larger types or underlying types of other types, which renders these other types uncacheable. The new logic takes account of that. --- .../tools/dotc/core/SymDenotations.scala | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 55611211487c..711a76ad50b3 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1317,6 +1317,18 @@ object SymDenotations { case _ => bt } + def inCache(tp: Type) = baseTypeRefCache.containsKey(tp) + + /** Can't cache types containing type variables which are uninstantiated + * or whose instances can change, depending on typerstate. + */ + def isCachable(tp: Type): Boolean = tp match { + case tp: TypeProxy => inCache(tp.underlying) + case tp: AndOrType => inCache(tp.tp1) && inCache(tp.tp2) + case tp: TypeVar => tp.inst.exists && inCache(tp.inst) + case _ => true + } + def computeBaseTypeRefOf(tp: Type): Type = { Stats.record("computeBaseTypeOf") if (symbol.isStatic && tp.derivesFrom(symbol)) @@ -1333,9 +1345,6 @@ object SymDenotations { case _ => baseTypeRefOf(tp.underlying) } - case tp: TypeVar => - if (tp.inst.exists) computeBaseTypeRefOf(tp.inst) - else Uncachable(computeBaseTypeRefOf(tp.underlying)) case tp: TypeProxy => baseTypeRefOf(tp.underlying) case AndType(tp1, tp2) => @@ -1356,15 +1365,9 @@ object SymDenotations { var basetp = baseTypeRefCache get tp if (basetp == null) { baseTypeRefCache.put(tp, NoPrefix) - val computedBT = computeBaseTypeRefOf(tp) - basetp = computedBT match { - case Uncachable(basetp) => - baseTypeRefCache.remove(tp) - computedBT - case basetp => - baseTypeRefCache.put(tp, basetp) - basetp - } + basetp = computeBaseTypeRefOf(tp) + if (isCachable(tp)) baseTypeRefCache.put(tp, basetp) + else baseTypeRefCache.remove(tp) } else if (basetp == NoPrefix) { throw CyclicReference(this) } @@ -1444,8 +1447,7 @@ object SymDenotations { copySymDenotation(info = ClassInfo(pre, classSymbol, ps, decls.cloneScope, selfInfo)) .installAfter(phase) } - } - private case class Uncachable(tp: Type) extends UncachedGroundType + } /** The denotation of a package class. * It overrides ClassDenotation to take account of package objects when looking for members From 06173800c03a06b35b3ade30ce52f2dd295851b4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 24 Sep 2014 14:02:56 +0200 Subject: [PATCH 25/70] Added method to help traversing children in a TreeTraverser --- src/dotty/tools/dotc/ast/Trees.scala | 3 ++- src/dotty/tools/dotc/typer/VarianceChecker.scala | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 19f52b08fdaa..fb9ad24b3b0d 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -1336,6 +1336,7 @@ object Trees { abstract class TreeTraverser extends TreeAccumulator[Unit] { def traverse(tree: Tree): Unit def apply(x: Unit, tree: Tree) = traverse(tree) + protected def traverseChildren(tree: Tree) = foldOver((), tree) } /** Fold `f` over all tree nodes, in depth-first, prefix order */ @@ -1361,9 +1362,9 @@ object Trees { case tree: Bind => cpy.Bind(tree)(newName, tree.body) case tree: ValDef => cpy.ValDef(tree)(name = newName.asTermName) case tree: DefDef => cpy.DefDef(tree)(name = newName.asTermName) + case tree: untpd.PolyTypeDef => untpd.cpy.PolyTypeDef(tree)(tree.mods, newName.asTypeName, tree.tparams, tree.rhs) case tree: TypeDef => cpy.TypeDef(tree)(name = newName.asTypeName) case tree: SelectFromTypeTree => cpy.SelectFromTypeTree(tree)(tree.qualifier, newName) - case tree: untpd.PolyTypeDef => untpd.cpy.PolyTypeDef(tree)(tree.mods, newName.asTypeName, tree.tparams, tree.rhs) } }.asInstanceOf[tree.ThisTree[T]] } diff --git a/src/dotty/tools/dotc/typer/VarianceChecker.scala b/src/dotty/tools/dotc/typer/VarianceChecker.scala index 5865f0133f67..e1f50ded9d74 100644 --- a/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -125,7 +125,7 @@ class VarianceChecker()(implicit ctx: Context) { ctx.debuglog(s"Skipping variance check of ${sym.showDcl}") case tree: TypeDef => checkVariance(sym) - foldOver((), tree) + traverseChildren(tree) case tree: ValDef => checkVariance(sym) case DefDef(_, _, tparams, vparamss, _, _) => @@ -133,7 +133,7 @@ class VarianceChecker()(implicit ctx: Context) { tparams foreach traverse vparamss foreach (_ foreach traverse) case Template(_, _, _, body) => - foldOver((), tree) + traverseChildren(tree) case _ => } } From 103d16793f312e2cefc8095de58255728ceebc88 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 24 Sep 2014 14:05:43 +0200 Subject: [PATCH 26/70] Move private fields into constructor Private fields that are accessed only from the constructor, and are accessed only after they are properly initialized are now moved into the constructor. This avoids creating a redundant objetc field. Good example: gcd in Rationals (see constrs.scala). --- .../tools/dotc/transform/Constructors.scala | 179 +++++++++++------- tests/pos/Fileish.scala | 53 ++++++ tests/pos/constrs.scala | 33 ++++ 3 files changed, 193 insertions(+), 72 deletions(-) create mode 100644 tests/pos/Fileish.scala create mode 100644 tests/pos/constrs.scala diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index 3d2f5a0d7079..9cb348dfff5e 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -21,6 +21,8 @@ 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 SymTransformer { thisTransform => import tpd._ @@ -35,7 +37,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor */ override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { def ownerBecomesConstructor(owner: Symbol): Boolean = - (owner.isLocalDummy || owner.isTerm && !owner.is(Method)) && + (owner.isLocalDummy || owner.isTerm && !owner.is(Method | Lazy)) && owner.owner.isClass if (ownerBecomesConstructor(sym.owner)) sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor) @@ -48,38 +50,14 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor private def noDirectRefsFrom(tree: Tree)(implicit ctx: Context) = tree.isDef && tree.symbol.isClass && !tree.symbol.is(InSuperCall) - /** 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. + /** Class members that can be eliminated if referenced only from their own + * constructor. */ - class IntoConstrMap(accessors: List[Symbol], params: List[Symbol]) extends TreeMap { - private var excluded: FlagSet = _ - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case Ident(_) | Select(This(_), _) => - val sym = tree.symbol - if (sym is (ParamAccessor, butNot = excluded)) { - val param = sym.subst(accessors, params) - if (param ne sym) ref(param).withPos(tree.pos) - else tree - } - 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) - } + private def mightBeDropped(sym: Symbol)(implicit ctx: Context) = + sym.is(Private, butNot = KeeperFlags) && !sym.is(MutableParamAccessor) - def apply(tree: Tree, excluded: FlagSet)(implicit ctx: Context): Tree = { - this.excluded = excluded - transform(tree) - } - } + private final val KeeperFlags = Method | Lazy | NotJavaPrivate + private final val MutableParamAccessor = allOf(Mutable, ParamAccessor) override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { val cls = ctx.owner.asClass @@ -102,7 +80,34 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor } val paramSyms = vparamsWithOuter map (_.symbol) - val intoConstr = new IntoConstrMap(accessors, paramSyms) + // 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 { + private var excluded: FlagSet = _ + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case Ident(_) | Select(This(_), _) => + var sym = tree.symbol + if (sym is (ParamAccessor, butNot = excluded)) 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, inSuperCall: Boolean = false)(implicit ctx: Context): Tree = { + this.excluded = if (inSuperCall) EmptyFlags else Mutable + transform(tree) + } + } + val superCalls = new mutable.ListBuffer[Tree] // If parent is a constructor call, pull out the call into a separate @@ -115,12 +120,12 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor nme.CONSTRUCTOR), superArgs) => val toClass = !superType.symbol.is(Trait) - val mappedArgs = superArgs.map( - intoConstr(_, excluded = if (toClass) Mutable else EmptyFlags)) + val mappedArgs = superArgs.map(intoConstr(_, inSuperCall = toClass)) val receiver = if (toClass) Super(This(cls), tpnme.EMPTY, inConstrCall = true) else This(cls) - superCalls += cpy.Apply(superApp)( + superCalls += + cpy.Apply(superApp)( receiver.withPos(superNew.pos) .select(superSel.symbol).withPos(superSel.pos), mappedArgs) @@ -129,67 +134,97 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor } val parentTypeTrees = tree.parents.map(normalizeParent) + // 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 `_` + object usage extends TreeTraverser { + private var inConstr: Boolean = true + private val seen = mutable.Set[Symbol](accessors: _*) + val retained = mutable.Set[Symbol]() + def dropped: collection.Set[Symbol] = seen -- retained + override def traverse(tree: Tree) = { + val sym = tree.symbol + tree match { + case Ident(_) | Select(This(_), _) if inConstr && seen(tree.symbol) => + // could refer to definition in constructors, so no retention necessary + case tree: RefTree => + if (mightBeDropped(sym)) retained += sym + case _ => + } + if (!noDirectRefsFrom(tree)) traverseChildren(tree) + } + def collect(stats: List[Tree]): Unit = stats foreach { + case stat: ValDef if !stat.symbol.is(Lazy) => + traverse(stat) + if (mightBeDropped(stat.symbol)) + (if (isWildcardStarArg(stat.rhs)) retained else seen) += stat.symbol + case stat: DefTree => + inConstr = false + traverse(stat) + inConstr = true + case stat => + traverse(stat) + } + } + usage.collect(superCalls.toList ++ tree.body) + + def isRetained(acc: Symbol) = !mightBeDropped(acc) || usage.retained(acc) + + val constrStats, clsStats = new mutable.ListBuffer[Tree] + // Split class body into statements that go into constructor and // definitions that are kept as members of the class. - def splitStats(stats: List[Tree]): (List[Tree], List[Tree]) = stats match { + def splitStats(stats: List[Tree]): Unit = stats match { case stat :: stats1 => - val (constrStats, clsStats) = splitStats(stats1) stat match { - case stat @ ValDef(mods, name, tpt, rhs) => - val inits = - if (rhs.isEmpty || isWildcardArg(rhs)) Nil - else Assign(ref(stat.symbol), intoConstr(rhs, excluded = Mutable)) - .withPos(stat.pos) :: Nil - (inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats) + case stat @ ValDef(mods, name, tpt, rhs) if !stat.symbol.is(Lazy) => + val sym = stat.symbol + if (isRetained(sym)) { + if (!rhs.isEmpty && !isWildcardArg(rhs)) + constrStats += Assign(ref(sym), intoConstr(rhs)).withPos(stat.pos) + clsStats += cpy.ValDef(stat)(rhs = EmptyTree) + } + else if (!rhs.isEmpty) { + sym.copySymDenotation( + initFlags = sym.flags &~ Private, + owner = constr.symbol).installAfter(thisTransform) + constrStats += intoConstr(stat) + } case _: DefTree => - (constrStats, stat :: clsStats) + clsStats += stat case _ => - (intoConstr(stat, excluded = Mutable) :: constrStats, clsStats) + constrStats += intoConstr(stat) } + splitStats(stats1) case Nil => (Nil, Nil) } - val (constrStats, clsStats) = splitStats(tree.body) - - // Collect all private parameter accessors that need to be retained - // because they are accessed after the constructor has finished. - val collectRetained = new TreeAccumulator[Set[Symbol]] { - override def apply(retained: Set[Symbol], tree: Tree) = tree match { - case tree: RefTree => - val sym = tree.symbol - foldOver( - if (sym.is(PrivateParamAccessor) && sym.owner == cls) retained + sym else retained, - tree) - case _ => - if (noDirectRefsFrom(tree)) retained else foldOver(retained, tree) - } - } - val retainedPrivate = collectRetained(collectRetained(Set[Symbol](), constrStats), clsStats) - def isRetained(acc: Symbol) = - (!acc.is(Private) || acc.is(NotJavaPrivate) || retainedPrivate(acc)) + splitStats(tree.body) val accessorFields = accessors.filterNot(_ is Method) - val (retainedAccessors, droppedAccessors) = accessorFields.partition(isRetained) // The initializers for the retained accessors */ - val copyParams = retainedAccessors.map(acc => + val copyParams = accessorFields.filter(isRetained).map(acc => Assign(ref(acc), ref(acc.subst(accessors, paramSyms))).withPos(tree.pos)) // Drop accessors that are not retained from class scope - if (droppedAccessors.nonEmpty) { + val dropped = usage.dropped + if (dropped.nonEmpty) { val clsInfo = cls.classInfo // TODO investigate: expand clsInfo to cls.info => dotty type error cls.copy( info = clsInfo.derivedClassInfo( - decls = clsInfo.decls.filteredScope(!droppedAccessors.contains(_)))) + decls = clsInfo.decls.filteredScope(!dropped.contains(_)))) } cpy.Template(tree)( constr = cpy.DefDef(constr)( - rhs = Block(superCalls.toList ::: copyParams ::: constrStats, unitLiteral)), + rhs = Block(superCalls.toList ::: copyParams ::: constrStats.toList, unitLiteral)), parents = parentTypeTrees, - body = clsStats filter { - case vdef: ValDef => !droppedAccessors.contains(vdef.symbol) - case _ => true - }) + body = clsStats.toList) } } \ No newline at end of file diff --git a/tests/pos/Fileish.scala b/tests/pos/Fileish.scala new file mode 100644 index 000000000000..d226bb0bdeee --- /dev/null +++ b/tests/pos/Fileish.scala @@ -0,0 +1,53 @@ +// Inspired by the original Fileish, +// testing combinations of lazy and non-lazy vals for their treatment in constructors +package dotty.tools +package io + +import java.io.{ InputStream } +import java.util.jar.JarEntry +import language.postfixOps + +/** A common interface for File-based things and Stream-based things. + * (In particular, io.File and JarEntry.) + */ +class Fileish(val path: Path, val input: () => InputStream) extends Streamable.Chars { + def inputStream() = input() + + def parent = path.parent + def name = path.name + def isSourceFile = path.hasExtension("java", "scala") + + private lazy val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim } + lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".") + lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "." + + override def toString = path.path +} +class Fileish2(val path: Path, val input: () => InputStream) extends Streamable.Chars { + def inputStream() = input() + + def parent = path.parent + def name = path.name + def isSourceFile = path.hasExtension("java", "scala") + + private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim } + lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".") + lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "." + + override def toString = path.path +} + +class Fileish3(val path: Path, val input: () => InputStream) extends Streamable.Chars { + def inputStream() = input() + + def parent = path.parent + def name = path.name + def isSourceFile = path.hasExtension("java", "scala") + + private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim } + private val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".") + private val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "." + + override def toString = path.path +} + diff --git a/tests/pos/constrs.scala b/tests/pos/constrs.scala new file mode 100644 index 000000000000..dc0e1a369305 --- /dev/null +++ b/tests/pos/constrs.scala @@ -0,0 +1,33 @@ +class Foo(x: Int, var y: Int) { + + val z: Int = 0 + + var u: Int = _ + + def f = x + +} + +class Baz(val base: Int) { + +} + + +class Bar(base: Int, byName: => String, local: Int) extends Baz(base + local) { + + def f() = println(base.toString + byName) + +} + +class Rational(n: Int, d: Int) { + def gcd(x: Int, y: Int): Int = ??? + private val x = gcd(n, d) + def numer = n / x + def denom = d / x +} +class Rational2(n: Int, d: Int) { + def gcd(x: Int, y: Int): Int = ??? + private val x = gcd(n, d) + val numer = n / x + val denom = d / x +} From 9ab1568cd5854dd9dd3800f3d2f05866fcb26613 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Sat, 11 Oct 2014 08:28:38 +0200 Subject: [PATCH 27/70] Fix errors in SymDenotations.isCachable. TypeVar is a TypeProxy. --- src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 711a76ad50b3..cea9be604048 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1323,9 +1323,9 @@ object SymDenotations { * or whose instances can change, depending on typerstate. */ def isCachable(tp: Type): Boolean = tp match { + case tp: TypeVar => tp.inst.exists && inCache(tp.inst) case tp: TypeProxy => inCache(tp.underlying) case tp: AndOrType => inCache(tp.tp1) && inCache(tp.tp2) - case tp: TypeVar => tp.inst.exists && inCache(tp.inst) case _ => true } From c0da421ea2f7b63cc081224a025c8f102030be7b Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Sat, 11 Oct 2014 08:42:32 +0200 Subject: [PATCH 28/70] After-rebase fix: weaken owner-chain restriction. Allow to skip labels. --- src/dotty/tools/dotc/transform/TreeChecker.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 5412b66b8bc6..46886afb6f94 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -98,7 +98,7 @@ class TreeChecker { private def checkOwner(tree: untpd.Tree)(implicit ctx: Context): Unit = { def ownerMatches(symOwner: Symbol, ctxOwner: Symbol): Boolean = symOwner == ctxOwner || - ctxOwner.isWeakOwner && !(ctxOwner is Method | Lazy | Mutable) && + ctxOwner.isWeakOwner && (!(ctxOwner is Method | Lazy | Mutable) || (ctxOwner is Label)) && ownerMatches(symOwner, ctxOwner.owner) if(!ownerMatches(tree.symbol.owner, ctx.owner)) { assert(ownerMatches(tree.symbol.owner, ctx.owner), From 550ac131596c55a90ab87db94fec078c90cb5a00 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 11:08:25 +0200 Subject: [PATCH 29/70] Avoid creating ConstantTypes by TypeAssigner after erasure. --- src/dotty/tools/dotc/transform/Erasure.scala | 2 +- src/dotty/tools/dotc/typer/TypeAssigner.scala | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index e99a3a91ce35..379a59c0bdd2 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -238,7 +238,7 @@ object Erasure extends TypeTestsCasts{ override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = { assert(tree.hasType) - val erased = erasedType(tree)(ctx.withPhase(ctx.erasurePhase)) + val erased = erasedType(tree) ctx.log(s"promoting ${tree.show}: ${erased.showWithUnderlying()}") tree.withType(erased) } diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 79d86de06789..ccf67b55b9ae 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -209,10 +209,11 @@ trait TypeAssigner { def assignType(tree: untpd.Literal)(implicit ctx: Context) = tree.withType { - tree.const.tag match { + val value = tree.const + value.tag match { case UnitTag => defn.UnitType case NullTag => defn.NullType - case _ => ConstantType(tree.const) + case _ => if (ctx.erasedTypes) value.tpe else ConstantType(value) } } From f3d42cd1358ef6d317a6adb4ae4fc9a28cc01c7e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 19:29:13 +0200 Subject: [PATCH 30/70] Add possibility to compile all files in a directory and all subdirectories. We better make this configurable. Because sometimes we want to compile only the files in the immediate directory. --- test/dotc/tests.scala | 2 +- test/test/CompilerTest.scala | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index aa8cc05524d7..2816224f16d9 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -114,7 +114,7 @@ class tests extends CompilerTest { @Test def dotc_typer = compileDir(dotcDir + "tools/dotc/typer", twice) @Test def dotc_util = compileDir(dotcDir + "tools/dotc/util", twice) @Test def tools_io = compileDir(dotcDir + "tools/io", twice) - @Test def tools = compileDir(dotcDir + "tools", twice)(allowDeepSubtypes) + @Test def tools = compileDir(dotcDir + "tools", "-deep" :: twice)(allowDeepSubtypes) @Test def testNonCyclic = compileArgs(Array( dotcDir + "tools/dotc/CompilationUnit.scala", diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index f5b13845434c..c9c7c602bdea 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -24,8 +24,12 @@ class CompilerTest extends DottyTest { compileDir(Directory(path), args, xerrors) def compileDir(dir: Directory, args: List[String], xerrors: Int)(implicit defaultOptions: List[String]): Unit = { - val fileNames = dir.deepFiles.toArray.map(_.toString).filter(_ endsWith ".scala") - compileArgs(fileNames ++ args, xerrors) + val (files, normArgs) = args match { + case "-deep" :: args1 => (dir.deepFiles, args1) + case _ => (dir.files, args) + } + val fileNames = files.toArray.map(_.toString).filter(_ endsWith ".scala") + compileArgs(fileNames ++ normArgs, xerrors) } def compileFiles(path: String, args: List[String] = Nil)(implicit defaultOptions: List[String]): Unit = { From 35f46b4a0c2d290582981f67498ed77a4ae9b21d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 14:09:08 +0200 Subject: [PATCH 31/70] ElimByName now handles casts to by-name-parameter TermRefs A cast like expr.asInstanceOf[x.type] where x is a by-name parameter has to be mapped to expr.asInstanceOf[x.type].apply() See pos/t296.scala which would otherwise start failing when the pattern matcher is integrated. --- .../tools/dotc/transform/ElimByName.scala | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ElimByName.scala b/src/dotty/tools/dotc/transform/ElimByName.scala index e656c8438958..1d0307398fc3 100644 --- a/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/src/dotty/tools/dotc/transform/ElimByName.scala @@ -76,16 +76,29 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform cpy.Apply(tree)(tree.fun, args1) } - private def becomesFunction(symd: SymDenotation)(implicit ctx: Context) = + /** 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)) - override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = { - val origDenot = originalDenotation(tree) - if (becomesFunction(origDenot) && (origDenot.info.isInstanceOf[ExprType])) + /** 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 + } + def elimByNameParams(tp: Type)(implicit ctx: Context): Type = tp match { case tp: PolyType => tp.derivedPolyType(tp.paramNames, tp.paramBounds, elimByNameParams(tp.resultType)) @@ -102,6 +115,6 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform } def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = - if (becomesFunction(sym)) transformParamInfo(tp) + if (exprBecomesFunction(sym)) transformParamInfo(tp) else elimByNameParams(tp) } From 33113aa693c7290eda504029b14f7fcb8e104391 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Sep 2014 14:45:36 +0200 Subject: [PATCH 32/70] Fix tree typing to account for trees after constructors is run. --- src/dotty/tools/dotc/transform/TreeChecker.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 46886afb6f94..67b291bb2b04 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -130,6 +130,9 @@ class TreeChecker { super.typedStats(trees, exprOwner) } + override def ensureNoLocalRefs(block: Block, pt: Type, forcedDefined: Boolean = false)(implicit ctx: Context): Tree = + block + 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) From f244287fdba4ccf64e2404285b713cee599448ba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 21:24:57 +0200 Subject: [PATCH 33/70] Fix to primaryConstructor Primary constructor was picking last constructor instead of first one. This is now fixed. Also, added paramAccessors utility method. --- src/dotty/tools/dotc/core/SymDenotations.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index cea9be604048..4d0513abfaed 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -200,7 +200,7 @@ object SymDenotations { final def hasAnnotation(cls: Symbol)(implicit ctx: Context) = dropOtherAnnotations(annotations, cls).nonEmpty - /** Optionally, get annotation matching the given class symbol */ + /** Optionally, the annotation matching the given class symbol */ final def getAnnotation(cls: Symbol)(implicit ctx: Context): Option[Annotation] = dropOtherAnnotations(annotations, cls) match { case annot :: _ => Some(annot) @@ -431,7 +431,7 @@ object SymDenotations { /** Does this symbol denote the primary constructor of its enclosing class? */ final def isPrimaryConstructor(implicit ctx: Context) = - isConstructor && owner.primaryConstructor.denot == this + isConstructor && owner.primaryConstructor == symbol /** Is this a subclass of the given class `base`? */ def isSubClass(base: Symbol)(implicit ctx: Context) = false From 2ca36a96aee3f2e5cc9073b8970659180f839f95 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 Oct 2014 19:23:07 +0200 Subject: [PATCH 34/70] Create setters also for private vars Reason: We might need hooks for data representation transformations also for private vars. Private[this] vars in non-trait classes remain setterless. --- src/dotty/tools/dotc/ast/Desugar.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index d7bbdf2bb386..dba3872cb127 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -90,7 +90,7 @@ object desugar { def valDef(vdef: ValDef)(implicit ctx: Context): Tree = { val ValDef(mods, name, tpt, rhs) = vdef def setterNeeded = - (mods is Mutable) && ctx.owner.isClass && (!(mods is Private) || (ctx.owner is Trait)) + (mods is Mutable) && ctx.owner.isClass && (!(mods is PrivateLocal) || (ctx.owner is Trait)) if (setterNeeded) { // todo: copy of vdef as getter needed? // val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos ? From 5de255637589b800ffc99ddb3453ccd651bfafc9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 09:07:14 +0200 Subject: [PATCH 35/70] Fixes to TermRefs and others in TypeErasure. 1) TermRefs are now erased to their widened underlying type. The only exception are top-level term refs (e.g. the types of Ident or Select nodes), which are treated as before. Those top-level refs are treated by erasedRef instead of erasure. 2) We make sure that erasure methods are not run after phase erasure. Reason: After erasure underlying types change, which affects itself the result of erasure. --- src/dotty/tools/dotc/TypeErasure.scala | 46 ++++++++++++++------ src/dotty/tools/dotc/transform/Erasure.scala | 8 ++-- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index f6c62cf30b8b..cb3f76c102d6 100644 --- a/src/dotty/tools/dotc/TypeErasure.scala +++ b/src/dotty/tools/dotc/TypeErasure.scala @@ -87,15 +87,36 @@ object TypeErasure { private val javaSigFn = erasureFn(isJava = true, isSemi = false, isConstructor = false, wildcardOK = true) private val semiErasureFn = erasureFn(isJava = false, isSemi = true, isConstructor = false, wildcardOK = false) - def erasure(tp: Type)(implicit ctx: Context): Type = scalaErasureFn(tp) - def semiErasure(tp: Type)(implicit ctx: Context): Type = semiErasureFn(tp) + /** The current context with a phase no later than erasure */ + private def erasureCtx(implicit ctx: Context) = + if (ctx.erasedTypes) ctx.withPhase(ctx.erasurePhase) else ctx + + def erasure(tp: Type)(implicit ctx: Context): Type = scalaErasureFn(tp)(erasureCtx) + def semiErasure(tp: Type)(implicit ctx: Context): Type = semiErasureFn(tp)(erasureCtx) def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): TypeName = { val normTp = if (tp.isRepeatedParam) tp.translateParameterized(defn.RepeatedParamClass, defn.SeqClass) else tp - (if (isJava) javaSigFn else scalaSigFn).sigName(normTp) + (if (isJava) javaSigFn else scalaSigFn).sigName(normTp)(erasureCtx) + } + + /** The erasure of a top-level reference. Differs from normal erasure in that + * TermRefs are kept instead of being widened away. + */ + def erasedRef(tp: Type)(implicit ctx: Context): Type = tp match { + case tp: TermRef => + assert(tp.symbol.exists, tp) + TermRef(erasedRef(tp.prefix), tp.symbol.asTerm) + case tp => + erasure(tp) } + /** The erasure of a function result type. Differs from normal erasure in that + * Unit is kept instead of being mapped to BoxedUnit. + */ + def eraseResult(tp: Type)(implicit ctx: Context): Type = + scalaErasureFn.eraseResult(tp)(erasureCtx) + /** The symbol's erased info. This is the type's erasure, except for the following symbols: * * - For $asInstanceOf : [T]T @@ -113,8 +134,8 @@ object TypeErasure { if ((sym eq defn.Any_asInstanceOf) || (sym eq defn.Any_isInstanceOf)) eraseParamBounds(sym.info.asInstanceOf[PolyType]) else if (sym.isAbstractType) TypeAlias(WildcardType) - else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)) - else erase(tp) + else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx)) + else erase(tp)(erasureCtx) } def isUnboundedGeneric(tp: Type)(implicit ctx: Context) = !( @@ -225,7 +246,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild * - For NoType or NoPrefix, the type itself. * - For any other type, exception. */ - def apply(tp: Type)(implicit ctx: Context): Type = tp match { + private def apply(tp: Type)(implicit ctx: Context): Type = tp match { case tp: TypeRef => val sym = tp.symbol if (!sym.isClass) this(tp.info) @@ -236,8 +257,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild if (parent isRef defn.ArrayClass) eraseArray(tp) else this(parent) case tp: TermRef => - assert(tp.symbol.exists, tp) - TermRef(this(tp.prefix), tp.symbol.asTerm) + this(tp.widen) case ThisType(_) | SuperType(_, _) => tp case ExprType(rt) => @@ -270,7 +290,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil else removeLaterObjects(classParents.mapConserve(eraseTypeRef)) val erasedDecls = decls.filteredScope(d => !d.isType || d.isClass) - tp.derivedClassInfo(NoPrefix, parents, erasedDecls, this(tp.selfType)) + tp.derivedClassInfo(NoPrefix, parents, erasedDecls, erasedRef(tp.selfType)) // can't replace selftype by NoType because this would lose the sourceModule link } case NoType | NoPrefix | ErrorType | JavaArrayType(_) => @@ -279,7 +299,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild tp } - def eraseArray(tp: RefinedType)(implicit ctx: Context) = { + private def eraseArray(tp: RefinedType)(implicit ctx: Context) = { val defn.ArrayType(elemtp) = tp if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType) else if (isUnboundedGeneric(elemtp)) @@ -328,7 +348,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild /** The name of the type as it is used in `Signature`s. * Need to ensure correspondence with erasure! */ - def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match { + private def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match { case tp: TypeRef => val sym = tp.symbol if (!sym.isClass) sigName(tp.info) @@ -338,8 +358,8 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild sigName(this(tp)) case JavaArrayType(elem) => sigName(elem) ++ "[]" - case tp: TypeBounds => - sigName(tp.hi) + case tp: TermRef => + sigName(tp.widen) case ExprType(rt) => sigName(defn.FunctionType(Nil, rt)) case tp: TypeProxy => diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 379a59c0bdd2..0e1b389ff663 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -234,7 +234,10 @@ object Erasure extends TypeTestsCasts{ class Typer extends typer.ReTyper with NoChecking { import Boxing._ - def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = erasure(tree.typeOpt) + def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = tree.typeOpt match { + case tp: TermRef if tree.isTerm => erasedRef(tp) + case tp => erasure(tp) + } override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = { assert(tree.hasType) @@ -366,8 +369,7 @@ object Erasure extends TypeTestsCasts{ tparams = Nil, vparamss = ddef.vparamss.flatten :: Nil, tpt = // keep UnitTypes intact in result position - if (ddef.tpt.typeOpt isRef defn.UnitClass) untpd.TypeTree(defn.UnitType) withPos ddef.tpt.pos - else ddef.tpt) + untpd.TypedSplice(TypeTree(eraseResult(ddef.tpt.typeOpt)).withPos(ddef.tpt.pos))) super.typedDefDef(ddef1, sym) } From afd9a66453848412ae0a974ddd475da2b0d846bf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 09:27:56 +0200 Subject: [PATCH 36/70] rename AddGetters -> GettersSetters ... and add a gettersSettersPhase to Context. --- src/dotty/tools/dotc/core/NameOps.scala | 32 +++-- src/dotty/tools/dotc/core/Phases.scala | 4 +- src/dotty/tools/dotc/core/StdNames.scala | 4 +- .../tools/dotc/core/SymDenotations.scala | 2 +- .../tools/dotc/transform/GettersSetters.scala | 118 ++++++++++++++++++ 5 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/GettersSetters.scala diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index b18f708edabd..beb3142d3876 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -68,10 +68,11 @@ object NameOps { def isProtectedAccessorName = name startsWith PROTECTED_PREFIX def isReplWrapperName = name containsSlice INTERPRETER_IMPORT_WRAPPER def isSetterName = name endsWith SETTER_SUFFIX - def isTraitSetterName = isSetterName && (name containsSlice TRAIT_SETTER_SEPARATOR) + def isTraitSetterName = isSetterName && (name containsSlice TRAIT_SETTER_PREFIX) def isSingletonName = name endsWith SINGLETON_SUFFIX def isModuleClassName = name endsWith MODULE_SUFFIX def isImportName = name startsWith IMPORT + def isFieldName = name endsWith LOCAL_SUFFIX def isInheritedName = name.length > 0 && name.head == '(' && name.startsWith(nme.INHERITED) def isDefaultGetterName = name.isTermName && name.asTermName.defaultGetterIndex >= 0 @@ -223,23 +224,36 @@ object NameOps { implicit class TermNameDecorator(val name: TermName) extends AnyVal { import nme._ - /** The expanded setter name of `name` relative to this class `base` - */ - def expandedSetterName(base: Symbol)(implicit ctx: Context): TermName = - name.expandedName(base, separator = TRAIT_SETTER_SEPARATOR) + def traitSetterName: TermName = + nme.TRAIT_SETTER_PREFIX ++ setterName + + def setterName: TermName = + if (name.isFieldName) name.fieldToGetter.setterName + else name ++ SETTER_SUFFIX + + def getterName: TermName = + if (name.isFieldName) fieldToGetter + else setterToGetter - def setterName: TermName = name ++ SETTER_SUFFIX + def fieldName: TermName = + if (name.isSetterName) getterName.fieldName + else name ++ LOCAL_SUFFIX - def setterToGetter: TermName = { - val p = name.indexOfSlice(TRAIT_SETTER_SEPARATOR) + private def setterToGetter: TermName = { + val p = name.indexOfSlice(TRAIT_SETTER_PREFIX) if (p >= 0) - (name drop (p + TRAIT_SETTER_SEPARATOR.length)).asTermName.setterToGetter + (name drop (p + TRAIT_SETTER_PREFIX.length)).asTermName.getterName else { assert(name.endsWith(SETTER_SUFFIX), name + " is referenced as a setter but has wrong name format") name.take(name.length - SETTER_SUFFIX.length).asTermName } } + def fieldToGetter: TermName = { + assert(name.isFieldName) + name.take(name.length - LOCAL_SUFFIX.length).asTermName + } + /** Nominally, name$default$N, encoded for * @param Post the parameters position. * @note Default getter name suffixes start at 1, so `pos` has to be adjusted by +1 diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 53c193994457..caeb18f5789d 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -9,7 +9,7 @@ import Denotations._ import config.Printers._ import scala.collection.mutable.{ListBuffer, ArrayBuffer} import dotty.tools.dotc.transform.TreeTransforms.{TreeTransformer, MiniPhase, TreeTransform} -import dotty.tools.dotc.transform.{ExplicitOuter, TreeTransforms, Erasure, Flatten} +import dotty.tools.dotc.transform.{TreeTransforms, ExplicitOuter, Erasure, Flatten, GettersSetters} import Periods._ import typer.{FrontEnd, RefChecks} import ast.tpd @@ -169,12 +169,14 @@ object Phases { private val erasureCache = new PhaseCache(classOf[Erasure]) private val flattenCache = new PhaseCache(classOf[Flatten]) private val explicitOuterCache = new PhaseCache(classOf[ExplicitOuter]) + private val gettersSettersCache = new PhaseCache(classOf[GettersSetters]) def typerPhase = typerCache.phase def refchecksPhase = refChecksCache.phase def erasurePhase = erasureCache.phase def flattenPhase = flattenCache.phase def explicitOuter = explicitOuterCache.phase + def gettersSettersPhase = gettersSettersCache.phase def isAfterTyper(phase: Phase): Boolean = phase.id > typerPhase.id } diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 4f2fe9dba3b9..65eea112816b 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -119,7 +119,7 @@ object StdNames { val SINGLETON_SUFFIX: N = ".type" val SPECIALIZED_SUFFIX: N = "$sp" val SUPER_PREFIX: N = "super$" - val TRAIT_SETTER_SEPARATOR: N = "$_setter_$" + val TRAIT_SETTER_PREFIX: N = "_setter_$" val WHILE_PREFIX: N = "while$" // value types (and AnyRef) are all used as terms as well @@ -226,7 +226,7 @@ object StdNames { val LAZY_LOCAL: N = "$lzy" val LAZY_FIELD_OFFSET: N = "OFFSET$" val LAZY_SLOW_SUFFIX: N = "$lzycompute" - val LOCAL_SUFFIX: N = " " + val LOCAL_SUFFIX: N = "$$local" val UNIVERSE_BUILD_PREFIX: N = "$u.build." val UNIVERSE_BUILD: N = "$u.build" val UNIVERSE_PREFIX: N = "$u." diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 4d0513abfaed..da83811f1779 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -612,7 +612,7 @@ object SymDenotations { /** The field accessed by this getter or setter, or if it does not exist, the getter */ def accessedFieldOrGetter(implicit ctx: Context): Symbol = { - val fieldName = if (isSetter) name.asTermName.setterToGetter else name + val fieldName = if (isSetter) name.asTermName.getterName else name val d = owner.info.decl(fieldName) val field = d.suchThat(!_.is(Method)).symbol def getter = d.suchThat(_.info.isParameterless).symbol diff --git a/src/dotty/tools/dotc/transform/GettersSetters.scala b/src/dotty/tools/dotc/transform/GettersSetters.scala new file mode 100644 index 000000000000..05c70641956d --- /dev/null +++ b/src/dotty/tools/dotc/transform/GettersSetters.scala @@ -0,0 +1,118 @@ +package dotty.tools.dotc +package transform + +import core._ +import DenotTransformers.SymTransformer +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._ + +/** Performs the following rewritings: + * + * val x: T = e + * --> private val x_L: T = e + * def x: T = x_L (if in class) + * --> private notJavaPrivate var x_L: T = e + * def x: T = x_L + * private notJavaPrivate def x_=(x: T): Unit = x_L = x (if in trait) + * var x: T = e + * --> private var x_L: T = e + * def x: T = x_L + * def x_=(x: T): Unit = x_L = x (if in class or trait) + * lazy val x: T = e + * --> lazy def x = e + * + * Furthermore, assignements to mutable vars are replaced by setter calls + * + * p.x = e + * --> p.x_=(e) + */ +class GettersSetters extends MiniPhaseTransform with SymTransformer { thisTransform => + import ast.tpd._ + + override def phaseName = "gettersSetters" + + override def treeTransformPhase = thisTransform.next + + override def transformSym(d: SymDenotation)(implicit ctx: Context): SymDenotation = { + /* def noGetterNeeded = + d.is(Method | Param | JavaDefined) || + d.initial.asInstanceOf[SymDenotation].is(PrivateLocal) && !d.owner.is(Trait) || + d.is(Module) && d.isStatic || + d.isSelfSym + if (d.isTerm && (d.owner.isClass || d.is(Lazy)) && d.info.isValueType && !noGetterNeeded) { + val maybeStable = if (d.isStable) Stable else EmptyFlags + if (d.name.toString == "_") println(i"make accessor $d in ${d.owner} ${d.symbol.id}") + d.copySymDenotation( + initFlags = d.flags | maybeStable | AccessorCreationFlags, + info = ExprType(d.info)) + } + else */ d + } +/* + override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (tree.symbol is Method) { + val getter = tree.symbol.asTerm + assert(getter is Accessor) + if (getter.is(Lazy | Deferred | ParamAccessor)) DefDef(getter, tree.rhs) + else { + val inTrait = getter.owner.is(Trait) + val maybePrivate = + if (inTrait) Private | NotJavaPrivate + else if (getter.owner.isClass) Private + else EmptyFlags + val maybeMutable = + if (inTrait || getter.is(Mutable)) Mutable + else EmptyFlags + val field = ctx.newSymbol( + owner = ctx.owner, + name = getter.name.fieldName, + flags = maybePrivate | maybeMutable, + info = getter.info.resultType).enteredAfter(thisTransform) + assert(tree.rhs.tpe.exists, tree.show) + val fieldDef = + cpy.ValDef(tree)( + mods = tree.mods & EmptyFlags | field.flags, + name = field.name, + rhs = tree.rhs.changeOwner(getter, field).ensureConforms(field.info.widen) + ).withType(field.valRef) + val rhs = ref(field) + assert(rhs.hasType) + val getterDef = DefDef(getter, rhs.ensureConforms(getter.info.widen)) + if (!getter.is(Mutable) && inTrait) { // add a setter anyway, will be needed for mixin + val setter = ctx.newSymbol( + owner = ctx.owner, + name = getter.name.traitSetterName, + flags = (getter.flags & AccessFlags) | Accessor | maybePrivate, + info = MethodType(field.info :: Nil, defn.UnitType)).enteredAfter(thisTransform) + val setterDef = DefDef(setter.asTerm, vrefss => Assign(ref(field), vrefss.head.head)) + Thicket(fieldDef, getterDef, setterDef) + } + else Thicket(fieldDef, getterDef) + } + } + else tree + } + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = + if (tree.symbol.isSetter && !tree.symbol.is(Deferred | ParamAccessor)) { + val Literal(Constant(())) = tree.rhs + val initializer = Assign(ref(tree.symbol.field), ref(tree.vparamss.head.head.symbol)) + assert(initializer.hasType) + cpy.DefDef(tree)(rhs = initializer) + } + else tree + + override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = + if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs) + else tree*/ +} \ No newline at end of file From f0578aa4bd3c87bd00658f4138eef3e4624f035e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 09:09:18 +0200 Subject: [PATCH 37/70] Two fixes to TypeComparer 1) Eliminated a case in the comparison that did not work and was apparently not needed anyway. 2) Augmented another case to also work when paths go through getters. --- src/dotty/tools/dotc/core/TypeComparer.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 6d0f81c72612..f36572755eb8 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -587,11 +587,22 @@ class TypeComparer(initctx: Context) extends DotClass { else thirdTry(tp1, tp2) } tp1.info match { + // There was the following code, which was meant to implement this logic: + // If x has type A | B, then x.type <: C if + // x.type <: C assuming x has type A, and + // x.type <: C assuming x has type B. + // But it did not work, because derivedRef would always give back the same + // type and cache the denotation. So it ended up copmparing just one branch. + // The code seems to be unncessary for the tests and does not seems to help performance. + // So it is commented out. If we ever need to come back to this, we would have + // to create unchached TermRefs in order to avoid cross talk between the branches. + /* case OrType(tp11, tp12) => val sd = tp1.denot.asSingleDenotation def derivedRef(tp: Type) = NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) + */ case TypeBounds(lo1, hi1) => if ((ctx.mode is Mode.GADTflexible) && (tp1.symbol is GADTFlexType) && !isSubTypeWhenFrozen(hi1, tp2)) @@ -762,6 +773,8 @@ class TypeComparer(initctx: Context) extends DotClass { tp2.info match { case tp2i: TermRef => isSubType(tp1, tp2i) + case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersSettersPhase.id) => + isSubType(tp1, tp2i) case _ => false } From 0e6e7bebaccf08796c578ae58c99d0bd5a2336d2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 09:21:06 +0200 Subject: [PATCH 38/70] Re-usable method to determine self-ness. Method isSelfSym decides whether a symbol is the symbol of a self definition in a class. Also, make all self symbols private[this] --- src/dotty/tools/dotc/ast/Trees.scala | 2 +- src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 23 +++++++++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index fb9ad24b3b0d..f1ccfdb75e3e 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -812,7 +812,7 @@ object Trees { } class EmptyValDef[T >: Untyped] extends ValDef[T]( - Modifiers[T](Private), nme.WILDCARD, genericEmptyTree[T], genericEmptyTree[T]) with WithoutTypeOrPos[T] { + Modifiers[T](PrivateLocal), nme.WILDCARD, genericEmptyTree[T], genericEmptyTree[T]) with WithoutTypeOrPos[T] { override def isEmpty: Boolean = true } diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 731917c42bea..5a6c9fa89012 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -208,7 +208,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { makeConstructor(Modifiers(), Nil, Nil) def makeSelfDef(name: TermName, tpt: Tree)(implicit ctx: Context) = - ValDef(Modifiers(Private), name, tpt, EmptyTree) + ValDef(Modifiers(PrivateLocal), name, tpt, EmptyTree) def makeTupleOrParens(ts: List[Tree])(implicit ctx: Context) = ts match { case t :: Nil => Parens(t) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index da83811f1779..ae37ab87cdeb 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -43,14 +43,10 @@ trait SymDenotations { this: Context => if (denot is ValidForever) true else try { val owner = denot.owner.denot - def isSelfSym = owner.infoOrCompleter match { - case ClassInfo(_, _, _, _, selfInfo) => selfInfo == denot.symbol - case _ => false - } stillValid(owner) && ( !owner.isClass || (owner.decls.lookupAll(denot.name) contains denot.symbol) - || isSelfSym + || denot.isSelfSym ) } catch { case ex: StaleSymbol => false @@ -360,6 +356,23 @@ object SymDenotations { /** Is this symbol an abstract or alias type? */ final def isAbstractOrAliasType = isType & !isClass + /** Is this the denotation of a self symbol of some class? + * This is the case if one of two conditions holds: + * 1. It is the symbol referred to in the selfInfo part of the ClassInfo + * which is the type of this symbol's owner. + * 2. This symbol is owned by a class, it's selfInfo field refers to a type + * (indicating the self definition does not introduce a name), and the + * symbol's name is "_". + * TODO: Find a more robust way to characterize self symbols, maybe by + * spending a Flag on them? + */ + final def isSelfSym(implicit ctx: Context) = owner.infoOrCompleter match { + case ClassInfo(_, _, _, _, selfInfo) => + selfInfo == symbol || + selfInfo.isInstanceOf[Type] && name == nme.WILDCARD + case _ => false + } + /** Is this definition contained in `boundary`? * Same as `ownersIterator contains boundary` but more efficient. */ From dfeff382f90a3c20d7b1600785263bc22a9edbbc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 09:45:48 +0200 Subject: [PATCH 39/70] More robust printing or trees The previous test, `symbol != null` crashed for trees that did not have an assigned type. --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ae75bcb2d365..6076ca78754f 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -180,7 +180,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - def useSymbol = ctx.isAfterTyper(ctx.phase) && tree.symbol != null && tree.symbol.exists + def useSymbol = ctx.isAfterTyper(ctx.phase) && tree.hasType && tree.symbol.exists def modText(mods: untpd.Modifiers, kw: String): Text = { // DD val suppressKw = if (ownerIsClass) mods is ParamAndLocal else mods is Param From 939bdf473f31c352a345243effb48bfb01d49b03 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 09:46:20 +0200 Subject: [PATCH 40/70] SymUtil methods to navigate between setters, getters and fields. --- src/dotty/tools/dotc/transform/SymUtils.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/dotty/tools/dotc/transform/SymUtils.scala b/src/dotty/tools/dotc/transform/SymUtils.scala index 5ae9aba63217..82bea1813fcf 100644 --- a/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/src/dotty/tools/dotc/transform/SymUtils.scala @@ -8,6 +8,7 @@ import Symbols._ import Decorators._ import Names._ import StdNames._ +import NameOps._ import Flags._ import language.implicitConversions @@ -46,4 +47,18 @@ class SymUtils(val self: Symbol) extends AnyVal { 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 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) orElse + accessorNamed(self.asTerm.name.traitSetterName) + + def field(implicit ctx: Context): Symbol = + self.owner.info.decl(self.asTerm.name.fieldName).suchThat(!_.is(Method)).symbol } From 88007462d3251cdc3032c724dde873eabfed6427 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 12:21:42 +0200 Subject: [PATCH 41/70] Avoid caching NoDenotations in NamedTypes --- src/dotty/tools/dotc/core/Types.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 6145bdffb809..d93e4eb09338 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1173,7 +1173,12 @@ object Types { else loadDenot } if (ctx.typerState.ephemeral) record("ephemeral cache miss: loadDenot") - else { + else if (d.exists) { + // Avoid storing NoDenotations in the cache - we will not be able to recover from + // them. The situation might arise that a type has NoDenotation in some later + // phase but a defined denotation earlier (e.g. a TypeRef to an abstract type + // is undefined after erasure.) We need to be able to do time travel back and + // forth also in these cases. lastDenotation = d lastSymbol = d.symbol checkedPeriod = ctx.period From c72d404dd12309219d312a5730bed82175840db5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 12:24:15 +0200 Subject: [PATCH 42/70] Replace typed with typedUnadapted. 1) Avoids unneccessary second adapt. 2) Avoids error when used in TreeChecking when we try to compare types in previous tree and new tree - if we used `typed`, the previous tree would not have a type! --- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 46fd17d15822..80eb5965c63f 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -394,7 +394,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val setterTypeRaw = pre select (setterName, setter) val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.pos) val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType) - typed(cpy.Apply(tree)(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) + typedUnadapted(cpy.Apply(tree)(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) case _ => reassignmentToVal } From 6efccf91e7856a4c8fde7daf914e77fef4fb6e52 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 12:26:54 +0200 Subject: [PATCH 43/70] Don't adapt to FunProto in TreeChecker. Similar to Erasure, we do not check conformance wrt <:< to a FunProto. Nothing is lost, because we typecheck the arguments and result anyway. And this led to failing tests after GettersSetters was added. --- src/dotty/tools/dotc/transform/TreeChecker.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 67b291bb2b04..4600ec823bec 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -20,6 +20,7 @@ import reporting.ThrowingReporter import ast.Trees._ import ast.{tpd, untpd} import util.SourcePosition +import ProtoTypes._ import java.lang.AssertionError /** Run by -Ycheck option after a given phase, this class retypes all syntax trees @@ -136,7 +137,7 @@ class TreeChecker { 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 && !isPrimaryConstructorReturn) + if (ctx.mode.isExpr && !isPrimaryConstructorReturn && !pt.isInstanceOf[FunProto]) assert(tree.tpe <:< pt, s"error at ${sourcePos(tree.pos)}\n" + err.typeMismatchStr(tree.tpe, pt)) From fa44d5cd694d76e285ebadcdf297f0d13936a6e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 21:37:06 +0200 Subject: [PATCH 44/70] Enabled GettersSetters phase. --- src/dotty/tools/dotc/Compiler.scala | 14 ++------------ src/dotty/tools/dotc/ast/tpd.scala | 15 ++++++++++++++- src/dotty/tools/dotc/transform/Constructors.scala | 11 ++++++++--- .../tools/dotc/transform/GettersSetters.scala | 8 ++++---- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 63f79c8d391f..239b38338fa6 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -24,17 +24,6 @@ class Compiler { * all refs to it would become outdated - they could not be dereferenced in the * new phase. * - * As an example, addGetters would change a field - * - * val x: T - * - * to a method - * - * def x: T - * - * but this would affect the signature of `x` (goes from NotAMethod to a method - * signature). So we can't do this before erasure. - * * After erasure, signature changing denot-transformers are OK because erasure * will make sure that only term refs with fixed SymDenotations survive beyond it. This * is possible because: @@ -61,7 +50,8 @@ class Compiler { new Splitter), List(new ElimByName, new InterceptedMethods, - new Literalize), + new Literalize, + new GettersSetters), List(new Erasure), List(new CapturedVars, new Constructors)/*, List(new LambdaLift)*/ diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 45e0aff541eb..2a6ec9d78141 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -2,8 +2,8 @@ package dotty.tools package dotc package ast +import transform.SymUtils._ import core._ -import dotty.tools.dotc.transform.TypeUtils import util.Positions._, Types._, Contexts._, Constants._, Names._, Flags._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Symbols._ import Denotations._, Decorators._ @@ -599,6 +599,19 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def or(that: Tree)(implicit ctx: Context): Tree = tree.select(defn.Boolean_||).appliedTo(that) + def becomes(rhs: Tree)(implicit ctx: Context): Tree = + if (tree.symbol is Method) { + val setr = tree match { + case Ident(_) => + val setter = tree.symbol.setter + assert(setter.exists, tree.symbol.showLocated) + ref(tree.symbol.setter) + case Select(qual, _) => qual.select(tree.symbol.setter) + } + setr.appliedTo(rhs) + } + else Assign(tree, rhs) + // --- Higher order traversal methods ------------------------------- def foreachSubTree(f: Tree => Unit): Unit = { //TODO should go in tpd. diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index 9cb348dfff5e..7bde1ba4ffa4 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -16,6 +16,7 @@ import SymDenotations._ import Types._ import Decorators._ import DenotTransformers._ +import util.Positions._ import collection.mutable /** This transform @@ -76,7 +77,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor case _ => } assert(accessors.hasSameLengthAs(vparamsWithOuter), - i"lengths differ for $cls, param accs = $accessors, params = $vparamsWithOuter") + i"lengths differ for $cls, param accs = $accessors, params = ($vparamsWithOuter%, %)") } val paramSyms = vparamsWithOuter map (_.symbol) @@ -177,6 +178,10 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor val constrStats, clsStats = new mutable.ListBuffer[Tree] + def assign(vble: Symbol, rhs: Tree): Tree = + if (cls is Trait) ref(vble.setter).appliedTo(rhs) + else Assign(ref(vble), rhs) + // 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 { @@ -186,7 +191,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor val sym = stat.symbol if (isRetained(sym)) { if (!rhs.isEmpty && !isWildcardArg(rhs)) - constrStats += Assign(ref(sym), intoConstr(rhs)).withPos(stat.pos) + constrStats += assign(sym, intoConstr(rhs)).withPos(stat.pos) clsStats += cpy.ValDef(stat)(rhs = EmptyTree) } else if (!rhs.isEmpty) { @@ -210,7 +215,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor // The initializers for the retained accessors */ val copyParams = accessorFields.filter(isRetained).map(acc => - Assign(ref(acc), ref(acc.subst(accessors, paramSyms))).withPos(tree.pos)) + assign(acc, ref(acc.subst(accessors, paramSyms))).withPos(tree.pos)) // Drop accessors that are not retained from class scope val dropped = usage.dropped diff --git a/src/dotty/tools/dotc/transform/GettersSetters.scala b/src/dotty/tools/dotc/transform/GettersSetters.scala index 05c70641956d..772a63e52a6c 100644 --- a/src/dotty/tools/dotc/transform/GettersSetters.scala +++ b/src/dotty/tools/dotc/transform/GettersSetters.scala @@ -44,7 +44,7 @@ class GettersSetters extends MiniPhaseTransform with SymTransformer { thisTransf override def treeTransformPhase = thisTransform.next override def transformSym(d: SymDenotation)(implicit ctx: Context): SymDenotation = { - /* def noGetterNeeded = + def noGetterNeeded = d.is(Method | Param | JavaDefined) || d.initial.asInstanceOf[SymDenotation].is(PrivateLocal) && !d.owner.is(Trait) || d.is(Module) && d.isStatic || @@ -56,9 +56,9 @@ class GettersSetters extends MiniPhaseTransform with SymTransformer { thisTransf initFlags = d.flags | maybeStable | AccessorCreationFlags, info = ExprType(d.info)) } - else */ d + else d } -/* + override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = { if (tree.symbol is Method) { val getter = tree.symbol.asTerm @@ -114,5 +114,5 @@ class GettersSetters extends MiniPhaseTransform with SymTransformer { thisTransf override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs) - else tree*/ + else tree } \ No newline at end of file From fd81127c6255d01237dd98e8296abf6fdfe80741 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Oct 2014 14:23:38 +0200 Subject: [PATCH 45/70] New tests --- tests/pending/pos/annot.scala | 5 +++ tests/pending/pos/t1672.scala | 36 ++++++++++++++++++++ tests/pending/pos/vararg-pattern.scala | 12 +++++++ tests/pos/array-clone.scala | 7 ++++ tests/pos/getset.scala | 23 +++++++++++++ tests/pos/lambdalift.scala | 46 ++++++++++++++++++++++++++ tests/pos/points.scala | 8 +++++ tests/pos/synthetics.scala | 4 +++ tests/pos/t7093.scala | 27 +++++++++++++++ tests/pos/unapply.scala | 11 ++++++ 10 files changed, 179 insertions(+) create mode 100644 tests/pending/pos/annot.scala create mode 100644 tests/pending/pos/t1672.scala create mode 100644 tests/pending/pos/vararg-pattern.scala create mode 100644 tests/pos/array-clone.scala create mode 100644 tests/pos/getset.scala create mode 100644 tests/pos/lambdalift.scala create mode 100644 tests/pos/points.scala create mode 100644 tests/pos/synthetics.scala create mode 100644 tests/pos/t7093.scala create mode 100644 tests/pos/unapply.scala diff --git a/tests/pending/pos/annot.scala b/tests/pending/pos/annot.scala new file mode 100644 index 000000000000..8e21803b431b --- /dev/null +++ b/tests/pending/pos/annot.scala @@ -0,0 +1,5 @@ +class Test { + + @SuppressWarnings("hi") def foo() = ??? + +} diff --git a/tests/pending/pos/t1672.scala b/tests/pending/pos/t1672.scala new file mode 100644 index 000000000000..77a86db2244e --- /dev/null +++ b/tests/pending/pos/t1672.scala @@ -0,0 +1,36 @@ +// moved to pending. +/* Tail calls translates this program to: + +final lazy object Test1672: Test1672$ = new Test1672$() + final class Test1672$() extends Object() { this: Test1672$.type => + @tailrec def bar: (x: Int)(y: Int)Nothing = { + def tailLabel2: ($this: Test1672$.type)(x: Int)(y: Int)Nothing = { + try { + throw new scala.package.RuntimeException() + } catch { + def $anonfun: (x$1: Throwable)Nothing = + x$1 match { + case _: scala.package.Throwable => + tailLabel2($this)(x)(y) + } + closure($anonfun) + } + } + tailLabel2(Test1672$.this)(x)(y) + } + } + +Note the tail call to taillabel2 from the local method $anonfun. +LambdaLift doe snot know how to deal wioth this. +*/ + +object Test1672 { + @annotation.tailrec + def bar(x: Int)(y: Int) : Nothing = { + try { + throw new RuntimeException + } catch { + case _: Throwable => bar(x)(y) + } + } +} diff --git a/tests/pending/pos/vararg-pattern.scala b/tests/pending/pos/vararg-pattern.scala new file mode 100644 index 000000000000..314d6460f840 --- /dev/null +++ b/tests/pending/pos/vararg-pattern.scala @@ -0,0 +1,12 @@ +object Test { + + List(1, 2, 3, 4) match { + case List(1, 2, xs: _*) => + val ys: Seq[Int] = xs + println(ys) + } + val List(1, 2, x: _*) = List(1, 2, 3, 4) + +} + + diff --git a/tests/pos/array-clone.scala b/tests/pos/array-clone.scala new file mode 100644 index 000000000000..ef5ac5c85911 --- /dev/null +++ b/tests/pos/array-clone.scala @@ -0,0 +1,7 @@ +object test { + + val xs = Array(1, 2, 3) + + val ys: Array[Int] = xs.clone() + +} diff --git a/tests/pos/getset.scala b/tests/pos/getset.scala new file mode 100644 index 000000000000..7b6207e94bcd --- /dev/null +++ b/tests/pos/getset.scala @@ -0,0 +1,23 @@ +package test + +trait T { + + val x = 2 + + var y = 2 + + private[this] var z = 3 + + private var a = 3 + +} +class C { + + val x = 2 + + var y = 2 + + private[this] var z = 3 + + private var a = 3 +} diff --git a/tests/pos/lambdalift.scala b/tests/pos/lambdalift.scala new file mode 100644 index 000000000000..febae6828bc6 --- /dev/null +++ b/tests/pos/lambdalift.scala @@ -0,0 +1,46 @@ +object test { + + def foo(x: Int) = { + + def bar(y: Int) = x + y + def baz(z: Int) = bar(z) + + baz(1) + + } + + def foo2(x: Int) = { + + class C { + def bam(y: Int): String => Int = { + def baz = x + y + z => baz * z.length + } + } + + val fun = new C().bam(1) + fun("abc") + + } +} + +class Super(x: Int) + +class Sub extends Super({ + def foo3(x: Int) = { + + class C { + def this(name: String) = this() + + def bam(y: Int): String => Int = { + def baz = x + y + z => baz * z.length + } + } + + val fun = new C("dummy").bam(1) + fun("abc") + + } + foo3(22) +}) diff --git a/tests/pos/points.scala b/tests/pos/points.scala new file mode 100644 index 000000000000..db6104c883e5 --- /dev/null +++ b/tests/pos/points.scala @@ -0,0 +1,8 @@ +class Point extends Comparable[Point] { + override def compareTo(other: Point): Int = ??? +} + +class ColoredPoint extends Point with Comparable[ColoredPoint] { + override def compareTo(other: ColoredPoint): Int = ??? +} + diff --git a/tests/pos/synthetics.scala b/tests/pos/synthetics.scala new file mode 100644 index 000000000000..c7d49df70de9 --- /dev/null +++ b/tests/pos/synthetics.scala @@ -0,0 +1,4 @@ +case class C(x: Int, var y: String) { + +} + diff --git a/tests/pos/t7093.scala b/tests/pos/t7093.scala new file mode 100644 index 000000000000..287b7a78c726 --- /dev/null +++ b/tests/pos/t7093.scala @@ -0,0 +1,27 @@ +object Test { + + trait A[+X] { + protected[this] def f(x: X): X = x + } + + trait B extends A[B] { + def kaboom = f(new B {}) + } + +// protected[this] disables variance checking +// of the signature of `f`. +// +// C's parent list unifies A[B] with A[C] +// +// The protected[this] loophole is widely used +// in the collections, every newBuilder method +// would fail variance checking otherwise. + class C extends B with A[C] { + override protected[this] def f(c: C) = c + } + +// java.lang.ClassCastException: B$$anon$1 cannot be cast to C +// at C.f(:15) + new C().kaboom +} + diff --git a/tests/pos/unapply.scala b/tests/pos/unapply.scala new file mode 100644 index 000000000000..ba885be7375a --- /dev/null +++ b/tests/pos/unapply.scala @@ -0,0 +1,11 @@ +object test { + class Foo[T](val arg : T) + + object Foo { + def unapply [a](m : Foo[a]) = Some (m.arg) + } + def matchAndGetArgFromFoo[b]( e:Foo[b]):b = {e match { case Foo(x) => x }} +// Unapply node here will have type argument [a] instantiated to scala.Nothing: +// UnApply(TypeApply(Select(Ident(Foo),unapply),List(TypeTree[TypeVar(PolyParam(a) -> TypeRef(ThisType(TypeRef(NoPrefix,scala)),Nothing))])),List(),List(Bind(x,Ident(_)))) +// but the type of the UnApply node itself is correct: RefinedType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,)),test$)),Foo), test$$Foo$$a, TypeAlias(TypeRef(NoPrefix,a))) +} From b371bae865ab61d984345df1cd1ddc776110a36b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 21:58:19 +0200 Subject: [PATCH 46/70] Bring back lost AccessorCreationFlags Was lost in merge chaos between pattern macther and rest --- src/dotty/tools/dotc/core/Flags.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index de27282fbb44..fd55a36673a5 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -459,6 +459,9 @@ object Flags { /** Module classes always have these flags set */ final val ModuleClassCreationFlags = ModuleClass | Final + /** Accessors always have these flags set */ + final val AccessorCreationFlags = Method | Accessor + /** The flags of the self symbol */ final val SelfSymFlags = Private | Local | Deferred From 41211c597dec676b258a20010f8147fb87167664 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 10 Oct 2014 23:15:51 +0200 Subject: [PATCH 47/70] Fix typing of _* patterns. And bring back two tests for typing of patterns --- test/dotc/tests.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 2816224f16d9..707e04babf28 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -31,6 +31,7 @@ class tests extends CompilerTest { val dotcDir = "./src/dotty/" + @Test def pos_t2168_pat = compileFile(posDir, "t2168", doErase) @Test def pos_erasure = compileFile(posDir, "erasure", doErase) @Test def pos_Coder() = compileFile(posDir, "Coder", doErase) @Test def pos_blockescapes() = compileFile(posDir, "blockescapes", doErase) From 73e7da5ecae2aa77f0adf23a430092e039cb2c9a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 00:14:13 +0200 Subject: [PATCH 48/70] Make widening ops more systematic & complete 1) strip TypeVars where necessary before widening 2) allow the combination of widening and dealiasing, in any order. --- src/dotty/tools/dotc/TypeErasure.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index cb3f76c102d6..9b0d73e88e9e 100644 --- a/src/dotty/tools/dotc/TypeErasure.scala +++ b/src/dotty/tools/dotc/TypeErasure.scala @@ -8,8 +8,8 @@ import util.DotClass /** Erased types are: * - * TypeRefWithFixedSym(denot is ClassDenotation) - * TermRefWithFixedSym(denot is SymDenotation) + * TypeRef(prefix is ignored, denot is ClassDenotation) + * TermRef(prefix is ignored, denot is SymDenotation) * JavaArrayType * AnnotatedType * MethodType From 4e897165d9dbd58536a176b9901e0c62457e6b4d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 22:05:25 +0200 Subject: [PATCH 49/70] Fix bug in varianceInType --- src/dotty/tools/dotc/typer/Variances.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Variances.scala b/src/dotty/tools/dotc/typer/Variances.scala index 44b64553b966..0fec1e5a772c 100644 --- a/src/dotty/tools/dotc/typer/Variances.scala +++ b/src/dotty/tools/dotc/typer/Variances.scala @@ -68,11 +68,10 @@ object Variances { /** Compute variance of type parameter tparam in type tp. */ def varianceInType(tp: Type)(tparam: Symbol)(implicit ctx: Context): Variance = tp match { - case TermRef(pre, sym) => + case TermRef(pre, _) => varianceInType(pre)(tparam) - case TypeRef(pre, sym) => - /* @odersky sym is a typeName here, comparison is always false */ - if (sym == tparam) Covariant else varianceInType(pre)(tparam) + case tp @ TypeRef(pre, _) => + if (tp.symbol == tparam) Covariant else varianceInType(pre)(tparam) case tp @ TypeBounds(lo, hi) => if (lo eq hi) compose(varianceInType(hi)(tparam), tp.variance) else flip(varianceInType(lo)(tparam)) & varianceInType(hi)(tparam) From 74fa107dab3c2442bf14753926bb82f7325645ad Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 22:06:17 +0200 Subject: [PATCH 50/70] Moved caseAccessors to SymUtils --- src/dotty/tools/dotc/transform/SymUtils.scala | 3 +++ src/dotty/tools/dotc/transform/SyntheticMethods.scala | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/SymUtils.scala b/src/dotty/tools/dotc/transform/SymUtils.scala index 82bea1813fcf..2875327c4712 100644 --- a/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/src/dotty/tools/dotc/transform/SymUtils.scala @@ -51,6 +51,9 @@ class SymUtils(val self: Symbol) extends AnyVal { def accessorNamed(name: TermName)(implicit ctx: Context): Symbol = self.owner.info.decl(name).suchThat(_ is Accessor).symbol + def caseAccessors(implicit ctx:Context) = + self.decls.filter(_ is CaseAccessor).toList + def getter(implicit ctx: Context): Symbol = if (self.isGetter) self else accessorNamed(self.asTerm.name.getterName) diff --git a/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/src/dotty/tools/dotc/transform/SyntheticMethods.scala index 34cca2872fdc..128449efa73a 100644 --- a/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc package transform import core._ -import Symbols._, Types._, Contexts._, Names._, StdNames._, Constants._ +import Symbols._, Types._, Contexts._, Names._, StdNames._, Constants._, SymUtils._ import scala.collection.{ mutable, immutable } import Flags._ import TreeTransforms._ @@ -48,7 +48,7 @@ class SyntheticMethods extends MiniPhaseTransform with IdentityDenotTransformer */ def syntheticMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = { val clazzType = clazz.typeRef - def accessors = clazz.decls.filter(_ is CaseAccessor).toList + lazy val accessors = clazz.caseAccessors val symbolsToSynthesize: List[Symbol] = if (clazz.is(Case)) caseSymbols From 3c6cf3936228fc317b90ffca8b5d9dda2883e508 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 12:14:06 +0200 Subject: [PATCH 51/70] TypeTestCasts should test and cast wrt erased types --- src/dotty/tools/dotc/transform/TypeTestsCasts.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 6f5f29b6034c..c25e81af9c22 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -13,6 +13,7 @@ import core.TypeErasure.isUnboundedGeneric import typer.ErrorReporting._ import ast.Trees._ import Erasure.Boxing._ +import core.TypeErasure._ /** This transform normalizes type tests and type casts, * also replacing type tests with singleton argument type with reference equality check @@ -92,11 +93,11 @@ trait TypeTestsCasts { else derivedTree(qual, defn.Any_asInstanceOf, argType) } - + def erasedArg = erasure(tree.args.head.tpe) if (sym eq defn.Any_isInstanceOf) - transformIsInstanceOf(qual, tree.args.head.tpe) + transformIsInstanceOf(qual, erasedArg) else if (sym eq defn.Any_asInstanceOf) - transformAsInstanceOf(tree.args.head.tpe) + transformAsInstanceOf(erasedArg) else tree case _ => From ce826ee7dd9beed775a614549b412df68f13fcac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 14:10:46 +0200 Subject: [PATCH 52/70] Avoid generating Any in erasedLubs ... because Any is not an erasedType (this is now checked) --- src/dotty/tools/dotc/TypeErasure.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index 9b0d73e88e9e..50aaafc82279 100644 --- a/src/dotty/tools/dotc/TypeErasure.scala +++ b/src/dotty/tools/dotc/TypeErasure.scala @@ -31,7 +31,7 @@ object TypeErasure { */ def isErasedType(tp: Type)(implicit ctx: Context): Boolean = tp match { case tp: TypeRef => - tp.symbol.isClass + tp.symbol.isClass && tp.symbol != defn.AnyClass case _: TermRef => true case JavaArrayType(elem) => @@ -168,7 +168,7 @@ object TypeErasure { def loop(bcs: List[ClassSymbol], bestSoFar: ClassSymbol): ClassSymbol = bcs match { case bc :: bcs1 => if (cls2.derivesFrom(bc)) - if (!bc.is(Trait)) bc + if (!bc.is(Trait) && bc != defn.AnyClass) bc else loop(bcs1, if (bestSoFar.derivesFrom(bc)) bestSoFar else bc) else loop(bcs1, bestSoFar) From 1f0b25bf3099aebafeddc2c90f04e2d621f00814 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 19:08:24 +0200 Subject: [PATCH 53/70] Make Scala2Existentials valid forever Avoids a stale symbol error when compiling all files in tools together. --- src/dotty/tools/dotc/core/Flags.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index fd55a36673a5..a27dd6614fac 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -529,7 +529,7 @@ object Flags { final val HasDefaultParams = DefaultParameterized | InheritedDefaultParams /** Is valid forever */ - final val ValidForever = Package | Permanent + final val ValidForever = Package | Permanent | Scala2ExistentialCommon /** Is a default parameter in Scala 2*/ final val DefaultParameter = allOf(Param, DefaultParameterized) From 8e9404b27bb73d16e2883ee682617011f9abc8fa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 19:09:48 +0200 Subject: [PATCH 54/70] Avoid capturing an old context in Classfile#typeParamCompleter Previously caused an illegal backwards timetravel in bringForward when compiling all files in tools together twice. --- src/dotty/tools/dotc/core/pickling/ClassfileParser.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala index 193c872f1a47..67f8255025f1 100644 --- a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala @@ -226,7 +226,9 @@ class ClassfileParser( while (!isDelimiter(sig(index))) { index += 1 } sig.slice(start, index) } - def sig2type(tparams: immutable.Map[Name,Symbol], skiptvs: Boolean): Type = { + // Warning: sigToType contains nested completers which might be forced in a later run! + // So local methods need their own ctx parameters. + def sig2type(tparams: immutable.Map[Name,Symbol], skiptvs: Boolean)(implicit ctx: Context): Type = { val tag = sig(index); index += 1 (tag: @switch) match { case BYTE_TAG => defn.ByteType @@ -321,7 +323,7 @@ class ClassfileParser( } } // sig2type(tparams, skiptvs) - def sig2typeBounds(tparams: immutable.Map[Name, Symbol], skiptvs: Boolean): Type = { + def sig2typeBounds(tparams: immutable.Map[Name, Symbol], skiptvs: Boolean)(implicit ctx: Context): Type = { val ts = new ListBuffer[Type] while (sig(index) == ':') { index += 1 From 765960f1bdacc228d51f3841acfa2fd2317f2814 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Sun, 12 Oct 2014 11:07:11 +0200 Subject: [PATCH 55/70] Rename explicitOuter to explicitOuterPhase in Phases --- src/dotty/tools/dotc/core/Phases.scala | 2 +- src/dotty/tools/dotc/transform/ExplicitOuter.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index caeb18f5789d..7b589fec1e0e 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -175,7 +175,7 @@ object Phases { def refchecksPhase = refChecksCache.phase def erasurePhase = erasureCache.phase def flattenPhase = flattenCache.phase - def explicitOuter = explicitOuterCache.phase + def explicitOuterPhase = explicitOuterCache.phase def gettersSettersPhase = gettersSettersCache.phase def isAfterTyper(phase: Phase): Boolean = phase.id > typerPhase.id diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index e07be8603d1b..179f8d7124e6 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -112,7 +112,7 @@ object ExplicitOuter { def ensureOuterAccessors(cls: ClassSymbol)(implicit ctx: Context): Unit = { //todo: implementing #165 would simplify this logic val prevPhase = ctx.phase.prev - assert(prevPhase.id <= ctx.explicitOuter.id, "can add $outer symbols only before ExplicitOuter") + 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])) From cf5999c497635cc891fbff65671479467e42bc5a Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Sun, 12 Oct 2014 11:10:07 +0200 Subject: [PATCH 56/70] Remove appliedIfMethod use ensureApplied instead --- src/dotty/tools/dotc/ast/tpd.scala | 15 ++++----------- .../tools/dotc/transform/PatternMatcher.scala | 12 ++++++------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 2a6ec9d78141..9b7c9cbae810 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -44,9 +44,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Apply(fn: Tree, args: List[Tree])(implicit ctx: Context): Apply = ta.assignType(untpd.Apply(fn, args), fn, args) - def ensureApplied(fn: Tree)(implicit ctx: Context): Tree = - if (fn.tpe.widen.isParameterless) fn else Apply(fn, Nil) - def TypeApply(fn: Tree, args: List[Tree])(implicit ctx: Context): TypeApply = ta.assignType(untpd.TypeApply(fn, args), fn, args) @@ -566,13 +563,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def appliedToNone(implicit ctx: Context): Apply = appliedToArgs(Nil) - def appliedIfMethod(implicit ctx: Context): Tree = { - tree.tpe.widen match { - case fntpe: MethodType => appliedToArgs(Nil) - case _ => tree - } - } - def appliedToType(targ: Type)(implicit ctx: Context): Tree = appliedToTypes(targ :: Nil) @@ -582,6 +572,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def appliedToTypeTrees(targs: List[Tree])(implicit ctx: Context): Tree = if (targs.isEmpty) tree else TypeApply(tree, targs) + def ensureApplied(implicit ctx: Context): Tree = + if (tree.tpe.widen.isParameterless) tree else tree.appliedToNone + def isInstance(tp: Type)(implicit ctx: Context): Tree = tree.select(defn.Any_isInstanceOf).appliedToType(tp) @@ -638,7 +631,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { val mname = ("to" + numericCls.name).toTermName val conversion = tree.tpe member mname if (conversion.symbol.exists) - ensureApplied(tree.select(conversion.symbol.termRef)) + tree.select(conversion.symbol.termRef).ensureApplied else if (tree.tpe.widen isRef numericCls) tree else { diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 97525e2891d2..9c8e1638616e 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -20,7 +20,7 @@ import typer.ErrorReporting._ import ast.Trees._ import Applications._ import TypeApplications._ -import TypeUtils._ +import SymUtils._, core.NameOps._ import dotty.tools.dotc.util.Positions.Position import dotty.tools.dotc.core.Decorators._ @@ -112,7 +112,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans 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).appliedIfMethod + else tgt.select(defn.Seq_head).ensureApplied } // Right now this blindly calls drop on the result of the unapplySeq @@ -237,7 +237,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val matchFail = newSynthCaseLabel(ctx.freshName("matchFail"), MethodType(Nil, restpe)) val catchAllDefBody = DefDef(matchFail, catchAllDef) - val nextCases = (caseSyms.tail ::: List(matchFail)).map(ref(_).appliedIfMethod) + 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) => @@ -248,7 +248,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val caseBody = DefDef(sym, _ => Block(List(acc), body)) - Block(List(caseBody),ref(sym).appliedIfMethod) + Block(List(caseBody),ref(sym).ensureApplied) }} @@ -278,7 +278,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val isDefined = extractorMemberType(prev.tpe, nme.isDefined) if ((isDefined isRef defn.BooleanClass) && getTp.exists) { - val prevValue = ref(prevSym).select("get".toTermName).appliedIfMethod + val prevValue = ref(prevSym).select("get".toTermName).ensureApplied Block( List(ValDef(prevSym, prev)), // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) @@ -1800,7 +1800,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans if ((extractorMemberType(resultType, nme.isDefined) isRef defn.BooleanClass) && resultOfGet.exists) getUnapplySelectors(resultOfGet, args) else if (defn.isProductSubType(resultType)) productSelectorTypes(resultType) - else if (resultType =:= defn.BooleanType) Nil + else if (resultType isRef defn.BooleanClass) Nil else { ctx.error(i"invalid return type in Unapply node: $resultType") Nil From e9be067cd1f46f0ba0fbabaefd31716e85fefc47 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Sun, 12 Oct 2014 11:12:01 +0200 Subject: [PATCH 57/70] move caseAccessors from TypeUtils They are in SymUtils --- src/dotty/tools/dotc/transform/PatternMatcher.scala | 4 ++-- src/dotty/tools/dotc/transform/TypeUtils.scala | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9c8e1638616e..29c05ca61763 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1391,7 +1391,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val accessors = if (defn.isProductSubType(binder.info)) productSelectors(binder.info) - else binder.info.caseAccessors + 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 @@ -1480,7 +1480,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans * 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.info.caseAccessors + 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 diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala index ceb4048a7060..e510fcc88aa4 100644 --- a/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -26,6 +26,4 @@ class TypeUtils(val self: Type) extends AnyVal { def isPrimitiveValueType(implicit ctx: Context): Boolean = self.classSymbol.isPrimitiveValueClass - - def caseAccessors(implicit ctx:Context) = self.decls.filter(x => x.is(Flags.CaseAccessor) && x.is(Flags.Method)).toList -} \ No newline at end of file +} From c1cc79befdd5e5f7eaf7a2bc69cc70e6959ca57d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Oct 2014 22:45:34 +0200 Subject: [PATCH 58/70] Adaptations to make pattern matcher in new world Tests now always include erasure (before quite a few tests failed when erasure was enableed). By contrast lazy vals creates problems with erasure, disabled for now. Some other small polishings on integration of pattern matcher with rest of dotc. Deep recompilation of tools still fails, currently disabled. --- src/dotty/tools/dotc/Compiler.scala | 2 +- src/dotty/tools/dotc/ElimLocals.scala | 2 +- src/dotty/tools/dotc/Flatten.scala | 2 +- src/dotty/tools/dotc/core/Definitions.scala | 2 +- src/dotty/tools/dotc/core/StdNames.scala | 9 --------- src/dotty/tools/dotc/transform/PatternMatcher.scala | 2 +- src/dotty/tools/dotc/transform/TreeChecker.scala | 4 ++-- test/dotc/tests.scala | 12 ++++-------- 8 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 239b38338fa6..1aa1cce1073d 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -46,7 +46,7 @@ class Compiler { new TailRec), List(new PatternMatcher, new ExplicitOuter, - new LazyValsTransform, + // new LazyValTranformContext().transformer, // disabled, awaiting fixes new Splitter), List(new ElimByName, new InterceptedMethods, diff --git a/src/dotty/tools/dotc/ElimLocals.scala b/src/dotty/tools/dotc/ElimLocals.scala index 20afc8eb4dc4..d18ad0288f9c 100644 --- a/src/dotty/tools/dotc/ElimLocals.scala +++ b/src/dotty/tools/dotc/ElimLocals.scala @@ -19,4 +19,4 @@ class ElimLocals extends MiniPhaseTransform with SymTransformer { thisTransforme private def dropLocal(ref: SymDenotation)(implicit ctx: Context) = if (ref.flags is Local) ref.copySymDenotation(initFlags = ref.flags &~ Local) else ref -} \ No newline at end of file +} diff --git a/src/dotty/tools/dotc/Flatten.scala b/src/dotty/tools/dotc/Flatten.scala index 71f669e26717..d7ccc1ae4e11 100644 --- a/src/dotty/tools/dotc/Flatten.scala +++ b/src/dotty/tools/dotc/Flatten.scala @@ -12,4 +12,4 @@ class Flatten extends MiniPhaseTransform with SymTransformer { thisTransformer = override def phaseName = "flatten" def transformSym(ref: SymDenotation)(implicit ctx: Context) = ??? -} \ No newline at end of file +} diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index b74506caf45a..b1c2baff6cd7 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -4,7 +4,6 @@ package core import Types._, Contexts._, Symbols._, Denotations._, SymDenotations._, StdNames._, Names._ import Flags._, Scopes._, Decorators._, NameOps._, util.Positions._ -import TypeApplications._ import pickling.UnPickler.ensureConstructor import scala.annotation.{ switch, meta } import scala.collection.{ mutable, immutable } @@ -192,6 +191,7 @@ class Definitions { lazy val ScalaStaticsClass = ScalaStaticsModule.moduleClass.asClass def staticsMethod(name: PreName) = ctx.requiredMethod(ScalaStaticsClass, name) + lazy val DottyPredefModule = ctx.requiredModule("dotty.DottyPredef") lazy val NilModule = ctx.requiredModule("scala.collection.immutable.Nil") lazy val PredefConformsClass = ctx.requiredClass("scala.Predef." + tpnme.Conforms) diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 65eea112816b..f7354a8b423d 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -685,15 +685,6 @@ object StdNames { def newBitmapName(bitmapPrefix: TermName, n: Int): TermName = bitmapPrefix ++ n.toString def selectorName(n: Int): TermName = "_" + (n + 1) - /** Is name a variable name? */ - def isVariableName(name: Name): Boolean = { - val first = name.firstChar - ( ((first.isLower && first.isLetter) || first == '_') - && (name != nme.false_) - && (name != nme.true_) - && (name != nme.null_) - ) - } object primitive { val arrayApply: TermName = "[]apply" diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 29c05ca61763..9087ba0c4bc0 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -945,7 +945,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def isVarPattern(pat: Tree): Boolean = pat match { case x: BackquotedIdent => false - case x: Ident => nme.isVariableName(x.name) + case x: Ident => x.name.isVariableName case _ => false } diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 4600ec823bec..1b5cc7c07c1a 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -47,6 +47,7 @@ class TreeChecker { case _ => Nil } + def check(phasesToRun: Seq[Phase], ctx: Context) = { println(s"checking ${ctx.compilationUnit} after phase ${ctx.phase.prev}") val checkingCtx = ctx.fresh @@ -99,8 +100,7 @@ class TreeChecker { private def checkOwner(tree: untpd.Tree)(implicit ctx: Context): Unit = { def ownerMatches(symOwner: Symbol, ctxOwner: Symbol): Boolean = symOwner == ctxOwner || - ctxOwner.isWeakOwner && (!(ctxOwner is Method | Lazy | Mutable) || (ctxOwner is Label)) && - ownerMatches(symOwner, ctxOwner.owner) + ctxOwner.isWeakOwner && ownerMatches(symOwner, ctxOwner.owner) if(!ownerMatches(tree.symbol.owner, ctx.owner)) { assert(ownerMatches(tree.symbol.owner, ctx.owner), i"bad owner; ${tree.symbol} has owner ${tree.symbol.owner}, expected was ${ctx.owner}\n" + diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 707e04babf28..dccd96eed215 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -15,15 +15,12 @@ class tests extends CompilerTest { implicit val defaultOptions = noCheckOptions ++ List( "-YnoDeepSubtypes", - "-Ycheck:patternMatcher,literalize,capturedVars", - "-Ystop-before:erasure" - /*,"-uniqid", "-explaintypes", "-verbose", "-Ylog:splitter", "-Xprompt", "-YnoDoubleBindings"*/ + "-Ycheck:patternMatcher,gettersSetters,constructors" ) - val allowDeepSubtypes = defaultOptions diff List("-YnoDeepSubtypes") - - val twice = List("#runs", "2", "-YnoDoubleBindings", "-Ystop-before:terminal") + val twice = List("#runs", "2", "-YnoDoubleBindings") val doErase = List("-Ystop-before:terminal") + val allowDeepSubtypes = defaultOptions diff List("-YnoDeepSubtypes") val posDir = "./tests/pos/" val negDir = "./tests/neg/" @@ -102,7 +99,6 @@ class tests extends CompilerTest { @Test def neg_variances = compileFile(negDir, "variances", xerrors = 2) @Test def neg_badAuxConstr = compileFile(negDir, "badAuxConstr", xerrors = 2) @Test def neg_typetest = compileFile(negDir, "typetest", xerrors = 1) - @Test def dotc = compileDir(dotcDir + "tools/dotc", twice)(allowDeepSubtypes) @Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", twice) @Test def dotc_config = compileDir(dotcDir + "tools/dotc/config", twice) @@ -115,7 +111,7 @@ class tests extends CompilerTest { @Test def dotc_typer = compileDir(dotcDir + "tools/dotc/typer", twice) @Test def dotc_util = compileDir(dotcDir + "tools/dotc/util", twice) @Test def tools_io = compileDir(dotcDir + "tools/io", twice) - @Test def tools = compileDir(dotcDir + "tools", "-deep" :: twice)(allowDeepSubtypes) + //@Test def tools = compileDir(dotcDir + "tools", "-deep" :: Nil)(allowDeepSubtypes) @Test def testNonCyclic = compileArgs(Array( dotcDir + "tools/dotc/CompilationUnit.scala", From 2f57af3e21d6686507a7565b6be372a7ce0f5aa5 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 13 Oct 2014 11:09:07 +0200 Subject: [PATCH 59/70] Patmat: fix negation left from isEmpty&fix scoping Should fix #188 --- src/dotty/tools/dotc/transform/PatternMatcher.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9087ba0c4bc0..4b9b325c18ba 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -246,16 +246,14 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans case ((mkCase, sym), nextCase) => val body = mkCase(new OptimizedCasegen(nextCase)).ensureConforms(restpe) - val caseBody = DefDef(sym, _ => Block(List(acc), body)) - - Block(List(caseBody),ref(sym).ensureApplied) + DefDef(sym, _ => Block(List(acc), body)) }} // scrutSym == NoSymbol when generating an alternatives matcher // val scrutDef = scrutSym.fold(List[Tree]())(ValDef(_, scrut) :: Nil) // for alternatives - caseDefs + Block(List(caseDefs), ref(caseSyms.head).ensureApplied) } class OptimizedCasegen(nextCase: Tree) extends CommonCodegen with Casegen { @@ -283,7 +281,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans List(ValDef(prevSym, prev)), // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) ifThenElseZero( - ref(prevSym).select(nme.isDefined).select(defn.Boolean_!), + ref(prevSym).select(nme.isDefined), Substitution(b, prevValue)(next) ) ) From 0876deed1d725b0350b558365179252b024c2d44 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Oct 2014 12:15:04 +0200 Subject: [PATCH 60/70] Initial lambda lifter --- src/dotty/tools/dotc/Compiler.scala | 4 +- .../tools/dotc/transform/LambdaLift.scala | 397 ++++++++++++++++++ 2 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/LambdaLift.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 1aa1cce1073d..ec899ebb8716 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -53,8 +53,8 @@ class Compiler { new Literalize, new GettersSetters), List(new Erasure), - List(new CapturedVars, new Constructors)/*, - List(new LambdaLift)*/ + List(new CapturedVars, new Constructors), + List(new LambdaLift) ) var runId = 1 diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala new file mode 100644 index 000000000000..9b7595f5445d --- /dev/null +++ b/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -0,0 +1,397 @@ +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 { + val NJ = NameTransformer.NAME_JOIN_STRING +} + +class LambdaLift extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import LambdaLift._ + import ast.tpd._ + + /** the following two members override abstract members in Transform */ + val phaseName: String = "lambdalift" + + 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. + + override def treeTransformPhase = thisTransform.next + override def relaxedTyping = true + + 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. + */ + private val liftedOwner = 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 proxies(sym: Symbol): List[Symbol] = { + val pm: Map[Symbol, Symbol] = proxyMap.getOrElse(sym, Map.empty) // Dotty deviation: Type annotation needed. TODO: figure out why + free.getOrElse(sym, Nil).toList.map(pm) + } + + def narrowLiftedOwner(sym: Symbol, owner: Symbol)(implicit ctx: Context) = { + println(i"narrow lifted $sym") + if (sym.owner.skipConstructor.isTerm && + owner.isProperlyContainedIn(liftedOwner(sym))) { + changedLiftedOwner = true + liftedOwner(sym) = owner + } + } + + /** Mark symbol `sym` as being free in `enclosure`, unless `sym` + * is defined in `enclosure` or there is a class between `enclosure`s owner + * and the owner of `sym`. + * Return `true` if there is no class between `enclosure` and + * the owner of sym. + * 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): Boolean = { + println(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") + (enclosure == sym.enclosure) || { + ctx.debuglog(i"$enclosure != ${sym.enclosure}") + narrowLiftedOwner(enclosure, sym.enclosingClass) + if (enclosure.is(PackageClass) || + !markFree(sym, enclosure.skipConstructor.enclosure)) false + else { + val ss = symSet(free, enclosure) + if (!ss(sym)) { + ss += sym + changedFreeVars = true + ctx.debuglog(i"$sym is free in $enclosure") + } + !enclosure.isClass + } + } + } + + private def markCalled(callee: Symbol, caller: Symbol)(implicit ctx: Context): Unit = { + ctx.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller") + assert(callee.skipConstructor.owner.isTerm) + symSet(called, caller) += callee + if (callee.enclosingClass != caller.enclosingClass) calledFromInner += callee + } + + private class CollectDependencies(implicit ctx: Context) extends EnclosingMethodTraverser { + def traverse(enclMeth: Symbol, tree: Tree) = try { //debug + val enclosure = enclMeth.skipConstructor + val sym = tree.symbol + tree match { + case tree: Ident => + if (sym.maybeOwner.isTerm) + if (sym is (Method, butNot = Label)) markCalled(sym, enclosure) + else if (sym.isTerm) markFree(sym, enclosure) + case tree: Select => + if (sym.isConstructor && sym.owner.owner.isTerm) + markCalled(sym, enclosure) + case tree: This => + val thisClass = tree.symbol.asClass + val enclClass = enclosure.enclosingClass + if (!thisClass.isStaticOwner && thisClass != enclClass) + narrowLiftedOwner(enclosure, + if (enclClass.isContainedIn(thisClass)) thisClass + else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner + case tree: DefDef => + if (sym.owner.isTerm && !sym.is(Label)) liftedOwner(sym) = sym.topLevelClass.owner + else if (sym.isPrimaryConstructor && sym.owner.owner.isTerm) symSet(called, sym) += sym.owner + case tree: TypeDef => + if (sym.owner.isTerm) liftedOwner(sym) = sym.topLevelClass.owner + case tree: Template => + liftedDefs(enclosure) = 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 + // println(s"called = ${called.toList map { case (from, to) => from.showLocated + " -> " + to.toList.map(_.showLocated) }}") + 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) + } narrowLiftedOwner(caller, liftedOwner(callee.skipConstructor)) + } while (changedLiftedOwner) + + private def newName(sym: Symbol)(implicit ctx: Context): Name = { + def freshen(prefix: String): Name = { + val fname = ctx.freshName(prefix) + if (sym.isType) fname.toTypeName else fname.toTermName + } + if (sym.isAnonymousFunction && sym.owner.is(Method, butNot = Label)) + freshen(sym.name.toString ++ NJ ++ sym.owner.name ++ NJ) + else if (sym is ModuleClass) + freshen(sym.sourceModule.name.toString ++ NJ).moduleClassName + else + freshen(sym.name.toString ++ NJ) + } + + 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.skipConstructor) + MethodType( + pnames ++ ps.map(_.name.asTermName), + ptypes ++ ps.map(_.info), + mt.resultType) + case info => info + } + + private def liftLocals()(implicit ctx: Context): Unit = { + for ((local, lOwner) <- liftedOwner) { + val (newOwner, maybeStatic) = + if (lOwner is Package) (local.topLevelClass, Static) + else (lOwner, EmptyFlags) + val maybeNotJavaPrivate = if (calledFromInner(local)) NotJavaPrivate else EmptyFlags + local.copySymDenotation( + owner = newOwner, + name = newName(local), + initFlags = local.flags | Private | maybeStatic | maybeNotJavaPrivate, + info = liftedInfo(local)).installAfter(thisTransform) + if (local.isClass) + for (member <- local.asClass.decls) + if (member.isConstructor) { + val linfo = liftedInfo(member) + if (linfo ne member.info) + member.copySymDenotation(info = linfo).installAfter(thisTransform) + } + } + } + + override def init(implicit ctx: Context, info: TransformerInfo) = { + assert(ctx.phase == thisTransform) + (new CollectDependencies).traverse(NoSymbol, ctx.compilationUnit.tpdTree) + computeFreeVars() + computeLiftedOwners() + generateProxies()(ctx.withPhase(thisTransform.next)) + liftLocals()(ctx.withPhase(thisTransform.next)) + } + + private def currentEnclosure(implicit ctx: Context) = + ctx.owner.enclosingMethod.skipConstructor + + private def inCurrentOwner(sym: Symbol)(implicit ctx: Context) = + sym.enclosure == currentEnclosure + + private def proxy(sym: Symbol)(implicit ctx: Context): Symbol = { + def searchIn(enclosure: Symbol): Symbol = { + if (!enclosure.exists) + throw new IllegalArgumentException(i"Could not find proxy for ${sym.showDcl} in ${sym.ownersIterator.toList}, currentOwner= $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(enclosure.enclosure) + } + if (inCurrentOwner(sym)) sym else searchIn(currentEnclosure) + } + + private def memberRef(sym: Symbol)(implicit ctx: Context, info: TransformerInfo): Tree = { + val clazz = sym.owner + val qual = if (clazz.isStaticOwner) singleton(clazz.thisType) else outer.path(clazz) + transformFollowingDeep(qual.select(sym)) + } + + private def proxyRef(sym: Symbol)(implicit ctx: Context, info: TransformerInfo): Tree = { + val psym = proxy(sym) + 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) => args ++ fvs.toList.map(proxyRef(_)) + case _ => args + } + + private def addFreeParams(tree: Tree, proxies: List[Symbol])(implicit ctx: Context, info: TransformerInfo): Tree = proxies match { + case Nil => tree + case proxies => + val ownProxies = + if (!tree.symbol.isConstructor) proxies + else proxies.map(_.copy(owner = tree.symbol, flags = Synthetic | Param)) + val freeParamDefs = ownProxies.map(proxy => + transformFollowingDeep(ValDef(proxy.asTerm).withPos(tree.pos)).asInstanceOf[ValDef]) + tree match { + case tree: DefDef => + cpy.DefDef(tree)(vparamss = tree.vparamss.map(_ ++ freeParamDefs)) + case tree: Template => + cpy.Template(tree)(body = tree.body ++ freeParamDefs) + } + } + + private def liftDef(tree: MemberDef)(implicit ctx: Context): Tree = { + liftedDefs(tree.symbol.owner) += rename(tree, tree.symbol.name) + 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 TermRef(NoPrefix, _) if sym.enclosure != currentEnclosure && !sym.isStatic => + (if (sym is Method) memberRef(sym) else proxyRef(sym)).withPos(tree.pos) + case _ => + tree + } + } + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + cpy.Apply(tree)(tree.fun, addFreeArgs(tree.symbol.skipConstructor, 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 proxyHolder = sym.skipConstructor + if (needsLifting(proxyHolder)) { + val paramsAdded = addFreeParams(tree, proxies(proxyHolder)).asInstanceOf[DefDef] + if (sym.isConstructor) paramsAdded else liftDef(paramsAdded) + } + else tree + } + + 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 + } + + +/* done in lazyvals? + case Block(stats, expr0) => + val (lzyVals, rest) = stats partition { + case stat: ValDef => stat.symbol.isLazy || stat.symbol.isModuleVar + case _ => false + } + if (lzyVals.isEmpty) tree + else treeCopy.Block(tree, lzyVals ::: rest, expr0) + +*/ From a8124506dc4aee451cadde7f07d1e0a1ebdab108 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 13 Oct 2014 12:05:41 +0200 Subject: [PATCH 61/70] More refined printing options When using RefinedPrinter we now have a choice whether when printing a definition tree such as def foo(x: T): U we print the parameter and result type info found in the tree or in the symbol. Previously, we printed the sym info when after typer and the tree info before. This turns out to be too inflexble. With the patch, we print the sym info if option -Yprint-syms is set, and the tree info otherwise. Also, align -Yno-deep-subtypes from camelCase to standard hyphenated option notation. Tweak where unique ids are printed. --- .../tools/dotc/config/ScalaSettings.scala | 5 ++-- .../tools/dotc/printing/RefinedPrinter.scala | 26 +++++++++++-------- test/dotc/tests.scala | 4 +-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index f23fb620106b..743925e40753 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -145,8 +145,9 @@ class ScalaSettings extends Settings.SettingGroup { val noSelfCheck = BooleanSetting("-Yno-self-type-checks", "Suppress check for self-type conformance among inherited members.") val YshowSuppressedErrors = BooleanSetting("-Yshow-suppressed-errors", "Also show follow-on errors and warnings that are normally supressed.") val Yheartbeat = BooleanSetting("-Yheartbeat", "show heartbeat stack trace of compiler operations.") - val Yprintpos = BooleanSetting("-Yprintpos", "show tree positions") - val YnoDeepSubtypes = BooleanSetting("-YnoDeepSubtypes", "throw an exception on deep subtyping call stacks") + val Yprintpos = BooleanSetting("-Yprintpos", "show tree positions.") + val YnoDeepSubtypes = BooleanSetting("-Yno-deep-subtypes", "throw an exception on deep subtyping call stacks.") + val YprintSyms = BooleanSetting("-Yprint-syms", "when printing trees print info in symbols instead of corresponding info in trees.") def stop = YstopAfter /** Area-specific debug output. diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6076ca78754f..d9e248e405a2 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -180,7 +180,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - def useSymbol = ctx.isAfterTyper(ctx.phase) && tree.hasType && tree.symbol.exists + def useSymbol = + tree.hasType && tree.symbol.exists && ctx.settings.YprintSyms.value def modText(mods: untpd.Modifiers, kw: String): Text = { // DD val suppressKw = if (ownerIsClass) mods is ParamAndLocal else mods is Param @@ -209,6 +210,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (ctx.settings.uniqid.value && tree.hasType && tree.symbol.exists) s"#${tree.symbol.id}" else "" } + def nameIdText(tree: untpd.NameTree): Text = + toText(tree.name) ~ idText(tree) + import untpd._ var txt: Text = tree match { @@ -219,8 +223,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tp: NamedType if name != nme.WILDCARD => toTextPrefix(tp.prefix) ~ selectionString(tp) case _ => toText(name) } - case Select(qual, name) => - toTextLocal(qual) ~ ("." ~ toText(name) provided name != nme.CONSTRUCTOR) + case tree @ Select(qual, name) => + toTextLocal(qual) ~ ("." ~ nameIdText(tree) provided name != nme.CONSTRUCTOR) case This(name) => optDotPrefix(name) ~ "this" ~ idText(tree) case Super(This(name), mix) => @@ -297,15 +301,15 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toTextLocal(extractor) ~ "(" ~ toTextGlobal(patterns, ", ") ~ ")" ~ ("(" ~ toTextGlobal(implicits, ", ") ~ ")" provided implicits.nonEmpty) - case ValDef(mods, name, tpt, rhs) => + case tree @ ValDef(mods, name, tpt, rhs) => dclTextOr { - modText(mods, if (mods is Mutable) "var" else "val") ~~ toText(name) ~ + modText(mods, if (mods is Mutable) "var" else "val") ~~ nameIdText(tree) ~ optAscription(tpt) } ~ optText(rhs)(" = " ~ _) - case DefDef(mods, name, tparams, vparamss, tpt, rhs) => + case tree @ DefDef(mods, name, tparams, vparamss, tpt, rhs) => atOwner(tree) { dclTextOr { - val first = modText(mods, "def") ~~ toText(name) ~ tparamsText(tparams) + val first = modText(mods, "def") ~~ nameIdText(tree) ~ tparamsText(tparams) addVparamssText(first, vparamss) ~ optAscription(tpt) } ~ optText(rhs)(" = " ~ _) } @@ -314,11 +318,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def typeDefText(rhsText: Text) = dclTextOr { val rhsText1 = if (tree.hasType) toText(tree.symbol.info) else rhsText - modText(mods, "type") ~~ toText(name) ~ tparamsText(tree.tparams) ~ rhsText1 + modText(mods, "type") ~~ nameIdText(tree) ~ tparamsText(tree.tparams) ~ rhsText1 } rhs match { case impl: Template => - modText(mods, if (mods is Trait) "trait" else "class") ~~ toText(name) ~~ idText(tree) ~ toText(impl) ~ + modText(mods, if (mods is Trait) "trait" else "class") ~~ nameIdText(tree) ~ toText(impl) ~ (if (tree.hasType && ctx.settings.verbose.value) s"[decls = ${tree.symbol.info.decls}]" else "") case rhs: TypeBoundsTree => typeDefText(toText(rhs)) @@ -367,9 +371,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "" case TypedSplice(t) => toText(t) - case ModuleDef(mods, name, impl) => + case tree @ ModuleDef(mods, name, impl) => atOwner(tree) { - modText(mods, "object") ~~ toText(name) ~ toText(impl) + modText(mods, "object") ~~ nameIdText(tree) ~ toText(impl) } case SymbolLit(str) => "'" + str diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index dccd96eed215..614dc95271db 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,13 +14,13 @@ class tests extends CompilerTest { "-pagewidth", "160") implicit val defaultOptions = noCheckOptions ++ List( - "-YnoDeepSubtypes", + "-Yno-deep-subtypes", "-Ycheck:patternMatcher,gettersSetters,constructors" ) val twice = List("#runs", "2", "-YnoDoubleBindings") val doErase = List("-Ystop-before:terminal") - val allowDeepSubtypes = defaultOptions diff List("-YnoDeepSubtypes") + val allowDeepSubtypes = defaultOptions diff List("-Yno-deep-subtypes") val posDir = "./tests/pos/" val negDir = "./tests/neg/" From 5ee2b7edc888dc8d71f2257f51da65cfccd35a41 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 13 Oct 2014 13:02:32 +0200 Subject: [PATCH 62/70] Remove special treatment of SubstOnlyTreeMakers. In scalac SubstOnlyTreeMakers were implemented using substitution, and didn't actually introduce new trees. Thus there was an optimization to remove them while generating code. This optimization led to #190. It is now removed. --- .../tools/dotc/transform/PatternMatcher.scala | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 4b9b325c18ba..5a7d7c8a6fbb 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -394,6 +394,25 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans override def toString = "B"+((body, matchPt)) } + /** + * In scalac for such block + * x match { + * case d => + * } + * + * d inside 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 . + * 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 @@ -851,29 +870,25 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def removeSubstOnly(makers: List[TreeMaker]) = makers filterNot (_.isInstanceOf[SubstOnlyTreeMaker]) // a foldLeft to accumulate the localSubstitution left-to-right - // it drops SubstOnly tree makers, since their only goal in life is to propagate substitutions to the next tree maker, which is fullfilled by propagateSubstitution + // unlike in scalace it does not drop SubstOnly tree makers, + // as there could types having them as prefix def propagateSubstitution(treeMakers: List[TreeMaker], initial: Substitution): List[TreeMaker] = { var accumSubst: Substitution = initial treeMakers foreach { maker => maker incorporateOuterSubstitution accumSubst accumSubst = maker.substitution } - removeSubstOnly(treeMakers) + 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 = { - // drops SubstOnlyTreeMakers, since their effect is now contained in the TreeMakers that follow them - val casesNoSubstOnly = casesRaw map (propagateSubstitution(_, EmptySubstitution)) - combineCasesNoSubstOnly(scrut, scrutSym, casesNoSubstOnly, pt, owner, matchFailGenOverride) - } + // unlike in scalac SubstOnlyTreeMakers are maintained. + val casesSubstitutionPropagated = casesRaw map (propagateSubstitution(_, EmptySubstitution)) - // pt is the fully defined type of the cases (either pt or the lub of the types of the cases) - def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Symbol => Tree]): Tree = - /*fixerUpper(owner, scrut.pos)*/ { - def matchFailGen = matchFailGenOverride orElse Some((arg: Symbol) => Throw(New(defn.MatchErrorType, List(ref(arg))))) + def matchFailGen = matchFailGenOverride orElse Some((arg: Symbol) => Throw(New(defn.MatchErrorType, List(ref(arg))))) - ctx.debuglog("combining cases: "+ (casesNoSubstOnly.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) + ctx.debuglog("combining cases: "+ (casesSubstitutionPropagated.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) val (suppression, requireSwitch): (Suppression, Boolean) = /*if (settings.XnoPatmatAnalysis)*/ (Suppression.NoSuppression, false) @@ -892,10 +907,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans (Suppression.NoSuppression, false) }*/ - emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ + emitSwitch(scrut, scrutSym, casesSubstitutionPropagated, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ if (requireSwitch) ctx.warning("could not emit switch for @switch annotated match", scrut.pos) - if (casesNoSubstOnly nonEmpty) { + if (casesSubstitutionPropagated 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 @@ -903,15 +918,15 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // ("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 (casesNoSubstOnly.nonEmpty && { - val nonTrivLast = casesNoSubstOnly.last + if (casesSubstitutionPropagated.nonEmpty && { + val nonTrivLast = casesSubstitutionPropagated.last nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] }) None else matchFailGen - analyzeCases(scrutSym, casesNoSubstOnly, pt, suppression) + analyzeCases(scrutSym, casesSubstitutionPropagated, pt, suppression) - val (cases, toHoist) = optimizeCases(scrutSym, casesNoSubstOnly, pt) + val (cases, toHoist) = optimizeCases(scrutSym, casesSubstitutionPropagated, pt) val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases.map(x => combineExtractors(x) _), synthCatchAll) From 8ae2853e5a72363bb56b9cc69643e96f2ca4c1a3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 13 Oct 2014 12:07:30 +0200 Subject: [PATCH 63/70] Disambiguate names in test case --- tests/pos/functions1.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/pos/functions1.scala b/tests/pos/functions1.scala index 93f4fc7b6d64..04b90d80e442 100644 --- a/tests/pos/functions1.scala +++ b/tests/pos/functions1.scala @@ -12,7 +12,7 @@ object Functions { val x2: String => Int = x.foo val x3 = x.foo _ } - + object sams { abstract class Spore[T, U] { def run(x: T): U @@ -22,9 +22,9 @@ object Functions { def run(x: T): U } - val x: String => String = { + val x33: String => String = x22 => x22 match { case "abc" => "" - case x => x + case x34 => x34 } val y: PartialFunction[String, String] = x => x match { case "abc" => "" From ac583c50d6c26dcfc2eb7ad54f63f1de22f29ae7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 13 Oct 2014 13:57:37 +0200 Subject: [PATCH 64/70] Better diagnostic from LambdaLift --- src/dotty/tools/dotc/transform/LambdaLift.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala index 9b7595f5445d..a08df1c33ef0 100644 --- a/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -22,7 +22,8 @@ import collection.{ mutable, immutable } import collection.mutable.{ HashMap, HashSet, LinkedHashMap, LinkedHashSet, TreeSet } object LambdaLift { - val NJ = NameTransformer.NAME_JOIN_STRING + private val NJ = NameTransformer.NAME_JOIN_STRING + private class NoPath extends Exception } class LambdaLift extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => @@ -125,7 +126,8 @@ class LambdaLift extends MiniPhaseTransform with IdentityDenotTransformer { this * } * } */ - private def markFree(sym: Symbol, enclosure: Symbol)(implicit ctx: Context): Boolean = { + private def markFree(sym: Symbol, enclosure: Symbol)(implicit ctx: Context): Boolean = try { + if (!enclosure.exists) throw new NoPath println(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") (enclosure == sym.enclosure) || { ctx.debuglog(i"$enclosure != ${sym.enclosure}") @@ -143,6 +145,11 @@ class LambdaLift extends MiniPhaseTransform with IdentityDenotTransformer { this } } } + 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") From 8b7988358782a569f9e74d83e98a2239d2085d40 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 13 Oct 2014 15:05:47 +0200 Subject: [PATCH 65/70] EqualityTestTreeMaker: fix incorrect type of symbol given a pattern object b x match { case y @ b => } y inside the `y` should have type b.type. Inherited from scalac EqualityTestTreeMaker would reset type of it in order to achive this. Dotty instead needs to know this in advance. --- .../tools/dotc/transform/PatternMatcher.scala | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 5a7d7c8a6fbb..f3fca47d0223 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -287,7 +287,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans ) } else { assert(defn.isProductSubType(prev.tpe)) - Substitution(b, ref(prevSym))(next) + Block( + List(ValDef(prevSym, prev)), + Substitution(b, ref(prevSym))(next) + ) } } @@ -432,8 +435,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val cond: Tree val res: Tree - lazy val nextBinder = freshSym(pos, nextBinderTp) - lazy val localSubstitution = Substitution(List(prevBinder), List(ref(nextBinder))) + val nextBinder: Symbol + lazy val localSubstitution = + if(nextBinder ne prevBinder) Substitution(List(prevBinder), List(ref(nextBinder))) + else EmptySubstitution def chainBefore(next: Tree)(casegen: Casegen): Tree = /*atPos(pos)(*/casegen.flatMapCond(cond, res, nextBinder, substitution(next))//) @@ -728,7 +733,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val prevBinder = testedBinder - override lazy val nextBinder = afterTest.asTerm + val nextBinder = afterTest.asTerm def needsOuterTest(patType: Type, selType: Type, currentOwner: Symbol) = { // See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest` @@ -823,13 +828,14 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } // 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, patTree: Tree, override val pos: Position) extends CondTreeMaker { - val nextBinderTp = prevBinder.info.widen + 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) + val res = ref(prevBinder).ensureConforms(nextBinderTp) override def toString = "ET"+((prevBinder.name, patTree)) } @@ -1020,6 +1026,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } } + 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(_, _) => Some(tree.typeOpt) @@ -1027,11 +1040,19 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } } + 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() = step(EqualityTestTreeMaker(binder, tree, pos))() + 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()) @@ -1083,9 +1104,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans case _: UnApply | _: Apply| Typed(_: UnApply | _: Apply, _) => extractorStep() case SymbolAndTypeBound(sym, tpe) => typeTestStep(sym, tpe) case TypeBound(tpe) => typeTestStep(binder, tpe) + case SymbolAndValueBound(sym, const) => equalityTestStep(binder, sym, const) case SymbolBound(sym, expr) => bindingStep(sym, expr) case WildcardPattern() => noStep() - case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => equalityTestStep() + case ConstantPattern(const) => equalityTestStep(binder, binder, const) case Alternative(alts) => alternativesStep(alts) case _ => ctx.error(unsupportedPatternMsg, pos) ; noStep() } From 5454bca536863a7db1d0b4a848697f33c5c8bded Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 13 Oct 2014 18:54:38 +0200 Subject: [PATCH 66/70] Get rid of Substitution. Substitution was creating more problems than solving. Instead the symbols in tree aren't rewriten anymore, and pattern matcher collects Rebindings and generates ValDefs for them just before the guard & body of the generated case. --- .../tools/dotc/transform/PatternMatcher.scala | 190 +++++++----------- 1 file changed, 74 insertions(+), 116 deletions(-) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index f3fca47d0223..de2503b6a346 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -138,73 +138,28 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } - object Substitution { - def apply(from: Symbol, to: Tree) = new Substitution(List(from), List(to)) + 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 Substitution(from, to) else EmptySubstitution + if (from nonEmpty) new Rebindings(from, to) else NoRebindings } - class Substitution(val from: List[Symbol], val to: List[Tree]) { - - // We must explicitly type the trees that we replace inside some other tree, since the latter may already have been typed, - // and will thus not be retyped. This means we might end up with untyped subtrees inside bigger, typed trees. - def apply(tree: Tree): Tree = { - // according to -Ystatistics 10% of translateMatch's time is spent in this method... - // since about half of the typedSubst's end up being no-ops, the check below shaves off 5% of the time spent in typedSubst - /*if (!tree.exists { case i@Ident(_) => from contains i.symbol case _ => false}) tree - else*/ - - var replaced = 0 - val toAdapted = (from zip to) map { - case (orig, nw) => - if (nw.tpe <:< orig.info) nw else nw.ensureConforms(orig.info & nw.tpe) - } - - val identReplace: Ident => Tree = { ident => - def subst(from: List[Symbol], to: List[Tree]): Tree = - if (from.isEmpty) ident - else if (ident.symbol == from.head) { - replaced += 1 - to.head //typedIfOrigTyped(to.head.shallowDuplicate.setPos(tree.pos), tree.tpe) - } - else subst(from.tail, to.tail) - subst(from, toAdapted) - } - val start = System.currentTimeMillis() - val res = new tpd.TreeMap() { - override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match{ - case s: Ident => identReplace(s) - case _ => super.transform(tree) - } - }.transform(tree) - //new TreeTypeMap(treeMap = identReplace/*, substFrom = from, substTo = to.map(_.symbol)*/).transform(tree) - - ctx.debuglog(s"${id} TreeTM replacing ${from.size} elems took ${(System.currentTimeMillis() - start)/1000.0} replaced:$replaced") - def treeSize = new ShallowFolder[Int]((a,b) => a + 1).apply(0, tree) - - ctx.debuglog(s"location: ${ctx.owner.showFullName}, size: ${treeSize}") - res - } - - - // the substitution that chains `other` before `this` substitution - // forall t: Tree. this(other(t)) == (this >> other)(t) - def >>(other: Substitution): Substitution = { - if (other == EmptySubstitution) this - else if (this == EmptySubstitution) other + 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 { - val (fromFiltered, toFiltered) = (from, to).zipped filter { (f, t) => !other.from.contains(f)} - new Substitution(other.from ++ fromFiltered, other.to.map(apply) ++ toFiltered) // a quick benchmarking run indicates the `.map(apply)` is not too costly + 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) } } - override def toString = (from.map(_.name) zip to) mkString("Substitution(", ", ", ")") - } - object EmptySubstitution extends Substitution(Nil, Nil) { - override def apply(tree: Tree): Tree = tree - override def >>(other: Substitution): Substitution = other + 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 { @@ -271,25 +226,27 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // next: MatchMonad[U] // returns MatchMonad[U] def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { - val prevSym = freshSym(prev.pos, prev.tpe, "o") + val getTp = extractorMemberType(prev.tpe, nme.get) val isDefined = extractorMemberType(prev.tpe, nme.isDefined) if ((isDefined isRef defn.BooleanClass) && getTp.exists) { - val prevValue = ref(prevSym).select("get".toTermName).ensureApplied + val tmpSym = freshSym(prev.pos, prev.tpe, "o") + val prevValue = ref(tmpSym).select("get".toTermName).ensureApplied + Block( - List(ValDef(prevSym, prev)), + 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(prevSym).select(nme.isDefined), - Substitution(b, prevValue)(next) + ref(tmpSym).select(nme.isDefined), + Block(List(ValDef(b.asTerm, prevValue)), next) ) ) } else { assert(defn.isProductSubType(prev.tpe)) Block( - List(ValDef(prevSym, prev)), - Substitution(b, ref(prevSym))(next) + List(ValDef(b.asTerm, prev)), + next //Substitution(b, ref(prevSym))(next) ) } } @@ -342,23 +299,23 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans abstract class TreeMaker { def pos: Position - private[this] var currSub: Substitution = null + 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 substitution: Substitution = - if (currSub eq null) localSubstitution + def rebindings: Rebindings = + if (currSub eq null) introducedRebindings else currSub - protected def localSubstitution: Substitution + protected def introducedRebindings: Rebindings - private[TreeMakers] def incorporateOuterSubstitution(outerSubst: Substitution): Unit = { + private[TreeMakers] def incorporateOuterRebinding(outerSubst: Rebindings): Unit = { if (currSub ne null) { - ctx.debuglog("BUG: incorporateOuterSubstitution called more than once for "+ ((this, currSub, outerSubst))) + ctx.debuglog("BUG: incorporateOuterRebinding called more than once for "+ ((this, currSub, outerSubst))) Thread.dumpStack() } - else currSub = outerSubst >> substitution + else currSub = outerSubst >> rebindings } /** The substitution that specifies the trees that compute the values of the subpattern binders. @@ -373,14 +330,14 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans * 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 subPatternsAsSubstitution: Substitution = substitution + 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 localSubstitution: Substitution = EmptySubstitution + protected val introducedRebindings: Rebindings = NoRebindings } case class TrivialTreeMaker(tree: Tree) extends TreeMaker with NoNewBinders { @@ -393,7 +350,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def pos = body.pos def chainBefore(next: Tree)(casegen: Casegen): Tree = // assert(next eq EmptyTree) - /*atPos(body.pos)*/(casegen.one(substitution(body))) // since SubstOnly treemakers are dropped, need to do it here + /*atPos(body.pos)*/(casegen.one(body)) // since SubstOnly treemakers are dropped, need to do it here override def toString = "B"+((body, matchPt)) } @@ -419,8 +376,8 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker { val pos = Positions.NoPosition - val localSubstitution = EmptySubstitution - def chainBefore(next: Tree)(casegen: Casegen): Tree = Block(List(ValDef(prevBinder.asTerm, ref(nextBinder))), next) + val introducedRebindings = Rebindings(prevBinder, nextBinder) + def chainBefore(next: Tree)(casegen: Casegen): Tree = next //override def toString = "S" + localSubstitution } @@ -436,12 +393,12 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val res: Tree val nextBinder: Symbol - lazy val localSubstitution = - if(nextBinder ne prevBinder) Substitution(List(prevBinder), List(ref(nextBinder))) - else EmptySubstitution + lazy val introducedRebindings = + if(nextBinder ne prevBinder) Rebindings(prevBinder, nextBinder) + else NoRebindings def chainBefore(next: Tree)(casegen: Casegen): Tree = - /*atPos(pos)(*/casegen.flatMapCond(cond, res, nextBinder, substitution(next))//) + /*atPos(pos)(*/casegen.flatMapCond(cond, res, nextBinder, next)//) } // unless we're optimizing, emit local variable bindings for all subpatterns of extractor/case class patterns @@ -469,10 +426,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans private lazy val (stored, substed) = (subPatBinders, subPatRefs).zipped.partition{ case (sym, _) => storedBinders(sym) } - protected lazy val localSubstitution: Substitution = if (!emitVars) Substitution(subPatBinders, subPatRefs) + protected lazy val introducedRebindings: Rebindings = if (!emitVars) Rebindings(subPatBinders, subPatRefs) else { val (subPatBindersSubstituted, subPatRefsSubstituted) = substed.unzip - Substitution(subPatBindersSubstituted.toList, subPatRefsSubstituted.toList) + Rebindings(subPatBindersSubstituted.toList, subPatRefsSubstituted.toList) } /** The substitution that specifies the trees that compute the values of the subpattern binders. @@ -480,8 +437,8 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans * 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 subPatternsAsSubstitution = - Substitution(subPatBinders, subPatRefs) >> super.subPatternsAsSubstitution + override def subPatternsAsRebindings = + Rebindings(subPatBinders, subPatRefs) >> super.subPatternsAsRebindings def bindSubPats(in: Tree): Tree = if (!emitVars) in @@ -549,13 +506,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def chainBefore(next: Tree)(casegen: Casegen): Tree = { val condAndNext = extraCond match { case Some(cond: Tree) => - casegen.ifThenElseZero(substitution(cond), bindSubPats(substitution(next))) + casegen.ifThenElseZero(cond, bindSubPats(next)) case _ => - bindSubPats(substitution(next)) + bindSubPats(next) } if (extractorReturnsBoolean) casegen.flatMapCond(extractor, unitLiteral, nextBinder, condAndNext) - else casegen.flatMap(extractor, nextBinder, condAndNext) + else casegen.flatMap(extractor, nextBinder, condAndNext) // getType? } @@ -605,13 +562,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans cond match { case Some(cond: Tree) => - casegen.ifThenElseZero(cond, bindSubPats(substitution(next))) + casegen.ifThenElseZero(cond, bindSubPats(next)) case _ => - bindSubPats(substitution(next)) + bindSubPats(next) } } - override def toString = "P"+((prevBinder.name, extraCond getOrElse "", localSubstitution)) + override def toString = "P"+((prevBinder.name, extraCond getOrElse "", introducedRebindings)) } object IrrefutableExtractorTreeMaker { @@ -747,7 +704,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } } - override lazy val localSubstitution: Substitution = EmptySubstitution + override lazy val introducedRebindings = NoRebindings def outerTestNeeded = { val np = expectedTp.normalizedPrefix @@ -842,9 +799,9 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans 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 incorporateOuterSubstitution(outerSubst: Substitution): Unit = { - super.incorporateOuterSubstitution(outerSubst) - altss = altss map (alts => propagateSubstitution(alts, substitution)) + 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 = { @@ -856,7 +813,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans ) val findAltMatcher = codegenAlt.matcher(EmptyTree, NoSymbol, defn.BooleanType)(combinedAlts, Some((x: Symbol) => Literal(Constant(false)))) - codegenAlt.ifThenElseZero(findAltMatcher, substitution(next)) + codegenAlt.ifThenElseZero(findAltMatcher, next) } } } @@ -864,25 +821,26 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans case class GuardTreeMaker(guardTree: Tree) extends TreeMaker with NoNewBinders { val pos = guardTree.pos - def chainBefore(next: Tree)(casegen: Casegen): Tree = casegen.flatMapGuard(substitution(guardTree), next) + 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 = - treeMakers.foldRight(EmptyTree: Tree)((a, b) => a.chainBefore(b)(casegen)) - - def removeSubstOnly(makers: List[TreeMaker]) = makers filterNot (_.isInstanceOf[SubstOnlyTreeMaker]) - + 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 types having them as prefix - def propagateSubstitution(treeMakers: List[TreeMaker], initial: Substitution): List[TreeMaker] = { - var accumSubst: Substitution = initial + // 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 incorporateOuterSubstitution accumSubst - accumSubst = maker.substitution + maker incorporateOuterRebinding accumSubst + accumSubst = maker.rebindings } treeMakers } @@ -890,11 +848,11 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // 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 casesSubstitutionPropagated = casesRaw map (propagateSubstitution(_, EmptySubstitution)) + val casesRebindingPropagated = casesRaw map (propagateRebindings(_, NoRebindings)) def matchFailGen = matchFailGenOverride orElse Some((arg: Symbol) => Throw(New(defn.MatchErrorType, List(ref(arg))))) - ctx.debuglog("combining cases: "+ (casesSubstitutionPropagated.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) + ctx.debuglog("combining cases: "+ (casesRebindingPropagated.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) val (suppression, requireSwitch): (Suppression, Boolean) = /*if (settings.XnoPatmatAnalysis)*/ (Suppression.NoSuppression, false) @@ -913,10 +871,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans (Suppression.NoSuppression, false) }*/ - emitSwitch(scrut, scrutSym, casesSubstitutionPropagated, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ + emitSwitch(scrut, scrutSym, casesRebindingPropagated, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ if (requireSwitch) ctx.warning("could not emit switch for @switch annotated match", scrut.pos) - if (casesSubstitutionPropagated nonEmpty) { + 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 @@ -924,15 +882,16 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // ("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 (casesSubstitutionPropagated.nonEmpty && { - val nonTrivLast = casesSubstitutionPropagated.last + if (casesRebindingPropagated.nonEmpty && { + val nonTrivLast = casesRebindingPropagated.last nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] }) None else matchFailGen - analyzeCases(scrutSym, casesSubstitutionPropagated, pt, suppression) + analyzeCases(scrutSym, casesRebindingPropagated, pt, suppression) + + val (cases, toHoist) = optimizeCases(scrutSym, casesRebindingPropagated, pt) - val (cases, toHoist) = optimizeCases(scrutSym, casesSubstitutionPropagated, pt) val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases.map(x => combineExtractors(x) _), synthCatchAll) @@ -1104,7 +1063,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans case _: UnApply | _: Apply| Typed(_: UnApply | _: Apply, _) => extractorStep() case SymbolAndTypeBound(sym, tpe) => typeTestStep(sym, tpe) case TypeBound(tpe) => typeTestStep(binder, tpe) - case SymbolAndValueBound(sym, const) => equalityTestStep(binder, sym, const) case SymbolBound(sym, expr) => bindingStep(sym, expr) case WildcardPattern() => noStep() case ConstantPattern(const) => equalityTestStep(binder, binder, const) From 49cfd9fe87c86cd7fff0952fe379eb1e7acca611 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 13 Oct 2014 19:04:12 +0200 Subject: [PATCH 67/70] Quickfix patmat not handling Bind(_, Typed(_: Unapply)) Typer creates a tree that given current decoding scheme means case a @ (Assign(Ident(id), rhs) : tpt) Which isn't valid scala. --- src/dotty/tools/dotc/transform/PatternMatcher.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index de2503b6a346..ac92bb80c009 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -979,6 +979,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans 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 From 06d278bcbf6237c884bde0691536d28fbdb2e593 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Tue, 14 Oct 2014 13:18:06 +0200 Subject: [PATCH 68/70] Test for patmat action on a @ Bind(_, Typed(_: Unapply, _) --- tests/pos/Patterns.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/pos/Patterns.scala b/tests/pos/Patterns.scala index 228345b9877d..54c4d8ab27b9 100644 --- a/tests/pos/Patterns.scala +++ b/tests/pos/Patterns.scala @@ -1,3 +1,4 @@ +import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.core.Types._ object Patterns { @@ -13,6 +14,8 @@ object Patterns { d match { case WildcardType(bounds: TypeBounds) => bounds.variance + case a @ Assign(Ident(id), rhs) => id + case a: Object => a } ('1', "1") match { From 6eeb8c61a9b9aff6119977b09adebcfcc622c041 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 17 Oct 2014 12:18:01 +0200 Subject: [PATCH 69/70] Erasure casting should perform primitive conversions After erasure, asInstanceworks only bedtween reference types, so erasure is not allowed to insert asInstanceOfs between numeric types. Previously this made t1260 fail even if LambdaLift is disabled. --- src/dotty/tools/dotc/transform/Erasure.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 0e1b389ff663..ed853f8f1c2c 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -195,8 +195,8 @@ object Erasure extends TypeTestsCasts{ // See SI-2386 for one example of when this might be necessary. cast(ref(defn.runtimeMethod(nme.toObjectArray)).appliedTo(tree), pt) case _ => - ctx.log(s"casting from ${tree.showSummary}: ${tree.tpe.show} to ${pt.show}") - tree.asInstance(pt) + if (pt.isPrimitiveValueType) primitiveConversion(tree, pt.classSymbol) + else tree.asInstance(pt) } } From f27010fbfb3b56d1b62e3549b8bb64d6f4dce2cc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 17 Oct 2014 12:18:19 +0200 Subject: [PATCH 70/70] Disable LambdaLift to get a working baseline build --- src/dotty/tools/dotc/Compiler.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index ec899ebb8716..1aa1cce1073d 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -53,8 +53,8 @@ class Compiler { new Literalize, new GettersSetters), List(new Erasure), - List(new CapturedVars, new Constructors), - List(new LambdaLift) + List(new CapturedVars, new Constructors)/*, + List(new LambdaLift)*/ ) var runId = 1