diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 258cab36889a..1aa1cce1073d 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: @@ -57,13 +46,15 @@ class Compiler { new TailRec), List(new PatternMatcher, new ExplicitOuter, - new LazyValsTransform, + // new LazyValTranformContext().transformer, // disabled, awaiting fixes new Splitter), List(new ElimByName, new InterceptedMethods, - new Literalize), + new Literalize, + new GettersSetters), List(new Erasure), - List(new CapturedVars) + List(new CapturedVars, new Constructors)/*, + List(new LambdaLift)*/ ) var runId = 1 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/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index 1212c9e4dac8..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) => @@ -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) = !( @@ -147,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) @@ -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(NoPrefix, tp.symbol.asTerm) + this(tp.widen) case ThisType(_) | SuperType(_, _) => tp case ExprType(rt) => @@ -269,7 +289,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, erasedRef(tp.selfType)) // can't replace selftype by NoType because this would lose the sourceModule link } case NoType | NoPrefix | ErrorType | JavaArrayType(_) => @@ -278,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)) @@ -327,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) @@ -337,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/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 ? 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/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index b63f0ad8cc03..f1ccfdb75e3e 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[_]] @@ -820,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 } @@ -1344,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,6 +1354,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: 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) + } + }.asInstanceOf[tree.ThisTree[T]] } } // ----- Helper functions and classes --------------------------------------- diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 3fa08b9c6a6f..9b7c9cbae810 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._ @@ -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) @@ -358,7 +355,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,15 +561,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 appliedIfMethod(implicit ctx: Context): Tree = { - tree.tpe.widen match { - case fntpe: MethodType => appliedToArgs(Nil) - case _ => tree - } - } - + def appliedToNone(implicit ctx: Context): Apply = appliedToArgs(Nil) def appliedToType(targ: Type)(implicit ctx: Context): Tree = appliedToTypes(targ :: Nil) @@ -584,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) @@ -601,6 +592,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. @@ -627,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 { @@ -655,6 +659,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/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 9ac01df9cc93..5a6c9fa89012 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 ------------------- @@ -210,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) @@ -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/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/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/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/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 9970c5948274..82fd60fa0cef 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) = { @@ -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/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index e34483f8f75f..a27dd6614fac 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 = @@ -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 @@ -526,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) @@ -543,12 +546,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/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 5eb8cd920b18..7b589fec1e0e 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 explicitOuterPhase = explicitOuterCache.phase + def gettersSettersPhase = gettersSettersCache.phase def isAfterTyper(phase: Phase): Boolean = phase.id > typerPhase.id } @@ -200,6 +202,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/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) diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 4f2fe9dba3b9..f7354a8b423d 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." @@ -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/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 073c9112d3d6..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 @@ -200,7 +196,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) @@ -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. */ @@ -372,6 +385,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 @@ -428,7 +444,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 @@ -609,7 +625,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 @@ -916,7 +932,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 @@ -1314,6 +1330,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: TypeVar => tp.inst.exists && inCache(tp.inst) + case tp: TypeProxy => inCache(tp.underlying) + case tp: AndOrType => inCache(tp.tp1) && inCache(tp.tp2) + case _ => true + } + def computeBaseTypeRefOf(tp: Type): Type = { Stats.record("computeBaseTypeOf") if (symbol.isStatic && tp.derivesFrom(symbol)) @@ -1330,9 +1358,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) => @@ -1353,15 +1378,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) } @@ -1420,27 +1439,29 @@ 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`. - * @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 - /** The denotation of a package class. * It overrides ClassDenotation to take account of package objects when looking for members */ 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 } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 529452c733ac..d93e4eb09338 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, @@ -1169,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 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 diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d66d93a16f80..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.symbol != null && 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)) @@ -326,10 +330,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 +345,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 { @@ -366,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/src/dotty/tools/dotc/transform/CapturedVars.scala b/src/dotty/tools/dotc/transform/CapturedVars.scala index d5bd56bc365e..91b81fea5d0a 100644 --- a/src/dotty/tools/dotc/transform/CapturedVars.scala +++ b/src/dotty/tools/dotc/transform/CapturedVars.scala @@ -17,31 +17,26 @@ 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] = _ - 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() @@ -50,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)) { @@ -69,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 } @@ -91,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/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index 34c90fdc2d31..7bde1ba4ffa4 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -1,27 +1,235 @@ -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 util.Positions._ +import collection.mutable -/** This transform moves initializers from body to constructor. - * Right now it's a dummy. - * Awaiting for real implemetation +/** 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 { +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 + + /** 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 | Lazy)) && + owner.owner.isClass + if (ownerBecomesConstructor(sym.owner)) + sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor) + else sym + } + + /** @return true if after ExplicitOuter, all references from this tree go via an + * outer link, so no parameter accessors need to be rewired to parameters + */ + private def noDirectRefsFrom(tree: Tree)(implicit ctx: Context) = + tree.isDef && tree.symbol.isClass && !tree.symbol.is(InSuperCall) + + /** Class members that can be eliminated if referenced only from their own + * constructor. + */ + private def mightBeDropped(sym: Symbol)(implicit ctx: Context) = + sym.is(Private, butNot = KeeperFlags) && !sym.is(MutableParamAccessor) + + 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 + 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) + + // 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 + // 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(_, inSuperCall = toClass)) + 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) + + // 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] + + 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 { + case stat :: stats1 => + stat match { + 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(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 => + clsStats += stat + case _ => + constrStats += intoConstr(stat) + } + splitStats(stats1) + case Nil => + (Nil, Nil) + } + splitStats(tree.body) + + val accessorFields = accessors.filterNot(_ is Method) + + // The initializers for the retained accessors */ + val copyParams = accessorFields.filter(isRetained).map(acc => + assign(acc, ref(acc.subst(accessors, paramSyms))).withPos(tree.pos)) + + // Drop accessors that are not retained from class scope + 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(!dropped.contains(_)))) + } + + cpy.Template(tree)( + constr = cpy.DefDef(constr)( + rhs = Block(superCalls.toList ::: copyParams ::: constrStats.toList, unitLiteral)), + parents = parentTypeTrees, + body = clsStats.toList) } } \ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/ElimByName.scala b/src/dotty/tools/dotc/transform/ElimByName.scala index 5925ffa721ff..1d0307398fc3 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,13 +76,29 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform cpy.Apply(tree)(tree.fun, args1) } - override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = { - val origDenot = originalDenotation(tree) - if ((origDenot is Param) && (origDenot.info.isInstanceOf[ExprType])) + /** If denotation had an ExprType before, it now gets a function type */ + private def exprBecomesFunction(symd: SymDenotation)(implicit ctx: Context) = + (symd is Param) || (symd is (ParamAccessor, butNot = Method)) + + /** Map `tree` to `tree.apply()` is `ftree` was of ExprType and becomes now a function */ + private def applyIfFunction(tree: Tree, ftree: Tree)(implicit ctx: Context) = { + val origDenot = originalDenotation(ftree) + if (exprBecomesFunction(origDenot) && (origDenot.info.isInstanceOf[ExprType])) tree.select(defn.Function0_apply).appliedToNone else tree } + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + applyIfFunction(tree, tree) + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { + case TypeApply(Select(_, nme.asInstanceOf_), arg :: Nil) => + // tree might be of form e.asInstanceOf[x.type] where x becomes a function. + // See pos/t296.scala + applyIfFunction(tree, arg) + case _ => tree + } + def elimByNameParams(tp: Type)(implicit ctx: Context): Type = tp match { case tp: PolyType => tp.derivedPolyType(tp.paramNames, tp.paramBounds, elimByNameParams(tp.resultType)) @@ -98,6 +115,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 (exprBecomesFunction(sym)) transformParamInfo(tp) else elimByNameParams(tp) } diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index aae1ff1baf79..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) } } @@ -234,11 +234,14 @@ 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) - val erased = erasedType(tree)(ctx.withPhase(ctx.erasurePhase)) + val erased = erasedType(tree) ctx.log(s"promoting ${tree.show}: ${erased.showWithUnderlying()}") tree.withType(erased) } @@ -364,10 +367,9 @@ 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) + untpd.TypedSplice(TypeTree(eraseResult(ddef.tpt.typeOpt)).withPos(ddef.tpt.pos))) super.typedDefDef(ddef1, sym) } diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index aeda6a1d30e3..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])) @@ -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) diff --git a/src/dotty/tools/dotc/transform/GettersSetters.scala b/src/dotty/tools/dotc/transform/GettersSetters.scala new file mode 100644 index 000000000000..772a63e52a6c --- /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 diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala new file mode 100644 index 000000000000..a08df1c33ef0 --- /dev/null +++ b/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -0,0 +1,404 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import core.Phases._ +import ast.Trees._ +import SymUtils._ +import ExplicitOuter.outer +import util.Attachment +import util.NameTransformer +import util.Positions._ +import collection.{ mutable, immutable } +import collection.mutable.{ HashMap, HashSet, LinkedHashMap, LinkedHashSet, TreeSet } + +object LambdaLift { + private val NJ = NameTransformer.NAME_JOIN_STRING + private class NoPath extends Exception +} + +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 = 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}") + 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 + } + } + } + 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") + 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) + +*/ diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 97525e2891d2..ac92bb80c009 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 @@ -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 { @@ -237,7 +192,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) => @@ -246,16 +201,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).appliedIfMethod) + 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 { @@ -273,23 +226,28 @@ 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).appliedIfMethod + 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).select(defn.Boolean_!), - Substitution(b, prevValue)(next) + ref(tmpSym).select(nme.isDefined), + Block(List(ValDef(b.asTerm, prevValue)), next) ) ) } else { assert(defn.isProductSubType(prev.tpe)) - Substitution(b, ref(prevSym))(next) + Block( + List(ValDef(b.asTerm, prev)), + next //Substitution(b, ref(prevSym))(next) + ) } } @@ -341,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. @@ -372,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 { @@ -392,15 +350,34 @@ 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)) } + /** + * 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 - 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 } @@ -415,11 +392,13 @@ 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 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 @@ -447,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. @@ -458,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 @@ -527,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? } @@ -583,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 { @@ -711,7 +690,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` @@ -725,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 @@ -806,22 +785,23 @@ 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)) } 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 = { @@ -833,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) } } } @@ -841,41 +821,38 @@ 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 - // 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 - def propagateSubstitution(treeMakers: List[TreeMaker], initial: Substitution): List[TreeMaker] = { - var accumSubst: Substitution = initial + // unlike in scalace it does not drop SubstOnly tree makers, + // as there could be types having them as prefix + def propagateRebindings(treeMakers: List[TreeMaker], initial: Rebindings): List[TreeMaker] = { + var accumSubst: Rebindings = initial treeMakers foreach { maker => - maker incorporateOuterSubstitution accumSubst - accumSubst = maker.substitution + maker incorporateOuterRebinding accumSubst + accumSubst = maker.rebindings } - 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 casesRebindingPropagated = casesRaw map (propagateRebindings(_, NoRebindings)) - // 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: "+ (casesRebindingPropagated.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) val (suppression, requireSwitch): (Suppression, Boolean) = /*if (settings.XnoPatmatAnalysis)*/ (Suppression.NoSuppression, false) @@ -894,10 +871,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans (Suppression.NoSuppression, false) }*/ - emitSwitch(scrut, scrutSym, casesNoSubstOnly, 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 (casesNoSubstOnly 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 @@ -905,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 (casesNoSubstOnly.nonEmpty && { - val nonTrivLast = casesNoSubstOnly.last + if (casesRebindingPropagated.nonEmpty && { + val nonTrivLast = casesRebindingPropagated.last nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] }) None else matchFailGen - analyzeCases(scrutSym, casesNoSubstOnly, pt, suppression) + analyzeCases(scrutSym, casesRebindingPropagated, pt, suppression) + + val (cases, toHoist) = optimizeCases(scrutSym, casesRebindingPropagated, pt) - val (cases, toHoist) = optimizeCases(scrutSym, casesNoSubstOnly, pt) val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases.map(x => combineExtractors(x) _), synthCatchAll) @@ -945,7 +923,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 } @@ -1001,12 +979,20 @@ 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 } } + 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) @@ -1014,11 +1000,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()) @@ -1072,7 +1066,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans case TypeBound(tpe) => typeTestStep(binder, tpe) 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() } @@ -1391,7 +1385,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 +1474,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 @@ -1800,7 +1794,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 diff --git a/src/dotty/tools/dotc/transform/SymUtils.scala b/src/dotty/tools/dotc/transform/SymUtils.scala index df0ac59ae930..2875327c4712 100644 --- a/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/src/dotty/tools/dotc/transform/SymUtils.scala @@ -7,6 +7,9 @@ import Contexts._ import Symbols._ import Decorators._ import Names._ +import StdNames._ +import NameOps._ +import Flags._ import language.implicitConversions object SymUtils { @@ -17,9 +20,48 @@ 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) + } + + 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) + + 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 } 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 diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index ef5baca0729f..1b5cc7c07c1a 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 @@ -130,10 +131,13 @@ 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) = { - 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 && !pt.isInstanceOf[FunProto]) assert(tree.tpe <:< pt, s"error at ${sourcePos(tree.pos)}\n" + err.typeMismatchStr(tree.tpe, pt)) diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 7aaf7d86d73e..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 @@ -69,7 +70,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) @@ -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 _ => 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 +} diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 2ff116f461b5..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() } @@ -594,7 +604,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/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 _ => 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 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/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 c23b820e4a57..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) } } @@ -235,7 +236,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) @@ -246,7 +247,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 +259,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) @@ -285,7 +286,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 9066012f035a..80eb5965c63f 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 = @@ -390,8 +393,8 @@ 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) - typed(cpy.Apply(tree)(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) + val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType) + typedUnadapted(cpy.Apply(tree)(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) case _ => 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) @@ -1009,7 +1012,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) @@ -1272,7 +1276,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 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 _ => } } 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) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 02184599729a..614dc95271db 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,16 +14,13 @@ class tests extends CompilerTest { "-pagewidth", "160") implicit val defaultOptions = noCheckOptions ++ List( - "-YnoDeepSubtypes", - "-Ycheck:patternMatcher,literalize,capturedVars", - "-Ystop-before:erasure" - /*,"-uniqid", "-explaintypes", "-verbose", "-Ylog:splitter", "-Xprompt", "-YnoDoubleBindings"*/ + "-Yno-deep-subtypes", + "-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("-Yno-deep-subtypes") val posDir = "./tests/pos/" val negDir = "./tests/neg/" @@ -49,7 +46,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,29 +93,25 @@ 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) @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) @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) + //@Test def tools = compileDir(dotcDir + "tools", "-deep" :: Nil)(allowDeepSubtypes) @Test def testNonCyclic = compileArgs(Array( dotcDir + "tools/dotc/CompilationUnit.scala", @@ -129,7 +121,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", 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 = { 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/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/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/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 { 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/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 +} 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" => "" 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/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) - } -} 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))) +}