From d8864d36e07e1343fc623250b72fdd590c84e732 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jun 2014 19:15:41 +0200 Subject: [PATCH 01/47] Add cloneScope method and handle versioning of ClassInfo#decls This is done to streamline changing class denotations in new phases by adding to (or otherwise modifying) their decls scope. --- src/dotty/tools/dotc/core/Scopes.scala | 9 +++++---- src/dotty/tools/dotc/core/Types.scala | 6 +++--- src/dotty/tools/dotc/core/transform/Erasure.scala | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/core/Scopes.scala b/src/dotty/tools/dotc/core/Scopes.scala index 367713d11742..919e35a7e97b 100644 --- a/src/dotty/tools/dotc/core/Scopes.scala +++ b/src/dotty/tools/dotc/core/Scopes.scala @@ -18,6 +18,7 @@ import SymDenotations._ import printing.Texts._ import printing.Printer import util.common._ +import util.DotClass import SymDenotations.NoDenotation import collection.mutable.ListBuffer @@ -55,7 +56,7 @@ object Scopes { * or to delete them. These methods are provided by subclass * MutableScope. */ - abstract class Scope extends printing.Showable with Iterable[Symbol] { + abstract class Scope extends DotClass with printing.Showable with Iterable[Symbol] { /** The last scope-entry from which all others are reachable via `prev` */ private[dotc] def lastEntry: ScopeEntry @@ -77,8 +78,8 @@ object Scopes { */ def iterator: Iterator[Symbol] = toList.iterator - /** Returns a new scope with the same content as this one. */ - def cloneScope(implicit ctx: Context): Scope + /** Returns a new mutable scope with the same content as this one. */ + def cloneScope(implicit ctx: Context): MutableScope /** Is the scope empty? */ override def isEmpty: Boolean = lastEntry eq null @@ -354,7 +355,7 @@ object Scopes { override def size = 0 override def nestingLevel = 0 override def toList = Nil - override def cloneScope(implicit ctx: Context): Scope = this + override def cloneScope(implicit ctx: Context): MutableScope = unsupported("cloneScope") override def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry = null override def lookupNextEntry(entry: ScopeEntry)(implicit ctx: Context): ScopeEntry = null } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 4885b30d8516..dad88bc60466 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2077,8 +2077,8 @@ object Types { if (prefix eq this.prefix) this else ClassInfo(prefix, cls, classParents, decls, selfInfo) - def derivedClassInfo(prefix: Type = this.prefix, classParents: List[TypeRef] = classParents, selfInfo: DotClass = this.selfInfo)(implicit ctx: Context) = - if ((prefix eq this.prefix) && (classParents eq this.classParents) && (selfInfo eq this.selfInfo)) this + def derivedClassInfo(prefix: Type = this.prefix, classParents: List[TypeRef] = classParents, decls: Scope = this.decls, selfInfo: DotClass = this.selfInfo)(implicit ctx: Context) = + if ((prefix eq this.prefix) && (classParents eq this.classParents) && (decls eq this.decls) && (selfInfo eq this.selfInfo)) this else ClassInfo(prefix, cls, classParents, decls, selfInfo) override def computeHash = doHash(cls, prefix) @@ -2431,7 +2431,7 @@ object Types { case self: Type => this(self) case _ => tp.self } - tp.derivedClassInfo(prefix1, parents1, self1) + tp.derivedClassInfo(prefix1, parents1, tp.decls, self1) } } diff --git a/src/dotty/tools/dotc/core/transform/Erasure.scala b/src/dotty/tools/dotc/core/transform/Erasure.scala index da14f72d1aa6..e35cdd12862b 100644 --- a/src/dotty/tools/dotc/core/transform/Erasure.scala +++ b/src/dotty/tools/dotc/core/transform/Erasure.scala @@ -146,7 +146,7 @@ class Erasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcard if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil else if (cls eq defn.ArrayClass) defn.ObjectClass.typeRef :: Nil else removeLaterObjects(classParents.mapConserve(eraseTypeRef)) - tp.derivedClassInfo(this(pre), parents, this(tp.selfType)) + tp.derivedClassInfo(this(pre), parents, decls, this(tp.selfType)) case NoType | NoPrefix | ErrorType => tp case tp: WildcardType if wildcardOK => From 65eb50f096506e8ae279f480d8f6c9dc0b95f5e4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jun 2014 19:16:15 +0200 Subject: [PATCH 02/47] Make SuperAccessorName and extractor. Should do the same with other name-creator/name-test pairs. --- src/dotty/tools/dotc/core/NameOps.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 5bdafcf8a854..7c10bfd4dfc2 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -47,6 +47,13 @@ object NameOps { } } + object SuperAccessorName { + val pre = nme.SUPER_PREFIX + def apply(name: TermName): TermName = pre ++ name + def unapply(name: TermName): Option[TermName] = + if (name startsWith pre) Some(name.drop(pre.length).asTermName) else None + } + implicit class NameDecorator[N <: Name](val name: N) extends AnyVal { import nme._ @@ -59,7 +66,6 @@ object NameOps { def isLocalDummyName = name startsWith LOCALDUMMY_PREFIX def isLoopHeaderLabel = (name startsWith WHILE_PREFIX) || (name startsWith DO_WHILE_PREFIX) def isProtectedAccessorName = name startsWith PROTECTED_PREFIX - def isSuperAccessorName = name startsWith SUPER_PREFIX def isReplWrapperName = name containsSlice INTERPRETER_IMPORT_WRAPPER def isSetterName = name endsWith SETTER_SUFFIX def isTraitSetterName = isSetterName && (name containsSlice TRAIT_SETTER_SEPARATOR) @@ -247,10 +253,6 @@ object NameOps { else -1 } - /** The name of a super-accessor */ - def superAccessorName: TermName = - SUPER_PREFIX ++ name - /** The name of an accessor for protected symbols. */ def protectedAccessorName: TermName = PROTECTED_PREFIX ++ name From 8b93f7b4339abb4e376860770eb5b7ca271de71b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Jun 2014 12:36:18 +0200 Subject: [PATCH 03/47] Added MacroTransform as a transformer template for macro phases. --- src/dotty/tools/dotc/ast/Trees.scala | 2 + .../tools/dotc/transform/MacroTransform.scala | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/dotty/tools/dotc/transform/MacroTransform.scala diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index dfd69c03c5a5..5d92702a2cfd 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -1162,6 +1162,8 @@ object Trees { cpy.Alternative(tree, transform(trees)) case UnApply(fun, implicits, patterns) => cpy.UnApply(tree, transform(fun), transform(implicits), transform(patterns)) + case EmptyValDef => + tree case ValDef(mods, name, tpt, rhs) => cpy.ValDef(tree, mods, name, transform(tpt), transform(rhs)) case DefDef(mods, name, tparams, vparamss, tpt, rhs) => diff --git a/src/dotty/tools/dotc/transform/MacroTransform.scala b/src/dotty/tools/dotc/transform/MacroTransform.scala new file mode 100644 index 000000000000..4113b2d8effa --- /dev/null +++ b/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -0,0 +1,54 @@ +package dotty.tools.dotc +package transform + +import core._ +import typer._ +import Phases._ +import ast.Trees._ +import Contexts._ +import Symbols._ +import Decorators._ + +/** A base class for transforms. + * A transform contains a compiler phase which applies a tree transformer. + */ +abstract class MacroTransform extends Phase { + + import ast.tpd._ + + override def run(implicit ctx: Context): Unit = { + val unit = ctx.compilationUnit + unit.tpdTree = newTransformer.transform(unit.tpdTree) + } + + def newTransformer: TransformerMap + + class TransformerMap extends TreeMap { + + def transformStats(trees: List[Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + val exprCtx = ctx.withOwner(exprOwner) + def transformStat(stat: Tree): Tree = stat match { + case _: Import | _: DefTree => transform(stat) + case Thicket(stats) => cpy.Thicket(stat, stats mapConserve transformStat) + case _ => transform(stat)(exprCtx) + } + flatten(trees.mapconserve(transformStat(_))) + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + def localCtx = ctx.fresh.setTree(tree).setOwner(tree.symbol) + tree match { + case _: PackageDef | _: MemberDef => + super.transform(tree)(localCtx) + case Template(constr, parents, self, body) => + cpy.Template(tree, + transformSub(constr), + transform(parents), + transformSub(self), + transformStats(body, tree.symbol)) + case _ => + super.transform(tree) + } + } + } +} From 2e15951a3a7e9dcb877a31ab4a9f32e428d47760 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Jul 2014 13:51:18 +0200 Subject: [PATCH 04/47] New utitility methods in tpd. Added the following utility methods: - polyDefDef: Create a DefDef given a function that takes type and value parameters and yields a body. - appliedToTypeTrees: Apply function to type arguments ion a TypeApply if arguments are nonempty. - mkAsInstanceOf - ensureConforms: generate a cast if expression has non-conforming type. --- src/dotty/tools/dotc/ast/tpd.scala | 19 ++++++++++++++++--- src/dotty/tools/dotc/transform/Erasure.scala | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 3b240ad2ca57..84e1118f947e 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -182,7 +182,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def DefDef(sym: TermSymbol, rhs: Tree = EmptyTree)(implicit ctx: Context): DefDef = ta.assignType(DefDef(sym, Function.const(rhs) _), sym) - def DefDef(sym: TermSymbol, rhsFn: List[List[Tree]] => Tree)(implicit ctx: Context): DefDef = { + def DefDef(sym: TermSymbol, rhsFn: List[List[Tree]] => Tree)(implicit ctx: Context): DefDef = + polyDefDef(sym, Function.const(rhsFn)) + + def polyDefDef(sym: TermSymbol, rhsFn: List[Type] => List[List[Tree]] => Tree)(implicit ctx: Context): DefDef = { val (tparams, mtp) = sym.info match { case tp: PolyType => val tparams = ctx.newTypeParams(sym, tp.paramNames, EmptyFlags, tp.instantiateBounds) @@ -200,11 +203,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case tp => (Nil, tp) } val (vparamss, rtp) = valueParamss(mtp) + val targs = tparams map (_.typeRef) val argss = vparamss map (_ map (vparam => Ident(vparam.termRef))) ta.assignType( untpd.DefDef( Modifiers(sym), sym.name, tparams map TypeDef, - vparamss map (_ map (ValDef(_))), TypeTree(rtp), rhsFn(argss)), sym) + vparamss map (_ map (ValDef(_))), TypeTree(rtp), rhsFn(targs)(argss)), sym) } def TypeDef(sym: TypeSymbol)(implicit ctx: Context): TypeDef = @@ -381,7 +385,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { new TreeTypeMap(ownerMap = (sym => if (sym == from) to else sym)).apply(tree) def appliedToTypes(targs: List[Type])(implicit ctx: Context): Tree = - if (targs.isEmpty) tree else TypeApply(tree, targs map (TypeTree(_))) + appliedToTypeTrees(targs map (TypeTree(_))) + + def appliedToTypeTrees(targs: List[Tree])(implicit ctx: Context): Tree = + if (targs.isEmpty) tree else TypeApply(tree, targs) } implicit class ListOfTreeDecorator(val xs: List[tpd.Tree]) extends AnyVal { @@ -451,6 +458,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def mkAnd(tree1: Tree, tree2: Tree)(implicit ctx: Context) = Apply(Select(tree1, defn.Boolean_and), tree2 :: Nil) + def mkAsInstanceOf(tree: Tree, pt: Type)(implicit ctx: Context): Tree = + TypeApply(Select(tree, defn.Any_asInstanceOf), TypeTree(pt) :: Nil) + + def ensureConforms(tree: Tree, pt: Type)(implicit ctx: Context): Tree = + if (tree.tpe <:< pt) tree else mkAsInstanceOf(tree, pt) + // ensure that constructors are fully applied? // ensure that normal methods are fully applied? diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index e56132057d10..c9b92e55fd83 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -139,7 +139,7 @@ object Erasure { cast(runtimeCall(nme.toObjectArray, tree :: Nil), pt) case _ => ctx.log(s"casting from ${tree.showSummary}: ${tree.tpe.show} to ${pt.show}") - TypeApply(Select(tree, defn.Any_asInstanceOf), TypeTree(pt) :: Nil) + mkAsInstanceOf(tree, pt) } /** Adaptation of an expression `e` to an expected type `PT`, applying the following From a2a5a57911bc4046e78d88b28a0c7759f3b8c595 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Jul 2014 13:52:21 +0200 Subject: [PATCH 05/47] ThisType trees have their class as a denotation Added this case, so that .symbol on a ThisType returns the underlying class. --- src/dotty/tools/dotc/ast/Trees.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 5d92702a2cfd..8a36fee3a5ae 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -361,6 +361,7 @@ object Trees { type ThisTree[-T >: Untyped] <: DenotingTree[T] override def denot(implicit ctx: Context) = tpe match { case tpe: NamedType => tpe.denot + case ThisType(cls) => cls.denot case _ => NoDenotation } } From 0cf64e8110717ec1c1008a7cdfb5c173a3f0bc49 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Jul 2014 13:55:09 +0200 Subject: [PATCH 06/47] Error method for implementation restrictions --- src/dotty/tools/dotc/reporting/Reporter.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 82b0a115814c..7f0c88bc51b2 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -93,6 +93,9 @@ trait Reporting { this: Context => reporter.report(new Error(msg, pos)) } + def restrictionError(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = + error(s"Implementation restriction: $msg", pos) + def incompleteInputError(msg: String, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit = reporter.incomplete(new Error(msg, pos))(ctx) From 8112a39d3a00f53a68af794d0a83cf995faf31e2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Jul 2014 14:37:58 +0200 Subject: [PATCH 07/47] Improved printing of flags (1) Make sure ModifierFlags is TermFlags and TypeFlags (2) Shorten private to private[this]; same with protected (3) Print [this] for local symbols in RefinedPrinter --- src/dotty/tools/dotc/core/Flags.scala | 17 ++++++++++++++--- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 40da7525dba4..07259276e8f5 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -102,8 +102,16 @@ object Flags { } /** The list of non-empty names of flags that are set in this FlagSet */ - def flagStrings: Seq[String] = - (2 to MaxFlag).flatMap(flagString) + def flagStrings: Seq[String] = { + val rawStrings = (2 to MaxFlag).flatMap(flagString) + if (this is Local) + rawStrings.filter(_ != "").map { + case "private" => "private[this]" + case "protected" => "protected[this]" + case str => str + } + else rawStrings + } /** The string representation of this flag set */ override def toString = flagStrings.mkString(" ") @@ -405,7 +413,10 @@ object Flags { /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Trait | Module | Param | Synthetic | Package + SourceModifierFlags | Module | Param | Synthetic | Package | Local + // | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags + + assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags) /** Flags representing access rights */ final val AccessFlags = Private | Protected | Local diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 385b407b54e4..a327b4e3ea44 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -15,7 +15,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { override protected def recursionLimitExceeded() = {} - protected val PrintableFlags = (SourceModifierFlags | Label | Module).toCommonFlags + protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags /** The closest enclosing DefDef, TypeDef, or ClassDef node */ private var currentOwner: untpd.Tree = untpd.EmptyTree From 3ab2784948d084557e88cd7eb5c55a29613742d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Jul 2014 14:43:18 +0200 Subject: [PATCH 08/47] Added phase: SuperAccessors Rewrote SuperAccessors (more to be done; see comments), and added stuff here and there to make it work smoother. --- src/dotty/tools/dotc/Compiler.scala | 1 + src/dotty/tools/dotc/core/Denotations.scala | 1 + src/dotty/tools/dotc/core/NameOps.scala | 9 +- src/dotty/tools/dotc/core/Signature.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 45 +- src/dotty/tools/dotc/core/Symbols.scala | 12 + src/dotty/tools/dotc/core/Types.scala | 9 +- .../dotc/core/pickling/ClassfileParser.scala | 2 +- .../tools/dotc/transform/MacroTransform.scala | 15 +- .../tools/dotc/transform/SuperAccessors.scala | 557 ++++++++++++++++++ test/dotc/tests.scala | 2 +- tests/pos/paramAliases.scala | 11 + 12 files changed, 651 insertions(+), 15 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/SuperAccessors.scala create mode 100644 tests/pos/paramAliases.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index a4a8fbbc84b1..55452d6ff28c 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -20,6 +20,7 @@ class Compiler { def phases: List[List[Phase]] = List( List(new FrontEnd), + List(new SuperAccessors), List(new LazyValsCreateCompanionObjects, new TailRec), //force separataion between lazyVals and LVCreateCO List(new PatternMatcher, diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 264f9aa467da..120f8e0f8732 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -574,6 +574,7 @@ object Denotations { /** Install this denotation to be the result of the given denotation transformer. * This is the implementation of the same-named method in SymDenotations. * It's placed here because it needs access to private fields of SingleDenotation. + * @pre Can only be called in `phase.next`. */ protected def installAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = { val targetId = phase.next.id diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 7c10bfd4dfc2..404a0844adc1 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -132,6 +132,9 @@ object NameOps { if (flags is (ModuleClass, butNot = Package)) name.asTypeName.moduleClassName.asInstanceOf[N] else name + /** The superaccessor for method with given name */ + def superName: TermName = (nme.SUPER_PREFIX ++ name).toTermName + /** The expanded name of `name` relative to this class `base` with given `separator` */ def expandedName(base: Symbol, separator: Name = nme.EXPAND_SEPARATOR)(implicit ctx: Context): N = { @@ -255,11 +258,11 @@ object NameOps { /** The name of an accessor for protected symbols. */ def protectedAccessorName: TermName = - PROTECTED_PREFIX ++ name + PROTECTED_PREFIX ++ name.unexpandedName() /** The name of a setter for protected symbols. Used for inherited Java fields. */ - def protectedSetterName(name: Name): TermName = - PROTECTED_SET_PREFIX ++ name + def protectedSetterName: TermName = + PROTECTED_SET_PREFIX ++ name.unexpandedName() def moduleVarName: TermName = name ++ MODULE_VAR_SUFFIX diff --git a/src/dotty/tools/dotc/core/Signature.scala b/src/dotty/tools/dotc/core/Signature.scala index eb85fbb99e08..22d038d11f69 100644 --- a/src/dotty/tools/dotc/core/Signature.scala +++ b/src/dotty/tools/dotc/core/Signature.scala @@ -49,7 +49,7 @@ object Signature { * a type different from PolyType, MethodType, or ExprType. */ val NotAMethod = Signature(List(), EmptyTypeName) - + /** The signature of an overloaded denotation. */ val OverloadedSignature = Signature(List(tpnme.OVERLOADED), EmptyTypeName) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 8027620458dc..6432370389af 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -395,6 +395,14 @@ object SymDenotations { /** Is this a user defined "def" method? Excluded are accessors. */ final def isSourceMethod(implicit ctx: Context) = this is (Method, butNot = Accessor) + /** This this a method in a value class that is implemented as an extension method? */ + final def isMethodWithExtension(implicit ctx: Context) = + isSourceMethod && + owner.isDerivedValueClass && + !isConstructor && + !is(SuperAccessor) && + !is(Macro) + /** Is this a setter? */ final def isGetter(implicit ctx: Context) = (this is Accessor) && !originalName.isSetterName @@ -447,7 +455,7 @@ object SymDenotations { def accessWithin(boundary: Symbol) = ctx.owner.isContainedIn(boundary) && (!(this is JavaDefined) || // disregard package nesting for Java - ctx.owner.enclosingPackage == boundary.enclosingPackage) + ctx.owner.enclosingPackageClass == boundary.enclosingPackageClass) /** Are we within definition of linked class of `boundary`? */ def accessWithinLinked(boundary: Symbol) = { @@ -572,6 +580,12 @@ object SymDenotations { NoSymbol } + /** The field accessed by this getter or setter */ + def accessedField(implicit ctx: Context): Symbol = { + val fieldName = if (isSetter) name.asTermName.setterToGetter else name + owner.info.decl(fieldName).suchThat(d => !(d is Method)).symbol + } + /** The chain of owners of this denotation, starting with the denoting symbol itself */ final def ownersIterator(implicit ctx: Context) = new Iterator[Symbol] { private[this] var current = symbol @@ -624,8 +638,8 @@ object SymDenotations { } /** The package class containing this denotation */ - final def enclosingPackage(implicit ctx: Context): Symbol = - if (this is PackageClass) symbol else owner.enclosingPackage + final def enclosingPackageClass(implicit ctx: Context): Symbol = + if (this is PackageClass) symbol else owner.enclosingPackageClass /** The module object with the same (term-) name as this class or module class, * and which is also defined in the same scope and compilation unit. @@ -747,7 +761,6 @@ object SymDenotations { loop(base.info.baseClasses.dropWhile(owner != _).tail) } - /** A a member of class `base` is incomplete if * (1) it is declared deferred or * (2) it is abstract override and its super symbol in `base` is @@ -895,6 +908,15 @@ object SymDenotations { case _ => Nil } + /** The symbol of the superclass, NoSymbol if no superclass exists */ + def superClass(implicit ctx: Context): Symbol = classParents match { + case parent :: _ => + val cls = parent.classSymbol + if (cls is Trait) NoSymbol else cls + case _ => + NoSymbol + } + /** The denotation is fully completed: all attributes are fully defined. * ClassDenotations compiled from source are first completed, then fully completed. * @see Namer#ClassCompleter @@ -1292,6 +1314,21 @@ object SymDenotations { def underlyingOfValueClass: Type = ??? def valueClassUnbox: Symbol = ??? + + /** 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) + } + } } /** The denotation of a package class. diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 26553ddff77c..cfd5bdf231f4 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -16,6 +16,7 @@ import printing.Printer import Types._ import Annotations._ import util.Positions._ +import DenotTransformers._ import StdNames._ import NameOps._ import ast.tpd.{TreeTypeMap, Tree} @@ -372,6 +373,17 @@ object Symbols { this } + /** Enter this symbol in its class owner after given `phase`. Create a fresh + * denotation for its owner class if the class has not yet already one + * that starts being valid after `phase`. + * @pre Symbol is a class member + */ + def enteredAfter(phase: DenotTransformer)(implicit ctx: Context): this.type = { + val nextCtx = ctx.withPhase(phase.next) + this.owner.asClass.ensureFreshScopeAfter(phase)(nextCtx) + entered(nextCtx) + } + /** This symbol, if it exists, otherwise the result of evaluating `that` */ def orElse(that: => Symbol)(implicit ctx: Context) = if (this.exists) this else that diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index dad88bc60466..a92b252b5ef0 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1609,10 +1609,15 @@ object Types { protected def computeSignature(implicit ctx: Context): Signature - protected def resultSignature(implicit ctx: Context) = resultType match { + protected def resultSignature(implicit ctx: Context) = try resultType match { case rtp: SignedType => rtp.signature case tp => Signature(tp, isJava = false) } + catch { + case ex: AssertionError => + println(i"failure while taking result signture of $resultType") + throw ex + } final override def signature(implicit ctx: Context): Signature = { if (ctx.runId != mySignatureRunId) { @@ -1717,6 +1722,8 @@ object Types { def apply(paramNames: List[TermName], paramTypes: List[Type])(resultTypeExp: MethodType => Type)(implicit ctx: Context): MethodType def apply(paramNames: List[TermName], paramTypes: List[Type], resultType: Type)(implicit ctx: Context): MethodType = apply(paramNames, paramTypes)(_ => resultType) + def apply(paramTypes: List[Type])(resultTypeExp: MethodType => Type)(implicit ctx: Context): MethodType = + apply(nme.syntheticParamNames(paramTypes.length), paramTypes)(resultTypeExp) def apply(paramTypes: List[Type], resultType: Type)(implicit ctx: Context): MethodType = apply(nme.syntheticParamNames(paramTypes.length), paramTypes, resultType) def fromSymbols(params: List[Symbol], resultType: Type)(implicit ctx: Context) = { diff --git a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala index 0ed301732e8a..0f074759759f 100644 --- a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala @@ -752,7 +752,7 @@ class ClassfileParser( private def setPrivateWithin(denot: SymDenotation, jflags: Int)(implicit ctx: Context): Unit = { if ((jflags & (JAVA_ACC_PRIVATE | JAVA_ACC_PUBLIC)) == 0) - denot.privateWithin = denot.enclosingPackage + denot.privateWithin = denot.enclosingPackageClass } private def isPrivate(flags: Int) = (flags & JAVA_ACC_PRIVATE) != 0 diff --git a/src/dotty/tools/dotc/transform/MacroTransform.scala b/src/dotty/tools/dotc/transform/MacroTransform.scala index 4113b2d8effa..eacbd1717e1c 100644 --- a/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -21,9 +21,17 @@ abstract class MacroTransform extends Phase { unit.tpdTree = newTransformer.transform(unit.tpdTree) } - def newTransformer: TransformerMap + protected def newTransformer(implicit ctx: Context): Transformer - class TransformerMap extends TreeMap { + class Transformer extends TreeMap { + + protected def localCtx(tree: Tree)(implicit ctx: Context) = + ctx.fresh.setTree(tree).setOwner(tree.symbol) + + /** The current enclosing class + * @pre We must be inside a class + */ + def currentClass(implicit ctx: Context): ClassSymbol = ctx.owner.enclosingClass.asClass def transformStats(trees: List[Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { val exprCtx = ctx.withOwner(exprOwner) @@ -36,10 +44,9 @@ abstract class MacroTransform extends Phase { } override def transform(tree: Tree)(implicit ctx: Context): Tree = { - def localCtx = ctx.fresh.setTree(tree).setOwner(tree.symbol) tree match { case _: PackageDef | _: MemberDef => - super.transform(tree)(localCtx) + super.transform(tree)(localCtx(tree)) case Template(constr, parents, self, body) => cpy.Template(tree, transformSub(constr), diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala new file mode 100644 index 000000000000..fd784862ea91 --- /dev/null +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -0,0 +1,557 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import dotty.tools.dotc.ast.{Trees, tpd} +import scala.collection.{ mutable, immutable } +import mutable.ListBuffer +import scala.annotation.tailrec +import core._ +import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import util.Positions._ +import Decorators._ +import Symbols._ + +/** This phase performs the following functions, each of which could be split out in a + * mini-phase: + * + * (1) Adds super accessors for all super calls that either + * appear in a trait or have as a target a member of some outer class. + * + * (2) Converts parameter fields that have the same name as a corresponding + * public parameter field in a superclass to a forwarder to the superclass + * field (corresponding = super class field is initialized with subclass field) + * + * (3) Adds protected accessors if the access to the protected member happens + * in a class which is not a subclass of the member's owner. + * + * (4) Finally, the phase used to mangle the names of class-members which are + * private up to an enclosing non-package class, in order to avoid overriding conflicts. + * This is currently disabled, and class-qualified private is deprecated. + * + * It also checks that: + * + * (1) Symbols accessed from super are not abstract, or are overridden by + * an abstract override. + * + * (2) If a symbol accessed accessed from super is defined in a real class (not a trait), + * there are no abstract members which override this member in Java's rules + * (see SI-4989; such an access would lead to illegal bytecode) + * + * (3) Super calls do not go to some synthetic members of Any (see isDisallowed) + * + * (4) Super calls do not go to synthetic field accessors + * + * (5) A class and its companion object do not both define a class or module with the + * same name. + * + * TODO: Rename phase to "Accessors" because it handles more than just super accessors + */ +class SuperAccessors extends MacroTransform with DenotTransformer { thisTransformer => + + import tpd._ + + /** the following two members override abstract members in Transform */ + val name: String = "superaccessors" + + protected def newTransformer(implicit ctx: Context): Transformer = + new SuperAccTransformer + + /** No transformation here, but new denotations are installed by the tree traversal */ + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref + + class SuperAccTransformer extends Transformer { + + /** validCurrentOwner arrives undocumented, but I reverse engineer it to be + * a flag for needsProtectedAccessor which is false while transforming either + * a by-name argument block or a closure. This excludes them from being + * considered able to access protected members via subclassing (why?) which in turn + * increases the frequency with which needsProtectedAccessor will be true. + */ + private var validCurrentOwner = true + + private val accDefs = mutable.Map[Symbol, ListBuffer[Tree]]() + + private def storeAccessorDefinition(clazz: Symbol, tree: Tree) = { + val buf = accDefs.getOrElse(clazz, sys.error("no acc def buf for "+clazz)) + buf += tree + } + + private def ensureSigned(tpe: Type)(implicit ctx: Context) = tpe match { + case tpe: SignedType => tpe + case _ => ExprType(tpe) + } + + private def ensureAccessor(sel: Select)(implicit ctx: Context) = { + val Select(qual, name) = sel + val sym = sel.symbol + val clazz = qual.symbol.asClass + val supername = name.superName + + val superAcc = clazz.info.decl(supername).suchThat(_.signature == sym.signature).symbol orElse { + ctx.debuglog(s"add super acc ${sym.showLocated} to $clazz") + val acc = ctx.newSymbol( + clazz, supername, SuperAccessor | Private | Artifact, + ensureSigned(sel.tpe.widenSingleton), coord = sym.coord).enteredAfter(thisTransformer) + // Diagnostic for SI-7091 + if (!accDefs.contains(clazz)) + ctx.error(s"Internal error: unable to store accessor definition in ${clazz}. clazz.hasPackageFlag=${clazz is Package}. Accessor required for ${sel} (${sel.show})", sel.pos) + else storeAccessorDefinition(clazz, DefDef(acc, EmptyTree)) + acc + } + + Select(This(clazz), superAcc) withPos sel.pos + } + + private def transformArgs(formals: List[Type], args: List[Tree])(implicit ctx: Context) = + args.zipWithConserve(formals) {(arg, formal) => + formal match { + case _: ExprType => withInvalidOwner(transform(arg)) + case _ => transform(arg) + } + } + + /** Check that a class and its companion object to not both define + * a class or module with same name + */ + private def checkCompanionNameClashes(cls: ClassSymbol)(implicit ctx: Context): Unit = + if (!(cls.owner is ModuleClass)) { + val other = cls.owner.linkedClass.info.decl(cls.name) + if (other.symbol.isClass) + ctx.error(s"name clash: ${cls.owner} defines $cls" + "\n" + + s"and its companion ${cls.owner.companionModule} also defines $other", + cls.pos) + } + + /** Expand all declarations in this class which are private within a class. + * Note: It's not sure whether this is the right way. Persumably, we expand + * qualified privates to prvent them from overriding or be overridden by + * symbols that are defined in classes where the qualified private is not + * visible. But it seems a bit dubiuous to do this between type checking + * and refchecks. + */ + def expandQualifiedPrivates(cls: ClassSymbol)(implicit ctx: Context) = { + val decls = cls.info.decls + val decls1: MutableScope = newScope + def needsExpansion(sym: Symbol) = + sym.privateWithin.isClass && + !(sym is Protected) && + !(sym.privateWithin is ModuleClass) && + !(sym is ExpandedName) && + !sym.isConstructor + val nextCtx = ctx.withPhase(thisTransformer.next) + for (s <- decls) { + // !!! hacky to do this by mutation; would be better to do with an infotransformer + // !!! also, why is this done before pickling? + if (needsExpansion(s)) { + ctx.deprecationWarning(s"private qualified with a class has been deprecated, use package enclosing ${s.privateWithin} instead", s.pos) + /* disabled for now + decls.asInstanceOf[MutableScope].unlink(s) + s.copySymDenotation(name = s.name.expandedName(s.privateWithin)) + .installAfter(thisTransformer) + decls1.enter(s)(nextCtx) + ctx.log(i"Expanded ${s.name}, ${s.name(nextCtx)}, sym") + */ + } + } + /* Disabled for now: + if (decls1.nonEmpty) { + for (s <- decls) + if (!needsExpansion(s)) decls1.enter(s)(nextCtx) + val ClassInfo(pre, _, ps, _, selfInfo) = cls.classInfo + cls.copySymDenotation(info = ClassInfo(pre, cls, ps, decls1, selfInfo)) + .installAfter(thisTransformer) + } + */ + } + + private def transformSuperSelect(sel: Select)(implicit ctx: Context): Tree = { + val Select(sup @ Super(_, mix), name) = sel + val sym = sel.symbol + assert(sup.symbol.exists, s"missing symbol in $sel: ${sup.tpe}") + val clazz = sup.symbol.asClass + + if (sym is Deferred) { + val member = sym.overridingSymbol(clazz) + if (mix != tpnme.EMPTY || + !member.exists || + !(member is AbsOverride) && member.isIncompleteIn(clazz)) + ctx.error( + i"${sym.showLocated} is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override'", + sel.pos) + } + else if (mix == tpnme.EMPTY && !(sym.owner is Trait)) + // SI-4989 Check if an intermediate class between `clazz` and `sym.owner` redeclares the method as abstract. + for (intermediateClass <- clazz.info.baseClasses.tail.takeWhile(_ != sym.owner)) { + val overriding = sym.overridingSymbol(intermediateClass) + if ((overriding is (Deferred, butNot = AbsOverride)) && !(overriding.owner is Trait)) + ctx.error( + s"${sym.showLocated} cannot be directly accessed from ${clazz} because ${overriding.owner} redeclares it as abstract", + sel.pos) + + } + if (name.isTermName && mix == tpnme.EMPTY && + ((clazz is Trait) || clazz != ctx.owner.enclosingClass || !validCurrentOwner)) + ensureAccessor(sel)(ctx.withPhase(thisTransformer.next)) + else sel + } + + // Disallow some super.XX calls targeting Any methods which would + // otherwise lead to either a compiler crash or runtime failure. + private def isDisallowed(sym: Symbol)(implicit ctx: Context) = { + val d = defn + import d._ + (sym eq Any_isInstanceOf) || + (sym eq Any_asInstanceOf) || + (sym eq Any_==) || + (sym eq Any_!=) || + (sym eq Any_##) + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + val sym = tree.symbol + + def mayNeedProtectedAccessor(sel: Select, targs: List[Tree], goToSuper: Boolean) = + if (sym.exists && needsProtectedAccessor(sym, tree.pos)) { + ctx.debuglog("Adding protected accessor for " + tree) + transform(makeAccessor(sel, targs)) + } + else if (goToSuper) super.transform(tree) + else tree + + try tree match { + // Don't transform patterns or strange trees will reach the matcher (ticket #4062) + // TODO Drop once this runs after pattern matcher + case CaseDef(pat, guard, body) => + cpy.CaseDef(tree, pat, transform(guard), transform(body)) + + case TypeDef(_, _, impl: Template) => + val cls = sym.asClass + checkCompanionNameClashes(cls) + expandQualifiedPrivates(cls) + super.transform(tree) + + case impl: Template => + + /** For all parameter accessors + * + * val x: T = ... + * + * if + * (1) x is forwarded in the supercall to a parameter that's also named `x` + * (2) the superclass parameter accessor for `x` is accessible from the current class to + * change the accessor to + * + * def x: T = super.x.asInstanceOf[T] + * + * Do the same also if there are intermediate inaccessible parameter accessor forwarders. + * The aim of this transformation is to avoid redundant parameter accessor fields. + */ + def forwardParamAccessors(stats: List[Tree]): List[Tree] = { + val (superArgs, superParamNames) = impl.parents match { + case superCall @ Apply(fn, args) :: _ => + fn.tpe.widen match { + case MethodType(paramNames, _) => (args, paramNames) + case _ => (Nil, Nil) + } + case _ => (Nil, Nil) + } + def inheritedAccessor(sym: Symbol): Symbol = { + val candidate = sym.owner.asClass.superClass + .info.decl(sym.name).suchThat(_ is (ParamAccessor, butNot = Mutable)).symbol + if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate + else if (candidate is Method) inheritedAccessor(candidate) + else NoSymbol + } + def forwardParamAccessor(stat: Tree): Tree = { + stat match { + case stat: ValDef => + val sym = stat.symbol.asTerm + if (sym is (PrivateLocalParamAccessor, butNot = Mutable)) { + val idx = superArgs.indexWhere(_.symbol == sym) + if (idx >= 0 && superParamNames(idx) == stat.name) { // supercall to like-named parameter + val alias = inheritedAccessor(sym) + if (alias.exists) { + def forwarder(implicit ctx: Context) = { + sym.copySymDenotation(initFlags = sym.flags | Method, info = ensureSigned(sym.info)) + .installAfter(thisTransformer) + val superAcc = + Select(Super(This(currentClass), tpnme.EMPTY, inConstrCall = false), alias) + DefDef(sym, ensureConforms(superAcc, sym.info.widen)) + } + return forwarder(ctx.withPhase(thisTransformer.next)) + } + } + } + case _ => + } + stat + } + stats map forwardParamAccessor + } + + def transformTemplate = { + val ownStats = new ListBuffer[Tree] + accDefs(currentClass) = ownStats + val body1 = forwardParamAccessors(transformStats(impl.body, tree.symbol)) + accDefs -= currentClass + ownStats ++= body1 + cpy.Template(tree, impl.constr, impl.parents, impl.self, body1) + } + transformTemplate + + case TypeApply(sel @ Select(This(_), name), args) => + mayNeedProtectedAccessor(sel, args, goToSuper = false) + + case sel @ Select(qual, name) => + def transformSelect = { + + qual match { + case This(_) => + // warn if they are selecting a private[this] member which + // also exists in a superclass, because they may be surprised + // to find out that a constructor parameter will shadow a + // field. See SI-4762. + /* to be added + if (settings.lint) { + if (sym.isPrivateLocal && sym.paramss.isEmpty) { + qual.symbol.ancestors foreach { parent => + parent.info.decls filterNot (x => x.isPrivate || x.isLocalToThis) foreach { m2 => + if (sym.name == m2.name && m2.isGetter && m2.accessed.isMutable) { + unit.warning(sel.pos, + sym.accessString + " " + sym.fullLocationString + " shadows mutable " + m2.name + + " inherited from " + m2.owner + ". Changes to " + m2.name + " will not be visible within " + + sym.owner + " - you may want to give them distinct names.") + } + } + } + } + } + */ + + /* + * A trait which extends a class and accesses a protected member + * of that class cannot implement the necessary accessor method + * because its implementation is in an implementation class (e.g. + * Foo$class) which inherits nothing, and jvm access restrictions + * require the call site to be in an actual subclass. So non-trait + * classes inspect their ancestors for any such situations and + * generate the accessors. See SI-2296. + */ + // FIXME - this should be unified with needsProtectedAccessor, but some + // subtlety which presently eludes me is foiling my attempts. + val shouldEnsureAccessor = ( + (currentClass is Trait) + && (sym is Protected) + && sym.enclosingClass != currentClass + && !(sym.owner is PackageClass) // SI-7091 no accessor needed package owned (ie, top level) symbols + && !(sym.owner is Trait) + && sym.owner.enclosingPackageClass != currentClass.enclosingPackageClass + && qual.symbol.info.member(sym.name).exists + && !needsProtectedAccessor(sym, tree.pos)) + if (shouldEnsureAccessor) { + ctx.log("Ensuring accessor for call to protected " + sym.showLocated + " from " + currentClass) + ensureAccessor(sel) + } else + mayNeedProtectedAccessor(sel, Nil, goToSuper = false) + + case Super(_, mix) => + if ((sym.isTerm) && !(sym is Method) || (sym is Accessor)) { + ctx.error(s"super may be not be used on ${sym.accessedField orElse sym}", tree.pos) + } else if (isDisallowed(sym)) { + ctx.error(s"super not allowed here: use this.${name.decode} instead", tree.pos) + } + transformSuperSelect(sel) + + case _ => + mayNeedProtectedAccessor(sel, Nil, goToSuper = true) + } + } + transformSelect + + case DefDef(mods, name, tparams, vparamss, tpt, rhs) => + val rhs1 = if (sym.isMethodWithExtension) withInvalidOwner(transform(rhs)) else transform(rhs) + cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, rhs1) + + case TypeApply(sel @ Select(qual, name), args) => + mayNeedProtectedAccessor(sel, args, goToSuper = true) + + case Assign(lhs @ Select(qual, name), rhs) => + def transformAssign = { + if ((lhs.symbol is Mutable) && + (lhs.symbol is JavaDefined) && + needsProtectedAccessor(lhs.symbol, tree.pos)) { + ctx.debuglog("Adding protected setter for " + tree) + val setter = makeSetter(lhs) + ctx.debuglog("Replaced " + tree + " with " + setter) + transform(Apply(setter, qual :: rhs :: Nil)) + } else + super.transform(tree) + } + transformAssign + + case Apply(fn, args) => + val MethodType(_, formals) = fn.tpe.widen + cpy.Apply(tree, transform(fn), transformArgs(formals, args)) + + case _ => + super.transform(tree) + } + catch { + case ex : AssertionError => + if (sym != null && sym != NoSymbol) + Console.println("TRANSFORM: " + tree.symbol.sourceFile) + + Console.println("TREE: " + tree) + throw ex + } + } + + private def withInvalidOwner[A](trans: => A): A = { + val saved = validCurrentOwner + validCurrentOwner = false + try trans + finally validCurrentOwner = saved + } + + /** Add a protected accessor, if needed, and return a tree that calls + * the accessor and returns the same member. The result is already + * typed. + * TODO why is targs needed? It looks like we can do without. + */ + private def makeAccessor(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = { + val Select(qual, _) = tree + val sym = tree.symbol.asTerm + val clazz = hostForAccessorOf(sym, currentClass) + assert(clazz.exists, sym) + ctx.debuglog("Decided for host class: " + clazz) + + val accName = sym.name.protectedAccessorName + + // if the result type depends on the this type of an enclosing class, the accessor + // has to take an object of exactly this type, otherwise it's more general + val receiverType = if (isThisType(sym.info.finalResultType)) clazz.thisType else clazz.classInfo.selfType + val accType = { + def accTypeOf(tpe: Type): Type = tpe match { + case tpe: PolyType => + tpe.derivedPolyType(tpe.paramNames, tpe.paramBounds, accTypeOf(tpe.resultType)) + case _ => + MethodType(receiverType :: Nil)(mt => tpe.substThis(sym.owner.asClass, MethodParam(mt, 0))) + } + accTypeOf(sym.info) + } + val protectedAccessor = clazz.info.decl(accName).suchThat(_.signature == accType.signature).symbol orElse { + val newAcc = ctx.newSymbol( + clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) + val code = polyDefDef(newAcc, targs => argss => { + val (receiver :: _) :: tail = argss + val base = Select(receiver, sym).appliedToTypes(targs) + (base /: argss)(Apply(_, _)) + }) + ctx.debuglog("created protected accessor: " + code) + storeAccessorDefinition(clazz, code) + newAcc + } + val res = + Apply(Select(This(clazz), protectedAccessor).appliedToTypeTrees(targs), qual :: Nil) + .withPos(tree.pos) + ctx.debuglog(s"Replaced $tree with $res") + res + } + + /** Add an accessor for field, if needed, and return a selection tree for it . + * The result is not typed. + */ + private def makeSetter(tree: Select)(implicit ctx: Context): Tree = { + val field = tree.symbol.asTerm + val clazz = hostForAccessorOf(field, currentClass) + assert(clazz.exists, field) + ctx.debuglog("Decided for host class: " + clazz) + + val accName = field.name.protectedSetterName + val accType = MethodType(clazz.classInfo.selfType :: field.info :: Nil, defn.UnitType) + val protectedAccessor = clazz.info.decl(accName).symbol orElse { + val newAcc = ctx.newSymbol( + clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) + val code = DefDef(newAcc, argss => { + val (receiver :: value :: Nil) :: Nil = argss + Assign(Select(receiver, field), value).withPos(tree.pos) + }) + ctx.debuglog("created protected setter: " + code) + storeAccessorDefinition(clazz, code) + newAcc + } + Select(This(clazz), protectedAccessor).withPos(tree.pos) + } + + /** Does `sym` need an accessor when accessed from `currentClass`? + * A special case arises for classes with explicit self-types. If the + * self type is a Java class, and a protected accessor is needed, we issue + * an error. If the self type is a Scala class, we don't add an accessor. + * An accessor is not needed if the access boundary is larger than the + * enclosing package, since that translates to 'public' on the host sys. + * (as Java has no real package nesting). + * + * If the access happens inside a 'trait', access is more problematic since + * the implementation code is moved to an '$class' class which does not + * inherit anything. Since we can't (yet) add accessors for 'required' + * classes, this has to be signaled as error. + * FIXME Need to better understand this logic + */ + private def needsProtectedAccessor(sym: Symbol, pos: Position)(implicit ctx: Context): Boolean = { + val clazz = currentClass + val host = hostForAccessorOf(sym, clazz) + val selfType = host.classInfo.selfType + def accessibleThroughSubclassing = + validCurrentOwner && (selfType <:< sym.owner.typeRef) && !clazz.is(Trait) + + val isCandidate = ( + sym.is(Protected) + && sym.is(JavaDefined) + && !sym.effectiveOwner.is(Package) + && !accessibleThroughSubclassing + && (sym.enclosingPackageClass != currentClass.enclosingPackageClass) + && (sym.enclosingPackageClass == sym.accessBoundary(sym.enclosingPackageClass)) + ) + def isSelfType = !(host.typeRef <:< selfType) && { + if (selfType.typeSymbol.is(JavaDefined)) + ctx.restrictionError(s"cannot accesses protected $sym from within $clazz with self type $selfType", pos) + true + } + def isJavaProtected = host.is(Trait) && sym.is(JavaDefined) && { + ctx.restrictionError( + s"""$clazz accesses protected $sym inside a concrete trait method. + |Add an accessor in a class extending ${sym.enclosingClass} as a workaround.""".stripMargin, + pos + ) + true + } + isCandidate && !host.is(Package) && !isSelfType && !isJavaProtected + } + + /** Return the innermost enclosing class C of referencingClass for which either + * of the following holds: + * - C is a subclass of sym.owner or + * - C is declared in the same package as sym's owner + */ + private def hostForAccessorOf(sym: Symbol, referencingClass: ClassSymbol)(implicit ctx: Context): ClassSymbol = + if (referencingClass.derivesFrom(sym.owner) + || referencingClass.classInfo.selfType <:< sym.owner.typeRef + || referencingClass.enclosingPackageClass == sym.owner.enclosingPackageClass) { + assert(referencingClass.isClass, referencingClass) + referencingClass + } + else if(referencingClass.owner.enclosingClass.exists) + hostForAccessorOf(sym, referencingClass.owner.enclosingClass.asClass) + else + referencingClass + + /** Is 'tpe' the type of a member of an enclosing class? */ + private def isThisType(tpe: Type)(implicit ctx: Context): Boolean = tpe match { + case ThisType(cls) => !cls.is(PackageClass) + case tpe: TypeProxy => isThisType(tpe.underlying) + case _ => false + } + } +} diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index d25288548e2d..92aa7240a5dd 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,7 +14,7 @@ class tests extends CompilerTest { "-pagewidth", "160") implicit val defaultOptions = noCheckOptions ++ List( - "-Ycheck:front"//, "-Ystop-before:terminal" + "-Ycheck:super"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") diff --git a/tests/pos/paramAliases.scala b/tests/pos/paramAliases.scala new file mode 100644 index 000000000000..e7787864aee4 --- /dev/null +++ b/tests/pos/paramAliases.scala @@ -0,0 +1,11 @@ +// This tests that the subclass parameter is not +// translated to a field, but is forwarded to the +// superclass parameter. Right now we need to verify +// this by inspecting the output with -Xprint:super +// TODO: Make a test that does this automatically. +class Base(val x: Int) + +class Sub(x: Int) extends Base(x) { + println(x) +} + From f0249f2bdd8aeb349309dc1ea6ed248987c800f9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Jul 2014 14:47:06 +0200 Subject: [PATCH 09/47] Rename SignedType -> MethodicType ... for the common supertype of MethodType, PolyType, and ExprType. Signed was confusing. --- src/dotty/tools/dotc/core/Denotations.scala | 2 +- src/dotty/tools/dotc/core/Types.scala | 8 ++++---- src/dotty/tools/dotc/transform/SuperAccessors.scala | 8 ++++---- src/dotty/tools/dotc/typer/Applications.scala | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 120f8e0f8732..bdc67aa08ce1 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -375,7 +375,7 @@ object Denotations { final def signature(implicit ctx: Context): Signature = { if (isType) Signature.NotAMethod // don't force info if this is a type SymDenotation else info match { - case info: SignedType => + case info: MethodicType => try info.signature catch { // !!! DEBUG case ex: Throwable => diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index a92b252b5ef0..dcb1ae4913eb 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1602,7 +1602,7 @@ object Types { // and therefore two different poly types would never be equal. /** A trait that mixes in functionality for signature caching */ - trait SignedType extends Type { + trait MethodicType extends Type { private[this] var mySignature: Signature = _ private[this] var mySignatureRunId: Int = NoRunId @@ -1610,7 +1610,7 @@ object Types { protected def computeSignature(implicit ctx: Context): Signature protected def resultSignature(implicit ctx: Context) = try resultType match { - case rtp: SignedType => rtp.signature + case rtp: MethodicType => rtp.signature case tp => Signature(tp, isJava = false) } catch { @@ -1628,7 +1628,7 @@ object Types { } } - trait MethodOrPoly extends SignedType + trait MethodOrPoly extends MethodicType abstract case class MethodType(paramNames: List[TermName], paramTypes: List[Type]) (resultTypeExp: MethodType => Type) @@ -1755,7 +1755,7 @@ object Types { } abstract case class ExprType(override val resultType: Type) - extends CachedProxyType with TermType with SignedType { + extends CachedProxyType with TermType with MethodicType { override def underlying(implicit ctx: Context): Type = resultType protected def computeSignature(implicit ctx: Context): Signature = resultSignature def derivedExprType(resultType: Type)(implicit ctx: Context) = diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index fd784862ea91..27378f533d80 100644 --- a/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -78,8 +78,8 @@ class SuperAccessors extends MacroTransform with DenotTransformer { thisTransfor buf += tree } - private def ensureSigned(tpe: Type)(implicit ctx: Context) = tpe match { - case tpe: SignedType => tpe + private def ensureMethodic(tpe: Type)(implicit ctx: Context) = tpe match { + case tpe: MethodicType => tpe case _ => ExprType(tpe) } @@ -93,7 +93,7 @@ class SuperAccessors extends MacroTransform with DenotTransformer { thisTransfor ctx.debuglog(s"add super acc ${sym.showLocated} to $clazz") val acc = ctx.newSymbol( clazz, supername, SuperAccessor | Private | Artifact, - ensureSigned(sel.tpe.widenSingleton), coord = sym.coord).enteredAfter(thisTransformer) + ensureMethodic(sel.tpe.widenSingleton), coord = sym.coord).enteredAfter(thisTransformer) // Diagnostic for SI-7091 if (!accDefs.contains(clazz)) ctx.error(s"Internal error: unable to store accessor definition in ${clazz}. clazz.hasPackageFlag=${clazz is Package}. Accessor required for ${sel} (${sel.show})", sel.pos) @@ -274,7 +274,7 @@ class SuperAccessors extends MacroTransform with DenotTransformer { thisTransfor val alias = inheritedAccessor(sym) if (alias.exists) { def forwarder(implicit ctx: Context) = { - sym.copySymDenotation(initFlags = sym.flags | Method, info = ensureSigned(sym.info)) + sym.copySymDenotation(initFlags = sym.flags | Method, info = ensureMethodic(sym.info)) .installAfter(thisTransformer) val superAcc = Select(Super(This(currentClass), tpnme.EMPTY, inConstrCall = false), alias) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 91f4ce9a5b12..99bb8284475d 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -735,9 +735,9 @@ trait Applications extends Compatibility { self: Typer => onMethod(tp, isApplicable(_, args, resultType)) private def onMethod(tp: Type, p: TermRef => Boolean)(implicit ctx: Context): Boolean = tp match { - case methRef: TermRef if methRef.widenSingleton.isInstanceOf[SignedType] => + case methRef: TermRef if methRef.widenSingleton.isInstanceOf[MethodicType] => p(methRef) - case mt: SignedType => + case mt: MethodicType => p(mt.narrow) case _ => tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) From 968d16c289aedc7532dae9225502789b507e22e3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jul 2014 18:51:50 +0200 Subject: [PATCH 10/47] Fixed problem with installAfter The problem is that when an installAfter completely replaces a previous denotation, a symbol or NamedType might still hang on to that denotation in the cache. We need to enforce that we switch to the new denotation. This is done by setting the replaced denotation's validFor field to Nowhere. --- src/dotty/tools/dotc/core/Denotations.scala | 32 +++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index bdc67aa08ce1..82ee6560da6a 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -512,7 +512,13 @@ object Denotations { def current(implicit ctx: Context): SingleDenotation = { val currentPeriod = ctx.period val valid = myValidFor - assert(valid.code > 0) + if (valid.code <= 0) { + // can happen if we sit on a stale denotation which has been replaced + // wholesale by an installAfter; in this case, proceed to the next + // denotation and try again. + if (validFor == Nowhere && nextInRun.validFor != Nowhere) return nextInRun.current + assert(false) + } if (valid.runId != currentPeriod.runId) bringForward.current else { @@ -551,6 +557,7 @@ object Denotations { cur = next } cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId) + //printPeriods(cur) //println(s"new denot: $cur, valid for ${cur.validFor}") } cur.current // multiple transformations could be required @@ -563,7 +570,11 @@ object Denotations { //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") cur = cur.nextInRun cnt += 1 - assert(cnt <= MaxPossiblePhaseId, s"seems to be a loop in Denotations for $this, currentPeriod = $currentPeriod") + if (cnt > MaxPossiblePhaseId) { + println(s"seems to be a loop in Denotations for $this, currentPeriod = $currentPeriod") + printPeriods(this) + assert(false) + } } cur } @@ -571,6 +582,19 @@ object Denotations { } } + private def printPeriods(current: SingleDenotation): Unit = { + print(s"periods for $this:") + var cur = current + var cnt = 0 + do { + print(" " + cur.validFor) + cur = cur.nextInRun + cnt += 1 + if (cnt > MaxPossiblePhaseId) { println(" ..."); return } + } while (cur ne current) + println() + } + /** Install this denotation to be the result of the given denotation transformer. * This is the implementation of the same-named method in SymDenotations. * It's placed here because it needs access to private fields of SingleDenotation. @@ -581,6 +605,8 @@ object Denotations { 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) { @@ -588,12 +614,14 @@ object Denotations { 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) = { From a47b8b4d3c22133e8cde0053701057e56cc71acb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jul 2014 18:54:46 +0200 Subject: [PATCH 11/47] Removed test case d.T is an access to a structural type member, so rejecting this is OK. Not sure why we compiled this before without warning. --- tests/pos/i39.scala | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 tests/pos/i39.scala diff --git a/tests/pos/i39.scala b/tests/pos/i39.scala deleted file mode 100644 index 5cbaee35d818..000000000000 --- a/tests/pos/i39.scala +++ /dev/null @@ -1,17 +0,0 @@ -object i39 { - - trait B { - type D <: { type T } - def d: D - } - - val bc: B = new B { - def d: D = ??? - } - - val d: bc.D = bc.d - - // infinite loop in Typer - val asT: d.T = ??? - -} From efe4f7e43652a303d16a5253f84316e547f45cca Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jul 2014 18:57:22 +0200 Subject: [PATCH 12/47] Changed PostTyperTransformer scheme 1) We now always generate companion objects for classes. This is done in mini-phase "companions", which also assures that companion-modules appear after companion-classes. 2) PostTyperTransformers is gone; the part which normalizes trees has been rolled into TreeTransform and the part which reordered companion classes and modules is now in Companions. Note: Some tests were deisabled; should be re-enabled by Dmitry where needed. --- src/dotty/tools/dotc/Compiler.scala | 5 +- src/dotty/tools/dotc/core/Phases.scala | 18 ++--- .../tools/dotc/transform/Companions.scala | 67 +++++++++++++++++++ .../transform/CreateCompanionObjects.scala | 53 --------------- src/dotty/tools/dotc/transform/LazyVals.scala | 21 +----- .../transform/PostTyperTransformers.scala | 62 ----------------- .../tools/dotc/transform/TreeTransform.scala | 6 +- .../CreateCompanionObjectsTest.scala | 6 +- test/test/transform/LazyValsTest.scala | 4 +- .../transform/PostTyperTransformerTest.scala | 4 +- 10 files changed, 87 insertions(+), 159 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/Companions.scala delete mode 100644 src/dotty/tools/dotc/transform/CreateCompanionObjects.scala delete mode 100644 src/dotty/tools/dotc/transform/PostTyperTransformers.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 55452d6ff28c..bf8cf41823cb 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -11,7 +11,6 @@ import reporting.ConsoleReporter import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.transform._ import dotty.tools.dotc.transform.TreeTransforms.{TreeTransform, TreeTransformer} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer import dotty.tools.dotc.core.DenotTransformers.DenotTransformer import dotty.tools.dotc.core.Denotations.SingleDenotation @@ -20,9 +19,9 @@ class Compiler { def phases: List[List[Phase]] = List( List(new FrontEnd), + List(new Companions), List(new SuperAccessors), - List(new LazyValsCreateCompanionObjects, - new TailRec), //force separataion between lazyVals and LVCreateCO + List(new TailRec), List(new PatternMatcher, new LazyValTranformContext().transformer, new Splitter), diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 7bc5f3052c89..aabde4cf9fb0 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -9,7 +9,6 @@ import Denotations._ import config.Printers._ import scala.collection.mutable.{ListBuffer, ArrayBuffer} import dotty.tools.dotc.transform.TreeTransforms.{TreeTransformer, TreeTransform} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer import dotty.tools.dotc.transform.TreeTransforms import TreeTransforms.Separator import Periods._ @@ -72,12 +71,10 @@ object Phases { /** Squash TreeTransform's beloning to same sublist to a single TreeTransformer * Each TreeTransform gets own period, * whereas a combined TreeTransformer gets period equal to union of periods of it's TreeTransforms - * first TreeTransformer emitted is PostTyperTransformer that simplifies trees, see it's documentation */ private def squashPhases(phasess: List[List[Phase]]): Array[Phase] = { val squashedPhases = ListBuffer[Phase]() var prevPhases: Set[String] = Set.empty - var postTyperEmmited = false var i = 0 while (i < phasess.length) { if (phasess(i).length > 1) { @@ -95,17 +92,10 @@ object Phases { } } val transforms = phasess(i).asInstanceOf[List[TreeTransform]] - val block = - if (!postTyperEmmited) { - postTyperEmmited = true - new PostTyperTransformer { - override def name: String = transformations.map(_.name).mkString("TreeTransform:{", ", ", "}") - override def transformations: Array[TreeTransform] = transforms.toArray - } - } else new TreeTransformer { - override def name: String = transformations.map(_.name).mkString("TreeTransform:{", ", ", "}") - override def transformations: Array[TreeTransform] = transforms.toArray - } + val block = new TreeTransformer { + override def name: String = transformations.map(_.name).mkString("TreeTransform:{", ", ", "}") + override def transformations: Array[TreeTransform] = transforms.toArray + } squashedPhases += block prevPhases ++= phasess(i).map(_.name) block.init(this, phasess(i).head.id, phasess(i).last.id) diff --git a/src/dotty/tools/dotc/transform/Companions.scala b/src/dotty/tools/dotc/transform/Companions.scala new file mode 100644 index 000000000000..0e31b511d43b --- /dev/null +++ b/src/dotty/tools/dotc/transform/Companions.scala @@ -0,0 +1,67 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import ast.Trees.flatten +import Flags._ +import Contexts.Context +import Symbols._ +import scala.collection.mutable +import DenotTransformers._ +import Names.Name +import NameOps._ + + +/** A transformer that provides a convenient way to create companion objects + */ +class Companions extends TreeTransform with IdentityDenotTransformer { thisTransformer => + import ast.tpd._ + + override def name = "companions" + + /** Reorder statements so that module classes always come after their companion classes, add missing companion classes */ + private def reorderAndComplete(stats: List[Tree])(implicit ctx: Context): List[Tree] = { + val moduleClassDefs, singleClassDefs = mutable.Map[Name, Tree]() + + def reorder(stats: List[Tree]): List[Tree] = stats match { + case (stat: TypeDef) :: stats1 if stat.symbol.isClass => + if (stat.symbol is Flags.Module) { + moduleClassDefs += (stat.name -> stat) + singleClassDefs -= stat.name.stripModuleClassSuffix + val stats1r = reorder(stats1) + if (moduleClassDefs contains stat.name) stat :: stats1r else stats1r + } else { + def stats1r = reorder(stats1) + val normalized = moduleClassDefs remove stat.name.moduleClassName match { + case Some(mcdef) => + mcdef :: stats1r + case None => + singleClassDefs += (stat.name -> stat) + stats1r + } + stat :: normalized + } + case stat :: stats1 => stat :: reorder(stats1) + case Nil => Nil + } + + def newCompanion(name: TermName): Thicket = { + val modul = ctx.newCompleteModuleSymbol(ctx.owner, name, Synthetic, Synthetic, + defn.ObjectClass.typeRef :: Nil, Scopes.newScope) + if (ctx.owner.isClass) modul.enteredAfter(thisTransformer) + ModuleDef(modul, Nil) + } + + def addMissingCompanions(stats: List[Tree]): List[Tree] = stats map { + case stat: TypeDef if singleClassDefs contains stat.name => + Thicket(stat :: newCompanion(stat.name.toTermName).trees) + case stat => stat + } + addMissingCompanions(reorder(stats)) + } + + override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = + ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisTransformer.next))) +} diff --git a/src/dotty/tools/dotc/transform/CreateCompanionObjects.scala b/src/dotty/tools/dotc/transform/CreateCompanionObjects.scala deleted file mode 100644 index b1cc8ea529f1..000000000000 --- a/src/dotty/tools/dotc/transform/CreateCompanionObjects.scala +++ /dev/null @@ -1,53 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import scala.collection.mutable.ListBuffer -import dotty.tools.dotc.core.{Scopes, Flags} -import dotty.tools.dotc.core.Symbols.NoSymbol -import scala.annotation.tailrec -import dotty.tools.dotc.core._ -import Symbols._ -import scala.Some -import dotty.tools.dotc.transform.TreeTransforms.{NXTransformations, TransformerInfo, TreeTransform, TreeTransformer} -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import scala.collection.mutable -import dotty.tools.dotc.core.Names.Name -import NameOps._ - -/** A transformer that provides a convenient way to create companion objects - */ -abstract class CreateCompanionObjects extends TreeTransform { - - import tpd._ - - /** Given class definition should return true if companion object creation should be enforced - */ - def predicate(cls: TypeDef)(implicit ctx: Context): Boolean - - override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = { - @tailrec - def transformStats0(trees: List[Tree], acc: ListBuffer[Tree]): List[Tree] = { - trees match { - case Nil => acc.toList - case (claz: TypeDef) :: stats if claz.symbol.isClass && !(claz.symbol is Flags.Module) => { - val moduleExists = !(claz.symbol.companionModule eq NoSymbol) - if (moduleExists || !predicate(claz)) transformStats0(stats, acc += claz) - else { - val moduleSymbol = ctx.newCompleteModuleSymbol(claz.symbol.owner, claz.name.toTermName, Flags.Synthetic, Flags.Synthetic, List(defn.ObjectClass.typeRef), Scopes.newScope) - if (moduleSymbol.owner.isClass) moduleSymbol.entered - val companion = tpd.ModuleDef(moduleSymbol, List(EmptyTree)).withPos(claz.pos) - acc += claz - acc += companion - transformStats0(stats, acc) - } - } - case stat :: stats => transformStats0(stats, acc += stat) - } - } - - transformStats0(trees, ListBuffer()) - } -} diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index fe6c3e2e4e2b..ecd94a211534 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -19,25 +19,6 @@ import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.SymDenotations.SymDenotation import dotty.tools.dotc.core.DenotTransformers.DenotTransformer - -class LazyValsCreateCompanionObjects extends CreateCompanionObjects { - import tpd._ - - - override def name: String = "lazyValsModules" - - /** Companion classes are required to hold offsets for volatile lazy vals */ - override def predicate(forClass: tpd.TypeDef)(implicit ctx: Context): Boolean = { - (!(forClass.symbol is Flags.Module)) && forClass.rhs.isInstanceOf[Template] && { - val body = forClass.rhs.asInstanceOf[Template].body - body.exists { - case x: ValDef => - (x.mods is Flags.Lazy) && x.symbol.hasAnnotation(defn.VolatileAnnot) - case _ => false - } - } - } -} class LazyValTranformContext { import tpd._ @@ -67,7 +48,7 @@ class LazyValTranformContext { /** List of names of phases that should have finished their processing of all compilation units * before this phase starts */ - override def runsAfterGroupsOf: Set[String] = Set("lazyValsModules") + /** List of names of phases that should have finished their processing of all compilation units * before this phase starts */ diff --git a/src/dotty/tools/dotc/transform/PostTyperTransformers.scala b/src/dotty/tools/dotc/transform/PostTyperTransformers.scala deleted file mode 100644 index 25f122cf58c4..000000000000 --- a/src/dotty/tools/dotc/transform/PostTyperTransformers.scala +++ /dev/null @@ -1,62 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.core._ -import Symbols._ -import scala.Some -import dotty.tools.dotc.transform.TreeTransforms.{NXTransformations, TransformerInfo, TreeTransform, TreeTransformer} -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import scala.collection.mutable -import dotty.tools.dotc.core.Names.Name -import NameOps._ - -object PostTyperTransformers { - - import tpd._ - - - /** A trait that's assumed by the transformers that run right after typer. - * Ensures that trees are normalized when seen by other transforms. This means: - * (1) All module class definitions appear after their companion class definitions - * (2) There are no import clauses or named arguments - * (3) All trees designating types are instances of TypeTree - */ - abstract class PostTyperTransformer extends TreeTransformer { - - /** Reorder statements so that module classes always come after their companion classes, add missing companion classes */ - def reorder(stats: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = { - val moduleClassDefs = mutable.Map[Name, Tree]() - def reorder0(stats: List[Tree]): List[Tree] = { - stats match { - case (stat: TypeDef) :: stats1 if stat.symbol.isClass => - if (stat.symbol is Flags.Module) { - moduleClassDefs += (stat.name -> stat) - val stats1r = reorder0(stats1) - if (moduleClassDefs contains stat.name) stat :: stats1r else stats1r - } - else { - val mclsName = stat.name.moduleClassName - moduleClassDefs remove mclsName match { - case Some(mcdef) => stat :: mcdef :: reorder0(stats1) - case None => stat :: reorder0(stats1) - } - } - case stat :: stats1 => stat :: reorder0(stats1) - case Nil => Nil - } - } - reorder0(stats) - } - - override def transformStats(trees: List[tpd.Tree], exprOwner: Symbol, info: TransformerInfo, current: Int)(implicit ctx: Context): List[tpd.Tree] = - super.transformStats(reorder(trees)(ctx, info), exprOwner, info, current) - - override def transform(tree: tpd.Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): tpd.Tree = tree match { - case tree: Import => EmptyTree - case tree: NamedArg => super.transform(tree.arg, info, cur) - case tree: TypeTree => super.transform(tree, info, cur) - case tree => super.transform(if (tree.isType) TypeTree(tree.tpe) else tree, info, cur) - } - } - -} \ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 347762678d81..2bc7334654e1 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -1124,8 +1124,12 @@ object TreeTransforms { val stats = transformStats(tree.stats, tree.symbol, mutatedInfo, cur)(nestedCtx) goPackageDef(cpy.PackageDef(tree, pid, stats), mutatedInfo.nx.nxTransPackageDef(cur)) } + case tree: Import => EmptyTree + case tree: NamedArg => transform(tree.arg, info, cur) case Thicket(trees) => cpy.Thicket(tree, transformTrees(trees, info, cur)) - case tree => tree + case tree => + if (tree.isType) transform(TypeTree(tree.tpe).withPos(tree.pos), info, cur) + else tree } def transform(tree: Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show} at ${ctx.phase}", transforms, show = true) { diff --git a/test/test/transform/CreateCompanionObjectsTest.scala b/test/test/transform/CreateCompanionObjectsTest.scala index 05f4e1062fd1..82830781c90b 100644 --- a/test/test/transform/CreateCompanionObjectsTest.scala +++ b/test/test/transform/CreateCompanionObjectsTest.scala @@ -14,14 +14,15 @@ import Types._ import Decorators._ import Trees._ import dotty.tools.dotc.transform.TreeTransforms.{TreeTransform, TreeTransformer} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer -import dotty.tools.dotc.transform.CreateCompanionObjects class CreateCompanionObjectsTest extends DottyTest { + /* FIXME: re-enable after adapting to new scheme import tpd._ + type PostTyperTransformer = TreeTransformer // FIXME do without + @Test def shouldCreateNonExistingObjectsInPackage = checkCompile("frontend", "class A{} ") { (tree, context) => @@ -123,4 +124,5 @@ class CreateCompanionObjectsTest extends DottyTest { classPos < modulePos && (notCreatedPos < 0) ) } + */ } diff --git a/test/test/transform/LazyValsTest.scala b/test/test/transform/LazyValsTest.scala index aee6cbb9cb4e..5b8a659fa96f 100644 --- a/test/test/transform/LazyValsTest.scala +++ b/test/test/transform/LazyValsTest.scala @@ -5,7 +5,7 @@ import test.DottyTest import org.junit.Assert class LazyValsTest extends DottyTest { - + /* FIXME: re-enable after adapting to new scheme @Test def doNotRewriteObjects = { checkCompile("LazyVals", "object O"){ (tree, ctx) => @@ -356,5 +356,5 @@ class LazyValsTest extends DottyTest { Assert.assertTrue("volatile field lazy ref rewritten to class creation", treeS.contains(moduleField) && treeS.contains(reuseFieldPattern)) } - } + }*/ } diff --git a/test/test/transform/PostTyperTransformerTest.scala b/test/test/transform/PostTyperTransformerTest.scala index 0c3f222c7566..9886c30232f1 100644 --- a/test/test/transform/PostTyperTransformerTest.scala +++ b/test/test/transform/PostTyperTransformerTest.scala @@ -14,9 +14,9 @@ import Types._ import Decorators._ import Trees._ import dotty.tools.dotc.transform.TreeTransforms.{TreeTransform, TreeTransformer} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer class PostTyperTransformerTest extends DottyTest { + /* FIXME: re-enable after adapting to new scheme @Test def shouldStripImports = checkCompile("frontend", "class A{ import scala.collection.mutable._; val d = 1}") { @@ -128,5 +128,5 @@ class PostTyperTransformerTest extends DottyTest { Assert.assertTrue("should reorder existing objects in template", classPos < modulePos ) - } + }*/ } From db88bf06958e33ae415ca227808ab1f3e48fed7f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jul 2014 19:02:26 +0200 Subject: [PATCH 13/47] Various cleanups and utility additions - Some new functionality in tpd and in Symbols. - Added `sm` interpolator to print nicely. - Make use of nestedMap where possible. - Add IdentityDenotTransformer as a convencience class --- src/dotty/tools/dotc/ast/tpd.scala | 14 +++++-- src/dotty/tools/dotc/core/Decorators.scala | 36 +++++++++++++++++- .../tools/dotc/core/DenotTransformers.scala | 8 ++++ .../tools/dotc/core/SymDenotations.scala | 38 +++++++++++++++++-- src/dotty/tools/dotc/core/SymbolLoaders.scala | 6 +-- src/dotty/tools/dotc/core/Symbols.scala | 6 +-- .../dotc/core/pickling/ClassfileParser.scala | 16 ++++---- .../tools/dotc/core/pickling/UnPickler.scala | 12 +++--- .../tools/dotc/transform/SuperAccessors.scala | 17 ++++----- .../tools/dotc/typer/ErrorReporting.scala | 2 +- src/dotty/tools/dotc/typer/ImportInfo.scala | 2 +- 11 files changed, 116 insertions(+), 41 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 84e1118f947e..f0151e1d3ea0 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -204,11 +204,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } val (vparamss, rtp) = valueParamss(mtp) val targs = tparams map (_.typeRef) - val argss = vparamss map (_ map (vparam => Ident(vparam.termRef))) + val argss = vparamss.nestedMap(vparam => Ident(vparam.termRef)) ta.assignType( untpd.DefDef( Modifiers(sym), sym.name, tparams map TypeDef, - vparamss map (_ map (ValDef(_))), TypeTree(rtp), rhsFn(targs)(argss)), sym) + vparamss.nestedMap(ValDef(_)), TypeTree(rtp), rhsFn(targs)(argss)), sym) } def TypeDef(sym: TypeSymbol)(implicit ctx: Context): TypeDef = @@ -384,6 +384,14 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def changeOwner(from: Symbol, to: Symbol)(implicit ctx: Context): ThisTree = new TreeTypeMap(ownerMap = (sym => if (sym == from) to else sym)).apply(tree) + def appliedToArg(arg: Tree)(implicit ctx: Context): Tree = appliedToArgs(arg :: Nil) + + def appliedToArgs(args: List[Tree])(implicit ctx: Context): Tree = + if (args.isEmpty) tree else Apply(tree, args) + + def appliedToArgss(argss: List[List[Tree]])(implicit ctx: Context): Tree = + ((tree: Tree) /: argss)(Apply(_, _)) + def appliedToTypes(targs: List[Type])(implicit ctx: Context): Tree = appliedToTypeTrees(targs map (TypeTree(_))) @@ -395,7 +403,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def tpes: List[Type] = xs map (_.tpe) } - class TreeTypeMap(val typeMap: TypeMap = IdentityTypeMap, val ownerMap: Symbol => Symbol = identity _)(implicit ctx: Context) extends TreeMap { + class TreeTypeMap(val typeMap: Type => Type = IdentityTypeMap, val ownerMap: Symbol => Symbol = identity _)(implicit ctx: Context) extends TreeMap { override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = super.transform { tree.withType(typeMap(tree.tpe)) match { case bind: tpd.Bind => diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 155ea87e0e61..cd7b4689689f 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -143,7 +143,7 @@ object Decorators { * 2) Lists can be formatted using the desired separator between two `%` signs, * eg `i"myList = (${myList}%, %)"` */ - implicit class InfoString(val sc: StringContext) extends AnyVal { + implicit class StringInterpolators(val sc: StringContext) extends AnyVal { def i(args: Any*)(implicit ctx: Context): String = { @@ -166,7 +166,39 @@ object Decorators { val (args1, suffixes1) = (args, suffixes).zipped.map(treatArg(_, _)).unzip new StringContext(prefix :: suffixes1.toList: _*).s(args1: _*) } - } + /** Lifted from scala.reflect.internal.util + * A safe combination of [[scala.collection.immutable.StringLike#stripMargin]] + * and [[scala.StringContext#raw]]. + * + * The margin of each line is defined by whitespace leading up to a '|' character. + * This margin is stripped '''before''' the arguments are interpolated into to string. + * + * String escape sequences are '''not''' processed; this interpolater is designed to + * be used with triple quoted Strings. + * + * {{{ + * scala> val foo = "f|o|o" + * foo: String = f|o|o + * scala> sm"""|${foo} + * |""" + * res0: String = + * "f|o|o + * " + * }}} + */ + final def sm(args: Any*): String = { + def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak + def stripTrailingPart(s: String) = { + val (pre, post) = s.span(c => !isLineBreak(c)) + pre + post.stripMargin + } + val stripped: List[String] = sc.parts.toList match { + case head :: tail => head.stripMargin :: (tail map stripTrailingPart) + case Nil => Nil + } + new StringContext(stripped: _*).raw(args: _*) + } + } } diff --git a/src/dotty/tools/dotc/core/DenotTransformers.scala b/src/dotty/tools/dotc/core/DenotTransformers.scala index 6daa028fc4a1..e052a07ea470 100644 --- a/src/dotty/tools/dotc/core/DenotTransformers.scala +++ b/src/dotty/tools/dotc/core/DenotTransformers.scala @@ -46,4 +46,12 @@ object DenotTransformers { } } } + + /** A `DenotTransformer` trait that has the identity as its `transform` method. + * You might want to inherit from this trait so that new denotations can be + * installed using `installAfter` and `enteredAfter` at the end of the phase. + */ + trait IdentityDenotTransformer extends DenotTransformer { + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref + } } diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 6432370389af..ba57909a0d75 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -212,6 +212,14 @@ object SymDenotations { final def addAnnotation(annot: Annotation): Unit = annotations = annot :: myAnnotations + /** Remove annotation with given class from this denotation */ + final def removeAnnotation(cls: Symbol)(implicit ctx: Context): Unit = + annotations = myAnnotations.filterNot(_ matches cls) + + /** Copy all annotations from given symbol by adding them to this symbol */ + final def addAnnotations(from: Symbol)(implicit ctx: Context): Unit = + from.annotations.foreach(addAnnotation) + @tailrec private def dropOtherAnnotations(anns: List[Annotation], cls: Symbol)(implicit ctx: Context): List[Annotation] = anns match { case ann :: rest => if (ann matches cls) anns else dropOtherAnnotations(rest, cls) @@ -861,6 +869,22 @@ object SymDenotations { /** Install this denotation as the result of the given denotation transformer. */ override def installAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = super.installAfter(phase) + + /** Remove private modifier from symbol's definition. If this symbol + * is not a constructor nor a static module, rename it by expanding its name to avoid name clashes + * @param base the fully qualified name of this class will be appended if name expansion is needed + */ + final def makeNotPrivateAfter(base: Symbol, phase: DenotTransformer)(implicit ctx: Context): Unit = { + if (this.is(Private)) { + val newName = + if (this.is(Module) && isStatic || isClassConstructor) name + else { + if (this.is(Module)) moduleClass.makeNotPrivateAfter(base, phase) + name.expandedName(base) + } + copySymDenotation(name = newName, initFlags = flags &~ Private).installAfter(phase) + } + } } /** The contents of a class definition during a period @@ -1311,9 +1335,17 @@ object SymDenotations { decls.denotsNamed(cname).first.symbol } - def underlyingOfValueClass: Type = ??? - - def valueClassUnbox: Symbol = ??? + /** The member that of a derived value class that unboxes it. */ + def valueClassUnbox(implicit ctx: Context): Symbol = + // (info.decl(nme.unbox)).orElse(...) uncomment once we accept unbox methods + classInfo.decls + .find(d => d.isTerm && d.symbol.is(ParamAccessor)) + .map(_.symbol) + .getOrElse(NoSymbol) + + /** The unboxed type that underlies a derived value class */ + def underlyingOfValueClass(implicit ctx: Context): Type = + valueClassUnbox.info.resultType /** 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`. diff --git a/src/dotty/tools/dotc/core/SymbolLoaders.scala b/src/dotty/tools/dotc/core/SymbolLoaders.scala index bebad60cc395..886c728b7272 100644 --- a/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -12,7 +12,7 @@ import scala.compat.Platform.currentTime import dotty.tools.io.{ ClassPath, AbstractFile } import Contexts._, Symbols._, Flags._, SymDenotations._, Types._, Scopes._, util.Positions._, Names._ import StdNames._, NameOps._ -import Decorators.StringDecorator +import Decorators.{StringDecorator, StringInterpolators} import pickling.ClassfileParser object SymbolLoaders { @@ -69,8 +69,8 @@ class SymbolLoaders { // require yjp.jar at runtime. See SI-2089. if (ctx.settings.termConflict.isDefault) throw new TypeError( - s"""$owner contains object and package with same name: $pname - |one of them needs to be removed from classpath""".stripMargin) + sm"""$owner contains object and package with same name: $pname + |one of them needs to be removed from classpath""") else if (ctx.settings.termConflict.value == "package") { ctx.warning( s"Resolving package/object name conflict in favor of package ${preExisting.fullName}. The object will be inaccessible.") diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index cfd5bdf231f4..6421018e5274 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -261,16 +261,14 @@ trait Symbols { this: Context => newSymbol(owner, name, SyntheticArtifact, if (name.isTypeName) TypeAlias(ErrorType) else ErrorType) - type OwnerMap = Symbol => Symbol - /** Map given symbols, subjecting all types to given type map and owner map. * Cross symbol references are brought over from originals to copies. * Do not copy any symbols if all attributes of all symbols stay the same. */ def mapSymbols( originals: List[Symbol], - typeMap: TypeMap = IdentityTypeMap, - ownerMap: OwnerMap = identity) + typeMap: Type => Type = IdentityTypeMap, + ownerMap: Symbol => Symbol = identity) = if (originals forall (sym => (typeMap(sym.info) eq sym.info) && (ownerMap(sym.owner) eq sym.owner))) diff --git a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala index 0f074759759f..f2b81072ae75 100644 --- a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala @@ -48,8 +48,8 @@ class ClassfileParser( case e: RuntimeException => if (ctx.debug) e.printStackTrace() throw new IOException( - s"""class file $classfile is broken, reading aborted with $e.getClass - |${Option(e.getMessage).getOrElse("")}""".stripMargin) + sm"""class file $classfile is broken, reading aborted with $e.getClass + |${Option(e.getMessage).getOrElse("")}""") } private def parseHeader(): Unit = { @@ -702,12 +702,12 @@ class ClassfileParser( getMember(owner, innerName.toTypeName) } assert(result ne NoSymbol, - s"""failure to resolve inner class: - |externalName = $externalName, - |outerName = $outerName, - |innerName = $innerName - |owner.fullName = owner.showFullName - |while parsing ${classfile}""".stripMargin) + sm"""failure to resolve inner class: + |externalName = $externalName, + |outerName = $outerName, + |innerName = $innerName + |owner.fullName = owner.showFullName + |while parsing ${classfile}""") result case None => diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index dd26b20df99f..462c1013713b 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -53,10 +53,10 @@ object UnPickler { val result = restpe.parameterizeWith(tparams) for ((msg, pos) <- err) ctx.warning( - s"""$msg - |originally parsed type : ${tp.show} - |will be approximated by: ${result.show}. - |Proceed at own risk.""".stripMargin) + sm"""$msg + |originally parsed type : ${tp.show} + |will be approximated by: ${result.show}. + |Proceed at own risk.""") result } else @@ -161,8 +161,8 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: protected def errorBadSignature(msg: String, original: Option[RuntimeException] = None)(implicit ctx: Context) = { val ex = new BadSignature( - s"""error reading Scala signature of $classRoot from $source: - |error occured at position $readIndex: $msg""".stripMargin) + sm"""error reading Scala signature of $classRoot from $source: + |error occured at position $readIndex: $msg""") /*if (debug)*/ original.getOrElse(ex).printStackTrace() // !!! DEBUG throw ex } diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index 27378f533d80..d6af963019a1 100644 --- a/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -48,7 +48,7 @@ import Symbols._ * * TODO: Rename phase to "Accessors" because it handles more than just super accessors */ -class SuperAccessors extends MacroTransform with DenotTransformer { thisTransformer => +class SuperAccessors extends MacroTransform with IdentityDenotTransformer { thisTransformer => import tpd._ @@ -58,9 +58,6 @@ class SuperAccessors extends MacroTransform with DenotTransformer { thisTransfor protected def newTransformer(implicit ctx: Context): Transformer = new SuperAccTransformer - /** No transformation here, but new denotations are installed by the tree traversal */ - def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref - class SuperAccTransformer extends Transformer { /** validCurrentOwner arrives undocumented, but I reverse engineer it to be @@ -444,10 +441,10 @@ class SuperAccessors extends MacroTransform with DenotTransformer { thisTransfor val protectedAccessor = clazz.info.decl(accName).suchThat(_.signature == accType.signature).symbol orElse { val newAcc = ctx.newSymbol( clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) - val code = polyDefDef(newAcc, targs => argss => { - val (receiver :: _) :: tail = argss - val base = Select(receiver, sym).appliedToTypes(targs) - (base /: argss)(Apply(_, _)) + val code = polyDefDef(newAcc, trefs => vrefss => { + val (receiver :: _) :: tail = vrefss + val base = Select(receiver, sym).appliedToTypes(trefs) + (base /: vrefss)(Apply(_, _)) }) ctx.debuglog("created protected accessor: " + code) storeAccessorDefinition(clazz, code) @@ -474,8 +471,8 @@ class SuperAccessors extends MacroTransform with DenotTransformer { thisTransfor val protectedAccessor = clazz.info.decl(accName).symbol orElse { val newAcc = ctx.newSymbol( clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) - val code = DefDef(newAcc, argss => { - val (receiver :: value :: Nil) :: Nil = argss + val code = DefDef(newAcc, vrefss => { + val (receiver :: value :: Nil) :: Nil = vrefss Assign(Select(receiver, field), value).withPos(tree.pos) }) ctx.debuglog("created protected setter: " + code) diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 0b016769b25b..89e73013f573 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -127,7 +127,7 @@ object ErrorReporting { case _ => true } - val s = new InfoString(sc).i(args : _*) + val s = new StringInterpolators(sc).i(args : _*) if (args.forall(isSensical(_))) s else nonSensicalStartTag + s + nonSensicalEndTag } } diff --git a/src/dotty/tools/dotc/typer/ImportInfo.scala b/src/dotty/tools/dotc/typer/ImportInfo.scala index 9fbd07102f83..18e5db209ce3 100644 --- a/src/dotty/tools/dotc/typer/ImportInfo.scala +++ b/src/dotty/tools/dotc/typer/ImportInfo.scala @@ -7,7 +7,7 @@ import ast.Trees._ import core._ import util.SimpleMap import Symbols._, Names._, Denotations._, Types._, Contexts._, StdNames._, Flags._ -import Decorators.InfoString +import Decorators.StringInterpolators object ImportInfo { /** The import info for a root import from given symbol `sym` */ From 357003062f994ac8a8ed985248e749297093185f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jul 2014 19:04:17 +0200 Subject: [PATCH 14/47] ExtensionMethods phase and TypeUtils New phase for extension methods. Also, split off some type handling functionality that can be used elsewhere in new TypeUtils decorator. The idea is that TypeUtils should contain methods on Type that make sense specifically for transformations. That way, we can keep Types from growing. Might make sense to do similar decorators for Denotations as well. There's a bug fix in MacroTransform: Need to treat selfInfo varDels specially, since they have no symbol. --- src/dotty/tools/dotc/Compiler.scala | 1 + .../dotc/transform/ExtensionMethods.scala | 170 ++++++++++++++++++ .../tools/dotc/transform/MacroTransform.scala | 7 +- .../tools/dotc/transform/TypeUtils.scala | 115 ++++++++++++ test/dotc/tests.scala | 3 +- 5 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/ExtensionMethods.scala create mode 100644 src/dotty/tools/dotc/transform/TypeUtils.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index bf8cf41823cb..d202f3a529e7 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -21,6 +21,7 @@ class Compiler { List(new FrontEnd), List(new Companions), List(new SuperAccessors), + List(new ExtensionMethods), List(new TailRec), List(new PatternMatcher, new LazyValTranformContext().transformer, diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala new file mode 100644 index 000000000000..55b53ef4d475 --- /dev/null +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -0,0 +1,170 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Martin Odersky + */ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import dotty.tools.dotc.ast.{Trees, tpd} +import scala.collection.{ mutable, immutable } +import mutable.ListBuffer +import scala.annotation.tailrec +import core._ +import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import TypeUtils._ +import util.Positions._ +import Decorators._ + +/** + * Perform Step 1 in the inline classes SIP: Creates extension methods for all + * methods in a value class, except parameter or super accessors, or constructors. + */ +class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { thisTransformer => + + import tpd._ + + /** the following two members override abstract members in Transform */ + val name: String = "extmethods" + + def newTransformer(implicit ctx: Context): Transformer = new Extender + + /** Generate stream of possible names for the extension version of given instance method `imeth`. + * If the method is not overloaded, this stream consists of just "extension$imeth". + * If the method is overloaded, the stream has as first element "extensionX$imeth", where X is the + * index of imeth in the sequence of overloaded alternatives with the same name. This choice will + * always be picked as the name of the generated extension method. + * After this first choice, all other possible indices in the range of 0 until the number + * of overloaded alternatives are returned. The secondary choices are used to find a matching method + * in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity + * of how overloaded types are ordered between phases and picklings. + */ + private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = { + val decl = imeth.owner.info.decl(imeth.name) + + /** No longer needed for Dotty, as we are more disciplined with scopes now. + // Bridge generation is done at phase `erasure`, but new scopes are only generated + // for the phase after that. So bridges are visible in earlier phases. + // + // `info.member(imeth.name)` filters these out, but we need to use `decl` + // to restrict ourselves to members defined in the current class, so we + // must do the filtering here. + val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe + */ + decl match { + case decl: SingleDenotation => + val alts = decl.alternatives + val index = alts indexOf imeth + assert(index >= 0, alts+" does not contain "+imeth) + def altName(index: Int) = (imeth.name+"$extension"+index).toTermName + altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName) + case tpe => + assert(tpe != NoType, imeth.name+" not found in "+imeth.owner+"'s decls: "+imeth.owner.info.decls) + Stream((imeth.name+"$extension").toTermName) + } + } + + /** Return the extension method that corresponds to given instance method `meth`. */ + def extensionMethod(imeth: Symbol)(implicit ctx: Context): Symbol = + ctx.atPhase(thisTransformer.next) { implicit ctx => + // FIXME use toStatic instead? + val companionInfo = imeth.owner.companionModule.info + val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists) + val matching = candidates filter (alt => alt.info.toDynamic(imeth.owner) matches imeth.info) + assert(matching.nonEmpty, + sm"""|no extension method found for: + | + | $imeth:${imeth.info} + | + | Candidates: + | + | ${candidates.map(c => c.name + ":" + c.info).mkString("\n")} + | + | Candidates (signatures normalized): + | + | ${candidates.map(c => c.name + ":" + c.info.toDynamic(imeth.owner)).mkString("\n")} + | + | Eligible Names: ${extensionNames(imeth).mkString(",")}""") + matching.head + } + + class Extender extends Transformer { + private val extensionDefs = mutable.Map[Symbol, mutable.ListBuffer[Tree]]() + + def checkNonCyclic(pos: Position, seen: Set[Symbol], clazz: ClassSymbol)(implicit ctx: Context): Unit = + if (seen contains clazz) + ctx.error("value class may not unbox to itself", pos) + else { + val unboxed = clazz.underlyingOfValueClass.typeSymbol + if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed.asClass) + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + tree match { + case tree: Template => + if (ctx.owner.isDerivedValueClass) { + /* This is currently redundant since value classes may not + wrap over other value classes anyway. + checkNonCyclic(ctx.owner.pos, Set(), ctx.owner) */ + extensionDefs(ctx.owner.companionModule) = new mutable.ListBuffer[Tree] + ctx.owner.primaryConstructor.makeNotPrivateAfter(NoSymbol, thisTransformer) + // SI-7859 make param accessors accessible so the erasure can generate unbox operations. + val paramAccessors = ctx.owner.info.decls.filter(_.is(ParamAccessor)) + paramAccessors.foreach(_.makeNotPrivateAfter(ctx.owner, thisTransformer)) + super.transform(tree) + } else if (ctx.owner.isStaticOwner) { + val tree1 @ Template(constr, parents, selfType, body) = super.transform(tree) + extensionDefs remove tree1.symbol.owner match { + case Some(defns) if defns.nonEmpty => + cpy.Template(tree1, constr, parents, selfType, body ++ defns) + case _ => + tree1 + } + } else tree + case DefDef(mods, name, tparams, vparamss, tpt, rhs) if tree.symbol.isMethodWithExtension => + val origMeth = tree.symbol + val origClass = ctx.owner.asClass + val origTParams = tparams.map(_.symbol) ::: origClass.typeParams // method type params ++ class type params + val origVParams = vparamss.flatten map (_.symbol) + val staticClass = origClass.companionClass + assert(staticClass.exists) + + val extensionMeth = ctx.atPhase(thisTransformer.next) { implicit ctx => + val extensionName = extensionNames(origMeth).head.toTermName + val extensionMeth = ctx.newSymbol(staticClass, extensionName, + origMeth.flags | Final &~ (Override | Protected | AbsOverride), + origMeth.info.toStatic(origClass), + privateWithin = origMeth.privateWithin, coord = tree.pos) + extensionMeth.addAnnotations(from = origMeth) + origMeth.removeAnnotation(defn.TailrecAnnotationClass) // it's on the extension method, now. + extensionMeth.enteredAfter(thisTransformer) + } + ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}") + + extensionDefs(staticClass) += polyDefDef(extensionMeth, trefs => vrefss => { + def methPart(tp: Type): MethodType = tp match { + case tp: PolyType => methPart(tp.resultType) + case tp: MethodType => tp + } + val substitutions: Type => Type = _ + .subst(origTParams, trefs) + .substSym(origVParams, vrefss.flatten.map(_.symbol)) + .substThis(origClass, MethodParam(methPart(extensionMeth.info), 0)) + new TreeTypeMap(substitutions, Map(origMeth -> extensionMeth)).transform(rhs) + }) + + // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this) + // which leaves the actual argument application for extensionCall. + val forwarder = ref(extensionMeth.termRef) + .appliedToTypes(origTParams.map(_.typeRef)) + .appliedToArg(This(origClass)) + .appliedToArgss(vparamss.nestedMap(vparam => ref(vparam.symbol))) + .withPos(rhs.pos) + cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, forwarder) + case _ => + super.transform(tree) + } + } + } +} diff --git a/src/dotty/tools/dotc/transform/MacroTransform.scala b/src/dotty/tools/dotc/transform/MacroTransform.scala index eacbd1717e1c..1ed9e68c2bdf 100644 --- a/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -45,17 +45,22 @@ abstract class MacroTransform extends Phase { override def transform(tree: Tree)(implicit ctx: Context): Tree = { tree match { + case EmptyValDef => + tree case _: PackageDef | _: MemberDef => super.transform(tree)(localCtx(tree)) case Template(constr, parents, self, body) => cpy.Template(tree, transformSub(constr), transform(parents), - transformSub(self), + transformSelf(self), transformStats(body, tree.symbol)) case _ => super.transform(tree) } } + + def transformSelf(vd: ValDef)(implicit ctx: Context) = + cpy.ValDef(vd, vd.mods, vd.name, transform(vd.tpt), vd.rhs) } } diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala new file mode 100644 index 000000000000..87d47e0cd55f --- /dev/null +++ b/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -0,0 +1,115 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import Contexts._ +import Symbols._ +import Decorators._ +import StdNames.nme +import language.implicitConversions + +object TypeUtils { + implicit def decorateTypeUtils(tpe: Type): TypeUtils = new TypeUtils(tpe) +} + +/** A decorator that provides methods for type transformations + * that are needed in the transofmer pipeline + */ +class TypeUtils(val self: Type) extends AnyVal { + import TypeUtils._ + + /** Converts the type of a member of class `clazz` to a method type that + * takes the `this` of the class and any type parameters of the class + * as additional parameters. Example: + * + * class Foo[+A <: AnyRef](val xs: List[A]) extends AnyVal { + * def baz[B >: A](x: B): List[B] = ... + * } + * + * leads to: + * + * object Foo { + * def extension$baz[B >: A <: Any, A >: Nothing <: AnyRef]($this: Foo[A])(x: B): List[B] + * } + */ + def toStatic(clazz: ClassSymbol)(implicit ctx: Context): Type = { + val (mtparamCount, origResult) = self match { + case self @ PolyType(mtnames) => (mtnames.length, self.resultType) + case _ => (0, self) + } + val ctparams = clazz.typeParams + val ctnames = ctparams.map(_.name) + + /** The method result type, prior to mapping any type parameters */ + val resultType = { + val thisParamType = clazz.typeRef.appliedTo(ctparams.map(_.typeRef)) + MethodType(nme.SELF :: Nil, thisParamType :: Nil)(mt => + origResult.substThis(clazz, MethodParam(mt, 0))) + } + + /** Replace class type parameters by the added type parameters of the polytype `pt` */ + def mapClassParams(tp: Type, pt: PolyType): Type = { + val classParamsRange = (mtparamCount until mtparamCount + ctparams.length).toList + tp.subst(clazz.typeParams, classParamsRange map (PolyParam(pt, _))) + } + + /** The bounds for the added type paraneters of the polytype `pt` */ + def mappedClassBounds(pt: PolyType): List[TypeBounds] = + ctparams.map(tparam => mapClassParams(tparam.info, pt).bounds) + + def mappedResultType(pt: PolyType): Type = mapClassParams(resultType, pt) + + self match { + case self @ PolyType(mtnames) => + PolyType(mtnames ++ ctnames)( + pt => (self.paramBounds ++ mappedClassBounds(pt)) + .mapConserve(_.subst(self, pt).bounds), + pt => mappedResultType(pt).subst(self, pt)) + case _ => + if (ctparams.isEmpty) resultType + else PolyType(ctnames)(mappedClassBounds, mappedResultType) + } + } + + /** Converts from the result of a `toStatic(clazz)` back to the original type. + * + * To do this, it removes the `$this` argument from the parameter list a method, + * and converts trailing type parameters of the method to the type parameters of + * the given `clazz`. + * + * If `stpe` is a `PolyType`, any parameters corresponding to class type parameters + * are remapped and `$this` is removed from the result type. + * If `stpe` is a `MethodType`, it may have a curried parameter list with the + * `$this` alone in the first parameter list, in which case that parameter list + * is dropped. Or, since the curried lists disappear during uncurry, it may have + * a single parameter list with `$this` as the first parameter, in which case that + * parameter is removed from the list. Note that we do not need to adjust the result + * type with substParams because at uncurry there are no more depdendent method types. + */ + def toDynamic(clazz: Symbol)(implicit ctx: Context): Type = self match { + case self: PolyType => + // contains method type parameters, followed by class type parameters + val nparams = self.paramNames.length - clazz.typeParams.length + val (mNames, cNames) = self.paramNames.splitAt(nparams) + val (mBounds, cBounds) = self.paramBounds.splitAt(nparams) + val mappedParams = + (0 until nparams).toList.map(PolyParam(self, _)) ++ clazz.typeParams.map(_.typeRef) + def mapParams(tp: Type, pt: PolyType) = { + val mapped = (0 until nparams).toList.map(PolyParam(pt, _)) ++ clazz.typeParams.map(_.typeRef) + tp.substParams(self, mapped) + } + val restpe = self.resultType.toDynamic(clazz).substParams(self, mappedParams) + if (nparams == 0) mapParams(restpe, self) + else PolyType(self.paramNames.take(nparams))( + pt => self.paramBounds.mapconserve(mapParams(_, pt).asInstanceOf[TypeBounds]), + pt => mapParams(restpe, pt)) + case mt @ MethodType(nme.SELF :: otherNames, thizType :: otherTypes) => + val remainder = + if (otherNames.isEmpty) mt.resultType + else MethodType(otherNames, otherTypes, mt.resultType) + remainder.substParam(MethodParam(mt, 0), clazz.thisType) + case _ => + self + } +} \ No newline at end of file diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 92aa7240a5dd..b1e0a6efe774 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,7 +14,7 @@ class tests extends CompilerTest { "-pagewidth", "160") implicit val defaultOptions = noCheckOptions ++ List( - "-Ycheck:super"//, "-Ystop-before:terminal" + "-Ycheck:extmethods"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") @@ -47,7 +47,6 @@ class tests extends CompilerTest { @Test def pos_overloaded() = compileFile(posDir, "overloaded", doErase) @Test def pos_templateParents() = compileFile(posDir, "templateParents", doErase) @Test def pos_structural() = compileFile(posDir, "structural", doErase) - @Test def pos_i39 = compileFile(posDir, "i39", doErase) @Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", doErase) @Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", doErase) @Test def pos_tailcall = compileDir(posDir + "tailcall/", doErase) From 30013465f45b9085b044b7abec36d7bd8d3b1b2b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jul 2014 19:20:14 +0200 Subject: [PATCH 15/47] Added infix methods for some tree constructions Added several forms of "select" and "appliedTo" methods which construct Select, Apply, TypeApply trees. Motivation: Infix methods allow chaining which is more legible than deep nesting. --- src/dotty/tools/dotc/ast/tpd.scala | 58 +++++++++++-------- .../tools/dotc/core/pickling/UnPickler.scala | 2 +- .../tools/dotc/transform/Constructors.scala | 3 +- src/dotty/tools/dotc/transform/Erasure.scala | 12 ++-- .../dotc/transform/ExtensionMethods.scala | 2 +- .../dotc/transform/InterceptedMethods.scala | 11 ++-- src/dotty/tools/dotc/transform/LazyVals.scala | 32 +++++----- .../tools/dotc/transform/Nullarify.scala | 4 +- src/dotty/tools/dotc/transform/Splitter.scala | 4 +- .../tools/dotc/transform/SuperAccessors.scala | 18 +++--- src/dotty/tools/dotc/transform/TailRec.scala | 2 +- .../tools/dotc/transform/TypeTestsCasts.scala | 4 +- 12 files changed, 85 insertions(+), 67 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index f0151e1d3ea0..d9c57bd2391c 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -25,17 +25,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Select(qualifier: Tree, name: Name)(implicit ctx: Context): Select = ta.assignType(untpd.Select(qualifier, name), qualifier) - def Select(qualifier: Tree, tp: NamedType)(implicit ctx: Context): Select = - untpd.Select(qualifier, tp.name).withType(tp) - - def Select(qualifier: Tree, sym: Symbol)(implicit ctx: Context): Select = - untpd.Select(qualifier, sym.name).withType( - TermRef.withSig(qualifier.tpe, sym.name.asTermName, sym.signature, sym.denot.asSeenFrom(qualifier.tpe))) - - def SelectWithSig(qualifier: Tree, name: Name, sig: Signature)(implicit ctx: Context) = - untpd.SelectWithSig(qualifier, name, sig) - .withType(TermRef.withSig(qualifier.tpe, name.asTermName, sig)) - def SelectFromTypeTree(qualifier: Tree, name: Name)(implicit ctx: Context): SelectFromTypeTree = ta.assignType(untpd.SelectFromTypeTree(qualifier, name), qualifier) @@ -264,7 +253,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def ref(tp: NamedType)(implicit ctx: Context): NameTree = if (tp.symbol.isStatic) Ident(tp) else tp.prefix match { - case pre: TermRef => Select(ref(pre), tp) + case pre: TermRef => ref(pre).select(tp) case pre => SelectFromTypeTree(TypeTree(pre), tp) } // no checks necessary @@ -287,12 +276,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** new C(args), calling given constructor `constr` of C */ def New(tp: Type, constr: TermSymbol, args: List[Tree])(implicit ctx: Context): Apply = { val targs = tp.argTypes - Apply( - Select( - New(tp withoutArgs targs), - TermRef.withSig(tp.normalizedPrefix, constr)) - .appliedToTypes(targs), - args) + New(tp withoutArgs targs) + .select(TermRef.withSig(tp.normalizedPrefix, constr)) + .appliedToTypes(targs) + .appliedToArgs(args) } /** An object def @@ -384,14 +371,37 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def changeOwner(from: Symbol, to: Symbol)(implicit ctx: Context): ThisTree = new TreeTypeMap(ownerMap = (sym => if (sym == from) to else sym)).apply(tree) - def appliedToArg(arg: Tree)(implicit ctx: Context): Tree = appliedToArgs(arg :: Nil) + def select(name: Name)(implicit ctx: Context): Select = + Select(tree, name) + + def select(tp: NamedType)(implicit ctx: Context): Select = + untpd.Select(tree, tp.name).withType(tp) + + def select(sym: Symbol)(implicit ctx: Context): Select = + untpd.Select(tree, sym.name).withType( + TermRef.withSig(tree.tpe, sym.name.asTermName, sym.signature, sym.denot.asSeenFrom(tree.tpe))) - def appliedToArgs(args: List[Tree])(implicit ctx: Context): Tree = - if (args.isEmpty) tree else Apply(tree, args) + def selectWithSig(name: Name, sig: Signature)(implicit ctx: Context) = + untpd.SelectWithSig(tree, name, sig) + .withType(TermRef.withSig(tree.tpe, name.asTermName, sig)) + + def appliedTo(arg: Tree)(implicit ctx: Context): Tree = + appliedToArgs(arg :: Nil) + + def appliedTo(arg: Tree, args: Tree*)(implicit ctx: Context): Tree = + appliedToArgs(arg :: args.toList) + + def appliedToArgs(args: List[Tree])(implicit ctx: Context): Apply = + Apply(tree, args) def appliedToArgss(argss: List[List[Tree]])(implicit ctx: Context): Tree = ((tree: Tree) /: argss)(Apply(_, _)) + def appliedToNone(implicit ctx: Context): Apply = appliedToArgs(Nil) + + def appliedToType(targ: Type)(implicit ctx: Context): Tree = + appliedToTypes(targ :: Nil) + def appliedToTypes(targs: List[Type])(implicit ctx: Context): Tree = appliedToTypeTrees(targs map (TypeTree(_))) @@ -444,7 +454,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(Select(tree, conversion.symbol.termRef)) + ensureApplied(tree.select(conversion.symbol.termRef)) else if (tree.tpe.widen isRef numericCls) tree else { @@ -464,10 +474,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def runtimeCall(name: TermName, args: List[Tree])(implicit ctx: Context): Tree = ??? def mkAnd(tree1: Tree, tree2: Tree)(implicit ctx: Context) = - Apply(Select(tree1, defn.Boolean_and), tree2 :: Nil) + tree1.select(defn.Boolean_and).appliedTo(tree2) def mkAsInstanceOf(tree: Tree, pt: Type)(implicit ctx: Context): Tree = - TypeApply(Select(tree, defn.Any_asInstanceOf), TypeTree(pt) :: Nil) + tree.select(defn.Any_asInstanceOf).appliedToType(pt) def ensureConforms(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (tree.tpe <:< pt) tree else mkAsInstanceOf(tree, pt) diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index 462c1013713b..5b41d1382dc9 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -1080,7 +1080,7 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: setSym() val qualifier = readTreeRef() val selector = readNameRef() - Select(qualifier, symbol.namedType) + qualifier.select(symbol.namedType) case IDENTtree => setSymName() Ident(symbol.namedType) diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index bc9d6ea61cbf..4bef41d8f3a5 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -17,7 +17,8 @@ class Constructors extends TreeTransform { val claz = tree.symbol.enclosingClass.asClass val zuper = claz.info.parents.head.typeSymbol cpy.DefDef(tree, tree.mods, tree.name, tree.tparams, tree.vparamss, tree.tpt, rhs = { - val parentCall = Apply(Select(Super(This(claz), tpnme.EMPTY, true), zuper.primaryConstructor), Nil) + val parentCall = + Super(This(claz), tpnme.EMPTY, true).select(zuper.primaryConstructor).appliedToNone if(tree.rhs.isEmpty) parentCall else Block(List(parentCall), tree.rhs) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index c9b92e55fd83..802e9595b38e 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -98,7 +98,7 @@ object Erasure { else { assert(cls ne defn.ArrayClass) val arg = safelyRemovableUnboxArg(tree) - if (arg.isEmpty) Apply(ref(boxMethod(cls.asClass)), tree :: Nil) + if (arg.isEmpty) ref(boxMethod(cls.asClass)).appliedTo(tree) else { ctx.log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") arg @@ -116,14 +116,16 @@ object Erasure { // via the unboxed type would yield a NPE (see SI-5866) unbox(tree, underlying) else - Apply(Select(adaptToType(tree, clazz.typeRef), clazz.valueClassUnbox), Nil) + adaptToType(tree, clazz.typeRef) + .select(clazz.valueClassUnbox) + .appliedToNone cast(tree1, pt) case _ => val cls = pt.classSymbol if (cls eq defn.UnitClass) constant(tree, Literal(Constant(()))) else { assert(cls ne defn.ArrayClass) - Apply(ref(unboxMethod(cls.asClass)), tree :: Nil) + ref(unboxMethod(cls.asClass)).appliedTo(tree) } } } @@ -267,7 +269,7 @@ object Erasure { override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context) = EmptyTree - override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[tpd.Tree] = { + override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { val statsFlatten = Trees.flatten(stats) val stats1 = super.typedStats(statsFlatten, exprOwner) @@ -343,7 +345,7 @@ object Erasure { bridge.entered // this should be safe, as we're executing in context of next phase ctx.debuglog(s"generating bridge from ${newDef.symbol} to $bridge") - val sel: Tree = tpd.Select(This(newDef.symbol.owner.asClass), newDef.symbol.termRef) + val sel: Tree = This(newDef.symbol.owner.asClass).select(newDef.symbol.termRef) val resultType = bridge.info.widen.resultType tpd.DefDef(bridge, { paramss: List[List[tpd.Tree]] => diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 55b53ef4d475..fbdc42a54759 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -158,7 +158,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th // which leaves the actual argument application for extensionCall. val forwarder = ref(extensionMeth.termRef) .appliedToTypes(origTParams.map(_.typeRef)) - .appliedToArg(This(origClass)) + .appliedTo(This(origClass)) .appliedToArgss(vparamss.nestedMap(vparam => ref(vparam.symbol))) .withPos(rhs.pos) cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, forwarder) diff --git a/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/src/dotty/tools/dotc/transform/InterceptedMethods.scala index f5fed6fda760..6dd66ec7592a 100644 --- a/src/dotty/tools/dotc/transform/InterceptedMethods.scala +++ b/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -91,9 +91,8 @@ class InterceptedMethods extends TreeTransform { def alt2 = defn.ScalaRuntimeModule.info.member(nme.hash_) .suchThat(_.info.firstParamTypes.head.typeSymbol == defn.AnyClass) - if (defn.ScalaNumericValueClasses contains s) { - tpd.Apply(Ident(alt1.termRef), List(tree)) - } else tpd.Apply(Ident(alt2.termRef), List(tree)) + Ident((if (defn.ScalaNumericValueClasses contains s) alt1 else alt2).termRef) + .appliedTo(tree) } } @@ -111,9 +110,9 @@ class InterceptedMethods extends TreeTransform { PoundPoundValue(qual) } else if (Any_comparisons contains tree.fun.symbol.asTerm) { if (tree.fun.symbol eq defn.Any_==) { - Apply(Select(qual, defn.Any_equals), tree.args) + qual.select(defn.Any_equals).appliedToArgs(tree.args) } else if (tree.fun.symbol eq defn.Any_!=) { - Select(Apply(Select(qual, defn.Any_equals), tree.args), defn.Boolean_!) + qual.select(defn.Any_equals).appliedToArgs(tree.args).select(defn.Boolean_!) } else unknown } /* else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) { // todo: this is needed to support value classes @@ -130,7 +129,7 @@ class InterceptedMethods extends TreeTransform { // we get a primitive form of _getClass trying to target a boxed value // so we need replace that method name with Object_getClass to get correct behavior. // See SI-5568. - Apply(Select(qual, defn.Any_getClass), Nil) + qual.select(defn.Any_getClass).appliedToNone } else { unknown } diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index ecd94a211534..75dc10ce46b6 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -7,6 +7,7 @@ import Contexts._ import Symbols._ import Decorators._ import NameOps._ +import StdNames.nme import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, TreeTransform} import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{untpd, tpd} @@ -125,9 +126,11 @@ class LazyValTranformContext { val holderSymbol = ctx.newSymbol(x.symbol.owner, holderName, containerFlags, holderImpl.typeRef, coord = x.symbol.coord) val holderTree = ValDef(holderSymbol, New(holderImpl.typeRef, List(valueInitter))) - val methodBody = - if(holderType != "LazyRef") Select(Ident(holderSymbol.termRef), "value".toTermName) - else TypeApply(Select(Select(Ident(holderSymbol.termRef), "value".toTermName), defn.Any_asInstanceOf), List(TypeTree(tpe))) + val methodBody = { + val prefix = ref(holderSymbol).select("value".toTermName) + if (holderType != "LazyRef") prefix + else prefix.select(defn.Any_asInstanceOf).appliedToType(tpe) + } val methodTree = DefDef(x.symbol.asTerm, methodBody) ctx.debuglog(s"found a lazy val ${x.show},\n rewrote with ${holderTree.show}") Thicket(holderTree, methodTree) @@ -141,7 +144,7 @@ class LazyValTranformContext { * flag = true * target * } - * } + * }` */ def mkNonThreadSafeDef(target: Symbol, flag: Symbol, rhs: Tree)(implicit ctx: Context) = { @@ -162,7 +165,7 @@ class LazyValTranformContext { * } */ def mkDefNonThreadSafeNonNullable(target: Symbol, rhs: Tree)(implicit ctx: Context) = { - val cond = Apply(Select(Ident(target.termRef), "eq".toTermName), List(Literal(Constant(null)))) + val cond = Ident(target.termRef).select(nme.eq).appliedTo(Literal(Constant(null))) val exp = Ident(target.termRef) val setTarget = Assign(exp, rhs) val init = Block(List(setTarget), exp) @@ -236,6 +239,7 @@ class LazyValTranformContext { * } * result * } + * FIXME: Don't use strings with toTermName, use predefined names instead. */ def mkThreadSafeDef(methodSymbol: TermSymbol, claz: ClassSymbol, ord: Int, target: Symbol, rhs: Tree, tp: Types.Type, offset: Tree, getFlag: Tree, stateMask: Tree, casFlag: Tree, setFlagState: Tree, waitOnLock: Tree)(implicit ctx: Context) = { val initState = Literal(Constants.Constant(0)) @@ -262,16 +266,16 @@ class LazyValTranformContext { val handler = Closure(handlerSymbol, { args => val exception = args.head.head - val complete = Apply(setFlagState, List(thiz, offset, initState, Literal(Constant(ord)))) + val complete = setFlagState.appliedTo(thiz, offset, initState, Literal(Constant(ord))) Block(List(complete), Throw(exception)) }) val compute = Assign(Ident(resultSymbol.termRef), rhs) val tr = Try(compute, handler, EmptyTree) val assign = Assign(Ident(target.termRef), Ident(resultSymbol.termRef)) - val complete = Apply(setFlagState, List(thiz, offset, computedState, Literal(Constant(ord)))) + val complete = setFlagState.appliedTo(thiz, offset, computedState, Literal(Constant(ord))) val noRetry = Assign(Ident(retrySymbol.termRef), Literal(Constants.Constant(false))) - val body = If(Apply(casFlag, List(thiz, offset, Ident(flagSymbol.termRef), computeState, Literal(Constant(ord)))), + val body = If(casFlag.appliedTo(thiz, offset, Ident(flagSymbol.termRef), computeState, Literal(Constant(ord))), Block(tr :: assign :: complete :: noRetry :: Nil, Literal(Constant(()))), Literal(Constant(()))) @@ -279,12 +283,12 @@ class LazyValTranformContext { } val waitFirst = { - val wait = Apply(waitOnLock, List(thiz, offset, Ident(flagSymbol.termRef), Literal(Constant(ord)))) + val wait = waitOnLock.appliedTo(thiz, offset, Ident(flagSymbol.termRef), Literal(Constant(ord))) CaseDef(computeState, EmptyTree, wait) } val waitSecond = { - val wait = Apply(waitOnLock, List(thiz, offset, Ident(flagSymbol.termRef), Literal(Constant(ord)))) + val wait = waitOnLock.appliedTo(thiz, offset, Ident(flagSymbol.termRef), Literal(Constant(ord))) CaseDef(notifyState, EmptyTree, wait) } @@ -295,10 +299,10 @@ class LazyValTranformContext { CaseDef(computedState, EmptyTree, body) } - val cases = Match(Apply(stateMask, List(Ident(flagSymbol.termRef), Literal(Constant(ord)))), + val cases = Match(stateMask.appliedTo(Ident(flagSymbol.termRef), Literal(Constant(ord))), List(compute, waitFirst, waitSecond, computed)) //todo: annotate with @switch - val whileBody = Block(List(Assign(Ident(flagSymbol.termRef), Apply(getFlag, List(thiz, offset)))), cases) + val whileBody = Block(List(Assign(Ident(flagSymbol.termRef), getFlag.appliedTo(thiz, offset))), cases) val cycle = untpd.WhileDo(whileCond, whileBody).withTypeUnchecked(defn.UnitType) DefDef(methodSymbol, Block(resultDef :: retryDef :: flagDef :: cycle :: Nil, Ident(resultSymbol.termRef))) } @@ -334,7 +338,7 @@ class LazyValTranformContext { val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType) addSym(claz, flagSymbol) flag = ValDef(flagSymbol, Literal(Constants.Constant(0L))) - val offsetTree = ValDef(offsetSymbol, Apply(getOffset, List(thiz, Literal(Constant(flagName.toString))))) + val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thiz, Literal(Constant(flagName.toString)))) info.defs = offsetTree :: info.defs } @@ -344,7 +348,7 @@ class LazyValTranformContext { val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType) addSym(claz, flagSymbol) flag = ValDef(flagSymbol, Literal(Constants.Constant(0L))) - val offsetTree = ValDef(offsetSymbol, Apply(getOffset, List(thiz, Literal(Constant(flagName.toString))))) + val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thiz, Literal(Constant(flagName.toString)))) appendOffsetDefs += (companion.name.moduleClassName -> new OffsetInfo(List(offsetTree), ord)) } diff --git a/src/dotty/tools/dotc/transform/Nullarify.scala b/src/dotty/tools/dotc/transform/Nullarify.scala index 664b4bee111e..8d967cc1a7d0 100644 --- a/src/dotty/tools/dotc/transform/Nullarify.scala +++ b/src/dotty/tools/dotc/transform/Nullarify.scala @@ -85,11 +85,11 @@ class Nullarify extends TreeTransform with InfoTransformer { def result(implicit ctx: Context) = { tp1.widen match { case MethodType(Nil, _) if origType.widenExpr.isInstanceOf[ValueType] => - Apply(tree1, Nil) + tree1.appliedToNone case _ => origType match { case _: ExprType => // it's a by-name parameter - Apply(Select(tree1, defn.Function0_apply), Nil) + tree1.select(defn.Function0_apply).appliedToNone case _ => tree1 } diff --git a/src/dotty/tools/dotc/transform/Splitter.scala b/src/dotty/tools/dotc/transform/Splitter.scala index 978a9cce44d1..745919f3a749 100644 --- a/src/dotty/tools/dotc/transform/Splitter.scala +++ b/src/dotty/tools/dotc/transform/Splitter.scala @@ -80,12 +80,12 @@ class Splitter extends TreeTransform { else { def choose(qual: Tree, syms: List[Symbol]): Tree = { def testOrCast(which: Symbol, mbr: Symbol) = - TypeApply(Select(qual, which), TypeTree(mbr.owner.typeRef) :: Nil) + qual.select(which).appliedToType(mbr.owner.typeRef) def select(sym: Symbol) = { val qual1 = if (qual.tpe derivesFrom sym.owner) qual else testOrCast(defn.Any_asInstanceOf, sym) - Select(qual1, sym) withPos tree.pos + qual1.select(sym).withPos(tree.pos) } syms match { case Nil => diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index d6af963019a1..0fbf33869a56 100644 --- a/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -98,7 +98,7 @@ class SuperAccessors extends MacroTransform with IdentityDenotTransformer { this acc } - Select(This(clazz), superAcc) withPos sel.pos + This(clazz).select(superAcc).withPos(sel.pos) } private def transformArgs(formals: List[Type], args: List[Tree])(implicit ctx: Context) = @@ -274,7 +274,7 @@ class SuperAccessors extends MacroTransform with IdentityDenotTransformer { this sym.copySymDenotation(initFlags = sym.flags | Method, info = ensureMethodic(sym.info)) .installAfter(thisTransformer) val superAcc = - Select(Super(This(currentClass), tpnme.EMPTY, inConstrCall = false), alias) + Super(This(currentClass), tpnme.EMPTY, inConstrCall = false).select(alias) DefDef(sym, ensureConforms(superAcc, sym.info.widen)) } return forwarder(ctx.withPhase(thisTransformer.next)) @@ -443,16 +443,18 @@ class SuperAccessors extends MacroTransform with IdentityDenotTransformer { this clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) val code = polyDefDef(newAcc, trefs => vrefss => { val (receiver :: _) :: tail = vrefss - val base = Select(receiver, sym).appliedToTypes(trefs) + val base = receiver.select(sym).appliedToTypes(trefs) (base /: vrefss)(Apply(_, _)) }) ctx.debuglog("created protected accessor: " + code) storeAccessorDefinition(clazz, code) newAcc } - val res = - Apply(Select(This(clazz), protectedAccessor).appliedToTypeTrees(targs), qual :: Nil) - .withPos(tree.pos) + val res = This(clazz) + .select(protectedAccessor) + .appliedToTypeTrees(targs) + .appliedTo(qual) + .withPos(tree.pos) ctx.debuglog(s"Replaced $tree with $res") res } @@ -473,13 +475,13 @@ class SuperAccessors extends MacroTransform with IdentityDenotTransformer { this clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer) val code = DefDef(newAcc, vrefss => { val (receiver :: value :: Nil) :: Nil = vrefss - Assign(Select(receiver, field), value).withPos(tree.pos) + Assign(receiver.select(field), value).withPos(tree.pos) }) ctx.debuglog("created protected setter: " + code) storeAccessorDefinition(clazz, code) newAcc } - Select(This(clazz), protectedAccessor).withPos(tree.pos) + This(clazz).select(protectedAccessor).withPos(tree.pos) } /** Does `sym` need an accessor when accessed from `currentClass`? diff --git a/src/dotty/tools/dotc/transform/TailRec.scala b/src/dotty/tools/dotc/transform/TailRec.scala index cd0643a6e7fe..a2278e72f01d 100644 --- a/src/dotty/tools/dotc/transform/TailRec.scala +++ b/src/dotty/tools/dotc/transform/TailRec.scala @@ -329,7 +329,7 @@ class TailRec extends TreeTransform with DenotTransformer { val newDenot = d.copySymDenotation(initFlags = sym.flags &~ Flags.Local) newDenot.installAfter(TailRec.this) } - Select(thiz, sym) + thiz.select(sym) case _ => tree } } diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 5f65ee414806..b209f7647ecf 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -34,7 +34,7 @@ class TypeTestsCasts extends TreeTransform { def isPrimitive(tp: Type) = tp.classSymbol.isPrimitiveValueClass def derivedTree(qual1: Tree, sym: Symbol, tp: Type) = - cpy.TypeApply(tree, Select(qual1, sym) withPos qual.pos, List(TypeTree(tp))) + cpy.TypeApply(tree, qual1.select(sym).withPos(qual.pos), List(TypeTree(tp))) def qualCls = qual.tpe.classSymbol @@ -49,7 +49,7 @@ class TypeTestsCasts extends TreeTransform { else argType.dealias match { case _: SingletonType => val cmpOp = if (argType derivesFrom defn.AnyValClass) defn.Any_equals else defn.Object_eq - Apply(Select(expr, cmpOp), singleton(argType) :: Nil) + expr.select(cmpOp).appliedTo(singleton(argType)) case AndType(tp1, tp2) => evalOnce(expr) { fun => val erased1 = transformIsInstanceOf(fun, tp1) From 1fdc188af5b8b34b1a54555dc48e6f2ccb3294c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jul 2014 19:20:47 +0200 Subject: [PATCH 16/47] New micro phase: Literalize Convert expressions with constant types to Literals. --- src/dotty/tools/dotc/Compiler.scala | 3 +- .../tools/dotc/transform/Literalize.scala | 65 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/dotty/tools/dotc/transform/Literalize.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index d202f3a529e7..b51f383f770e 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -28,7 +28,8 @@ class Compiler { new Splitter), List(new Nullarify, new TypeTestsCasts, - new InterceptedMethods), + new InterceptedMethods, + new Literalize), List(new Erasure), List(new UncurryTreeTransform /* , new Constructors */) diff --git a/src/dotty/tools/dotc/transform/Literalize.scala b/src/dotty/tools/dotc/transform/Literalize.scala new file mode 100644 index 000000000000..14ce8fd05c68 --- /dev/null +++ b/src/dotty/tools/dotc/transform/Literalize.scala @@ -0,0 +1,65 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import ast.Trees._ + +/** This phase rewrites idempotent expressions with constant types to Literals. + * The constant types are eliminated by erasure, so we need to keep + * the info about constantness in the trees. + */ +class Literalize extends TreeTransform { + import ast.tpd._ + + override def name: String = "literalize" + + /** Note: Demanding idempotency instead of purity is strictly speaking too loose. + * Example + * + * object O { final val x = 42; println("43") } + * O.x + * + * Strictly speaking we can't replace `O.x` with `42`. But this would make + * most expressions non-constant. Maybe we can change the spec to accept this + * kind of eliding behavior. Or else enforce true purity in the compiler. + * The choice will be affected by what we will do with `inline` and with + * Singleton type bounds (see SIP 23). Presumably + * + * object O1 { val x: Singleton = 42; println("43") } + * object O2 { inline val x = 42; println("43") } + * + * should behave differently. + * + * O1.x should have the same effect as { println("43"; 42 } + * + * whereas + * + * O2.x = 42 + * + * Revisit this issue once we have implemented `inline`. Then we can demand + * purity of the prefix unless the selection goes to an inline val. + */ + def literalize(tree: Tree)(implicit ctx: Context): Tree = tree.tpe match { + case ConstantType(value) if isIdempotentExpr(tree) => Literal(value) + case _ => tree + } + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = + literalize(tree) +} \ No newline at end of file From 5428549a57b710b11e57aab4eee24e9b89b8b97c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jul 2014 12:41:08 +0200 Subject: [PATCH 17/47] New Flag: Inline Added now because it affects purity if expressions: Inlined pure values are pure even if referenced from impure prefixes (i.e. prefix need not be evaluated). --- src/dotty/tools/dotc/core/Flags.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 07259276e8f5..b68e7dc42643 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -324,8 +324,8 @@ object Flags { /** Symbol is initialized to the default value, e.g. var x: T = _ */ final val DefaultInit = termFlag(29, "") - /** Symbol is a macro */ - final val Macro = commonFlag(30, "") + /** Symbol is inlined */ + final val Inline = commonFlag(30, "inline") /** Symbol is defined by a Java class */ final val JavaDefined = commonFlag(31, "") @@ -395,14 +395,17 @@ object Flags { /** A definition that's initialized before the super call (Scala 2.x only) */ final val Scala2PreSuper = termFlag(58, "") + /** A macro (Scala 2.x only) */ + final val Macro = commonFlag(59, "") + /** A method that is known to have inherited default parameters */ - final val InheritedDefaultParams = termFlag(59, "") + final val InheritedDefaultParams = termFlag(60, "") - /** A method that is known to no default parameters */ - final val NoDefaultParams = termFlag(60, "") + /** A method that is known to have no default parameters */ + final val NoDefaultParams = termFlag(61, "") /** A denotation that is valid in all run-ids */ - final val Permanent = commonFlag(61, "") + final val Permanent = commonFlag(62, "") // --------- Combined Flag Sets and Conjunctions ---------------------- From 4aa934bcc115aca15c2edfac5f31bd23e60b306f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jul 2014 13:13:11 +0200 Subject: [PATCH 18/47] Revised purity tests in TreeInfo Added two levels: pure and idempotent and fixed what look like obvious bugs. It seems the previous treatment confused the two levels. Changed EtaExpansion and Erasure to use Pure instead of Idempotent where appropriate. --- src/dotty/tools/dotc/ast/TreeInfo.scala | 81 +++++++++++++------ src/dotty/tools/dotc/transform/Erasure.scala | 2 +- src/dotty/tools/dotc/typer/EtaExpansion.scala | 4 +- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 734963ea395d..78fb2d80b28d 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -282,41 +282,46 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => - /** Is tree a definition that has no side effects when - * evaluated as part of a block after the first time? + /** The purity level of this statement. + * @return pure if statement has no side effects + * idempotent if running the statement a second time has no side effects + * impure otherwise */ - def isIdempotentDef(tree: tpd.Tree)(implicit ctx: Context): Boolean = unsplice(tree) match { + private def statPurity(tree: tpd.Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { case EmptyTree | TypeDef(_, _, _) | Import(_, _) | DefDef(_, _, _, _, _, _) => - true + Pure case ValDef(mods, _, _, rhs) => - !(mods is Mutable) && isIdempotentExpr(rhs) + if (mods is Mutable) Impure else exprPurity(rhs) case _ => - false + Impure } - /** Is tree an expression which can be inlined without affecting program semantics? + /** The purity level of this expression. + * @return pure if expression has no side effects + * idempotent if running the expression a second time has no side effects + * impure otherwise * - * Note that this is not called "isExprPure" since purity (lack of side-effects) - * is not the litmus test. References to modules and lazy vals are side-effecting, - * both because side-effecting code may be executed and because the first reference - * takes a different code path than all to follow; but they are safe to inline - * because the expression result from evaluating them is always the same. + * Note that purity and idempotency are different. References to modules and lazy + * vals are impure (side-effecting) both because side-effecting code may be executed and because the first reference + * takes a different code path than all to follow; but they are idempotent + * because running the expression a second time gives the cached result. */ - def isIdempotentExpr(tree: tpd.Tree)(implicit ctx: Context): Boolean = unsplice(tree) match { + private def exprPurity(tree: tpd.Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { case EmptyTree | This(_) | Super(_, _) | Literal(_) => - true + Pure case Ident(_) => - isIdempotentRef(tree) + refPurity(tree) case Select(qual, _) => - isIdempotentRef(tree) && isIdempotentExpr(qual) + refPurity(tree).min( + if (tree.symbol.is(Inline)) Pure else exprPurity(qual)) case TypeApply(fn, _) => - isIdempotentExpr(fn) + exprPurity(fn) /* * Not sure we'll need that. Comment out until we find out case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX => @@ -326,21 +331,36 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case Apply(fn, Nil) => // Note: After uncurry, field accesses are represented as Apply(getter, Nil), // so an Apply can also be pure. - // However, before typing, applications of nullary functional values are also - // Apply(function, Nil) trees. To prevent them from being treated as pure, - // we check that the callee is a method. - // The callee might also be a Block, which has a null symbol, so we guard against that (SI-7185) - fn.symbol != null && (fn.symbol is (Method, butNot = Lazy)) && isIdempotentExpr(fn) + if (fn.symbol is Stable) exprPurity(fn) else Impure case Typed(expr, _) => - isIdempotentExpr(expr) + exprPurity(expr) case Block(stats, expr) => - (stats forall isIdempotentDef) && isIdempotentExpr(expr) + (exprPurity(expr) /: stats.map(statPurity))(_ min _) case _ => - false + Impure } + def isPureExpr(tree: tpd.Tree)(implicit ctx: Context) = exprPurity(tree) == Pure + def isIdempotentExpr(tree: tpd.Tree)(implicit ctx: Context) = exprPurity(tree) >= Idempotent + + /** The purity level of this reference. + * @return + * pure if reference is (nonlazy and stable) or to a parameterized function + * idempotent if reference is lazy and stable + * impure otherwise + * @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable + * flags set. + */ + private def refPurity(tree: tpd.Tree)(implicit ctx: Context): PurityLevel = + if (!tree.tpe.widen.isParameterless) Pure + else if (!tree.symbol.is(Stable)) Impure + else if (tree.symbol.is(Lazy)) Idempotent + else Pure + + def isPureRef(tree: tpd.Tree)(implicit ctx: Context) = + refPurity(tree) == Pure def isIdempotentRef(tree: tpd.Tree)(implicit ctx: Context) = - tree.symbol.isStable || !tree.tpe.widen.isParameterless + refPurity(tree) >= Idempotent /** Is symbol potentially a getter of a mutable variable? */ @@ -456,6 +476,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case nil => Nil } + + private class PurityLevel(val x: Int) { + def >= (that: PurityLevel) = x >= that.x + def min(that: PurityLevel) = new PurityLevel(x min that.x) + } + + private val Pure = new PurityLevel(2) + private val Idempotent = new PurityLevel(1) + private val Impure = new PurityLevel(0) } /** a Match(Typed(_, tpt), _) must be translated into a switch if isSwitchAnnotation(tpt.tpe) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 802e9595b38e..44ee5db5ac5c 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -85,7 +85,7 @@ object Erasure { def isPrimitiveValueType(tpe: Type)(implicit ctx: Context): Boolean = tpe.classSymbol.isPrimitiveValueClass def constant(tree: Tree, const: Tree)(implicit ctx: Context) = - if (isIdempotentExpr(tree)) Block(tree :: Nil, const) else const + if (isPureExpr(tree)) Block(tree :: Nil, const) else const final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { tree.tpe.widen match { diff --git a/src/dotty/tools/dotc/typer/EtaExpansion.scala b/src/dotty/tools/dotc/typer/EtaExpansion.scala index 110dc6152932..69b512416d0a 100644 --- a/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -21,7 +21,7 @@ object EtaExpansion { import tpd._ private def lift(defs: mutable.ListBuffer[Tree], expr: Tree, prefix: String = "")(implicit ctx: Context): Tree = - if (isIdempotentExpr(expr)) expr + if (isPureExpr(expr)) expr else { val name = ctx.freshName(prefix).toTermName val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, expr.tpe.widen, coord = positionCoord(expr.pos)) @@ -83,7 +83,7 @@ object EtaExpansion { cpy.Apply(tree, liftApp(defs, fn), liftArgs(defs, fn.tpe, args)) case TypeApply(fn, targs) => cpy.TypeApply(tree, liftApp(defs, fn), targs) - case Select(pre, name) if tpd.isIdempotentRef(tree) => + case Select(pre, name) if isPureRef(tree) => cpy.Select(tree, liftApp(defs, pre), name) case Block(stats, expr) => liftApp(defs ++= stats, expr) From ab8d873430c22337c6fc6332cad5708514fd5fa0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jul 2014 18:12:09 +0200 Subject: [PATCH 19/47] Fix bad type passed to indexOf. This is a trap waiting to happen elsewhere as well. Not clear how to avoid it. --- src/dotty/tools/dotc/transform/ExtensionMethods.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index fbdc42a54759..73a0ed458bb2 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -55,7 +55,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th decl match { case decl: SingleDenotation => val alts = decl.alternatives - val index = alts indexOf imeth + val index = alts indexOf imeth.denot assert(index >= 0, alts+" does not contain "+imeth) def altName(index: Int) = (imeth.name+"$extension"+index).toTermName altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName) From 3eabbb77c8f8bd3e08b39e5335e8a67e2d68e659 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jul 2014 11:52:38 +0200 Subject: [PATCH 20/47] Move valueclass functionality into its own ValueClass module. --- .../tools/dotc/core/SymDenotations.scala | 26 ++++---------- src/dotty/tools/dotc/core/Symbols.scala | 10 ++---- src/dotty/tools/dotc/transform/Erasure.scala | 7 ++-- .../dotc/transform/ExtensionMethods.scala | 5 +-- .../tools/dotc/transform/SuperAccessors.scala | 3 +- .../tools/dotc/transform/ValueClasses.scala | 36 +++++++++++++++++++ test/test/showClass.scala | 1 - 7 files changed, 53 insertions(+), 35 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/ValueClasses.scala diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ba57909a0d75..f55817505b0c 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -329,6 +329,12 @@ object SymDenotations { final def isAnonymousClass(implicit ctx: Context): Boolean = initial.asSymDenotation.name startsWith tpnme.ANON_CLASS + /** Is symbol a primitive value class? */ + def isPrimitiveValueClass(implicit ctx: Context) = defn.ScalaValueClasses contains symbol + + /** Is symbol a phantom class for which no runtime representation exists? */ + def isPhantomClass(implicit ctx: Context) = defn.PhantomClasses contains symbol + /** Is this symbol a class representing a refinement? These classes * are used only temporarily in Typer and Unpickler as an intermediate * step for creating Refinement types. @@ -403,14 +409,6 @@ object SymDenotations { /** Is this a user defined "def" method? Excluded are accessors. */ final def isSourceMethod(implicit ctx: Context) = this is (Method, butNot = Accessor) - /** This this a method in a value class that is implemented as an extension method? */ - final def isMethodWithExtension(implicit ctx: Context) = - isSourceMethod && - owner.isDerivedValueClass && - !isConstructor && - !is(SuperAccessor) && - !is(Macro) - /** Is this a setter? */ final def isGetter(implicit ctx: Context) = (this is Accessor) && !originalName.isSetterName @@ -1335,18 +1333,6 @@ object SymDenotations { decls.denotsNamed(cname).first.symbol } - /** The member that of a derived value class that unboxes it. */ - def valueClassUnbox(implicit ctx: Context): Symbol = - // (info.decl(nme.unbox)).orElse(...) uncomment once we accept unbox methods - classInfo.decls - .find(d => d.isTerm && d.symbol.is(ParamAccessor)) - .map(_.symbol) - .getOrElse(NoSymbol) - - /** The unboxed type that underlies a derived value class */ - def underlyingOfValueClass(implicit ctx: Context): Type = - valueClassUnbox.info.resultType - /** 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`. diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 6421018e5274..9d60c99852d3 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -391,14 +391,8 @@ object Symbols { /** Is this symbol a user-defined value class? */ final def isDerivedValueClass(implicit ctx: Context): Boolean = - false && // value classes are not supported yet - isClass && denot.derivesFrom(defn.AnyValClass) && !isPrimitiveValueClass - - /** Is symbol a primitive value class? */ - def isPrimitiveValueClass(implicit ctx: Context) = defn.ScalaValueClasses contains this - - /** Is symbol a phantom class for which no runtime representation exists? */ - def isPhantomClass(implicit ctx: Context) = defn.PhantomClasses contains this + false // will migrate to ValueClasses.isDerivedValueClass; + // unsupported value class code will continue to use this stub while it exists /** The current name of this symbol */ final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName] diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 44ee5db5ac5c..35742ac8c714 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -22,6 +22,7 @@ import dotty.tools.dotc.ast.{Trees, tpd, untpd} import ast.Trees._ import scala.collection.mutable.ListBuffer import dotty.tools.dotc.core.Flags +import ValueClasses._ class Erasure extends Phase with DenotTransformer { @@ -90,7 +91,7 @@ object Erasure { final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { tree.tpe.widen match { case ErasedValueType(clazz, _) => - New(clazz.typeRef, cast(tree, clazz.underlyingOfValueClass) :: Nil) // todo: use adaptToType? + New(clazz.typeRef, cast(tree, underlyingOfValueClass(clazz)) :: Nil) // todo: use adaptToType? case tp => val cls = tp.classSymbol if (cls eq defn.UnitClass) constant(tree, ref(defn.BoxedUnit_UNIT)) @@ -117,7 +118,7 @@ object Erasure { unbox(tree, underlying) else adaptToType(tree, clazz.typeRef) - .select(clazz.valueClassUnbox) + .select(valueClassUnbox(clazz)) .appliedToNone cast(tree1, pt) case _ => @@ -365,4 +366,4 @@ object Erasure { if (tree.isEmpty) tree else adaptToType(tree, pt) } } -} \ No newline at end of file +} diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 73a0ed458bb2..b64f529bf561 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -6,6 +6,7 @@ package dotty.tools.dotc package transform import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import ValueClasses._ import dotty.tools.dotc.ast.{Trees, tpd} import scala.collection.{ mutable, immutable } import mutable.ListBuffer @@ -96,7 +97,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th if (seen contains clazz) ctx.error("value class may not unbox to itself", pos) else { - val unboxed = clazz.underlyingOfValueClass.typeSymbol + val unboxed = underlyingOfValueClass(clazz).typeSymbol if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed.asClass) } @@ -122,7 +123,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th tree1 } } else tree - case DefDef(mods, name, tparams, vparamss, tpt, rhs) if tree.symbol.isMethodWithExtension => + case DefDef(mods, name, tparams, vparamss, tpt, rhs) if isMethodWithExtension(tree.symbol) => val origMeth = tree.symbol val origClass = ctx.owner.asClass val origTParams = tparams.map(_.symbol) ::: origClass.typeParams // method type params ++ class type params diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index 0fbf33869a56..52306956e3ad 100644 --- a/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -4,6 +4,7 @@ package transform import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} import dotty.tools.dotc.ast.{Trees, tpd} import scala.collection.{ mutable, immutable } +import ValueClasses._ import mutable.ListBuffer import scala.annotation.tailrec import core._ @@ -368,7 +369,7 @@ class SuperAccessors extends MacroTransform with IdentityDenotTransformer { this transformSelect case DefDef(mods, name, tparams, vparamss, tpt, rhs) => - val rhs1 = if (sym.isMethodWithExtension) withInvalidOwner(transform(rhs)) else transform(rhs) + val rhs1 = if (isMethodWithExtension(sym)) withInvalidOwner(transform(rhs)) else transform(rhs) cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, rhs1) case TypeApply(sel @ Select(qual, name), args) => diff --git a/src/dotty/tools/dotc/transform/ValueClasses.scala b/src/dotty/tools/dotc/transform/ValueClasses.scala new file mode 100644 index 000000000000..c5cf44552bbf --- /dev/null +++ b/src/dotty/tools/dotc/transform/ValueClasses.scala @@ -0,0 +1,36 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import Symbols._ +import SymDenotations._ +import Contexts._ +import Flags._ + +/** Methods that apply to user-defined value classes */ +object ValueClasses { + + def isDerivedValueClass(d: SymDenotation)(implicit ctx: Context) = + d.isClass && d.derivesFrom(defn.AnyValClass) && !d.isPrimitiveValueClass + + def isMethodWithExtension(d: SymDenotation)(implicit ctx: Context) = + d.isSourceMethod && + isDerivedValueClass(d.owner) && + !d.isConstructor && + !d.is(SuperAccessor) && + !d.is(Macro) + + /** The member that of a derived value class that unboxes it. */ + def valueClassUnbox(d: ClassDenotation)(implicit ctx: Context): Symbol = + // (info.decl(nme.unbox)).orElse(...) uncomment once we accept unbox methods + d.classInfo.decls + .find(d => d.isTerm && d.symbol.is(ParamAccessor)) + .map(_.symbol) + .getOrElse(NoSymbol) + + /** The unboxed type that underlies a derived value class */ + def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type = + valueClassUnbox(d).info.resultType + +} diff --git a/test/test/showClass.scala b/test/test/showClass.scala index ee71854d103f..78751ad6edaa 100644 --- a/test/test/showClass.scala +++ b/test/test/showClass.scala @@ -1,7 +1,6 @@ package test import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Symbols object showClass extends ShowClassTests { From 8b1e58ffb847706cada8fc5834c5ac6bcfcd8421 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Jul 2014 14:21:45 +0200 Subject: [PATCH 21/47] Add definedPeriodsString method for disgnostics. --- src/dotty/tools/dotc/core/Denotations.scala | 35 ++++++++++----------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 82ee6560da6a..a2c8af8867ef 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -570,31 +570,14 @@ object Denotations { //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") cur = cur.nextInRun cnt += 1 - if (cnt > MaxPossiblePhaseId) { - println(s"seems to be a loop in Denotations for $this, currentPeriod = $currentPeriod") - printPeriods(this) - assert(false) - } + assert(cnt <= MaxPossiblePhaseId, + s"demanding denotation of $this outside defined interval: defined periods are${definedPeriodsString}") } cur } - } } - private def printPeriods(current: SingleDenotation): Unit = { - print(s"periods for $this:") - var cur = current - var cnt = 0 - do { - print(" " + cur.validFor) - cur = cur.nextInRun - cnt += 1 - if (cnt > MaxPossiblePhaseId) { println(" ..."); return } - } while (cur ne current) - println() - } - /** Install this denotation to be the result of the given denotation transformer. * This is the implementation of the same-named method in SymDenotations. * It's placed here because it needs access to private fields of SingleDenotation. @@ -643,6 +626,20 @@ object Denotations { if (symbol == NoSymbol) symbol.toString else s"" + + def definedPeriodsString: String = { + var sb = new StringBuilder() + var cur = this + var cnt = 0 + do { + sb.append(" " + cur.validFor) + cur = cur.nextInRun + cnt += 1 + if (cnt > MaxPossiblePhaseId) { sb.append(" ..."); cur = this } + } while (cur ne this) + sb.toString + } + // ------ PreDenotation ops ---------------------------------------------- final def first = this From 8d41e9dcee916e2fa4c7f096eb491d38e1185c1c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 11:37:30 +0200 Subject: [PATCH 22/47] Make TypeParamCreation flags depend on owner Type params should have different flags, depending on whether they are owned by a method or a class. Only class type parameters are marked Deferred, protected, and Local. --- src/dotty/tools/dotc/core/Definitions.scala | 2 +- src/dotty/tools/dotc/core/Flags.scala | 4 ++-- src/dotty/tools/dotc/core/SymDenotations.scala | 7 +++++++ src/dotty/tools/dotc/core/Symbols.scala | 2 +- src/dotty/tools/dotc/core/pickling/ClassfileParser.scala | 2 +- src/dotty/tools/dotc/core/pickling/UnPickler.scala | 2 +- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 5e335e240426..d04a26883056 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -37,7 +37,7 @@ class Definitions { scope.enter(newSymbol(cls, name, flags, TypeBounds.empty)) private def newTypeParam(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) = - newTypeField(cls, name, flags | TypeParamCreationFlags, scope) + newTypeField(cls, name, flags | ClassTypeParamCreationFlags, scope) private def newSyntheticTypeParam(cls: ClassSymbol, scope: MutableScope, paramFlags: FlagSet, suffix: String = "T0") = newTypeParam(cls, suffix.toTypeName.expandedName(cls), ExpandedName | paramFlags, scope) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index b68e7dc42643..c467a553f389 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -452,8 +452,8 @@ object Flags { /** The flags of the self symbol */ final val SelfSymFlags = Private | Local | Deferred - /** The flags of a type parameter */ - final val TypeParamCreationFlags = TypeParam | Deferred | Protected | Local + /** The flags of a class type parameter */ + final def ClassTypeParamCreationFlags = TypeParam | Deferred | Protected | Local /** Flags that can apply to both a module val and a module class, except those that * are added at creation anyway diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index f55817505b0c..7d1948784a0b 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -828,6 +828,11 @@ object SymDenotations { else if (this is Contravariant) -1 else 0 + /** The flags to be used for a type parameter owned by this symbol. + * Overridden by ClassDenotation. + */ + def typeParamCreationFlags: FlagSet = TypeParam + override def toString = { val kindString = if (myFlags is ModuleClass) "module class" @@ -1080,6 +1085,8 @@ object SymDenotations { (symbol eq defn.NothingClass) || (symbol eq defn.NullClass) && (base ne defn.NothingClass)) + final override def typeParamCreationFlags = ClassTypeParamCreationFlags + private[this] var myMemberFingerPrint: FingerPrint = FingerPrint.unknown private def computeMemberFingerPrint(implicit ctx: Context): FingerPrint = { diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 9d60c99852d3..ab083a128037 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -251,7 +251,7 @@ trait Symbols { this: Context => val tparams = tparamBuf.toList val bounds = boundsFn(trefBuf.toList) for ((name, tparam, bound) <- (names, tparams, bounds).zipped) - tparam.denot = SymDenotation(tparam, owner, name, flags | TypeParamCreationFlags, bound) + tparam.denot = SymDenotation(tparam, owner, name, flags | owner.typeParamCreationFlags, bound) tparams } diff --git a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala index f2b81072ae75..59658c9c14f0 100644 --- a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala @@ -353,7 +353,7 @@ class ClassfileParser( val tpname = subName(':'.==).toTypeName val expname = if (owner.isClass) tpname.expandedName(owner) else tpname val s = ctx.newSymbol( - owner, expname, Flags.TypeParamCreationFlags, + owner, expname, owner.typeParamCreationFlags, typeParamCompleter(index), coord = indexCoord(index)) if (owner.isClass) owner.asClass.enter(s, owner.decls) tparams = tparams + (tpname -> s) diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index 5b41d1382dc9..36b2c99bf395 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -453,7 +453,7 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: var flags1 = flags if (flags is TypeParam) { name1 = name1.expandedName(owner) - flags1 |= TypeParamCreationFlags | ExpandedName + flags1 |= owner.typeParamCreationFlags | ExpandedName } ctx.newSymbol(owner, name1, flags1, localMemberUnpickler, coord = start) case CLASSsym => From 7b13a861787eb1e7823957b156dbb989f7b415d3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 11:50:01 +0200 Subject: [PATCH 23/47] Invalidate member caches in different periods A membercache is not valid in a period different from the one it was created in. Reason: The denotations stored in the cache might have different infos in different periods. Also: add maybeOwner convenience method for printing, which handles NoDenotation/NoSymbol gracefully. --- src/dotty/tools/dotc/core/SymDenotations.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 7d1948784a0b..b0a09baf0885 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -83,6 +83,9 @@ object SymDenotations { /** The owner of the symbol; overridden in NoDenotation */ def owner: Symbol = ownerIfExists + /** Same as owner, except returns NoSymbol for NoSymbol */ + def maybeOwner: Symbol = if (exists) owner else NoSymbol + /** The flag set */ final def flags(implicit ctx: Context): FlagSet = { ensureCompleted(); myFlags } @@ -987,6 +990,7 @@ object SymDenotations { mySuperClassBits = null myMemberFingerPrint = FingerPrint.unknown myMemberCache = null + myMemberCachePeriod = Nowhere memberNamesCache = SimpleMap.Empty } @@ -1121,9 +1125,13 @@ object SymDenotations { } private[this] var myMemberCache: LRUCache[Name, PreDenotation] = null + private[this] var myMemberCachePeriod: Period = Nowhere - private def memberCache: LRUCache[Name, PreDenotation] = { - if (myMemberCache == null) myMemberCache = new LRUCache + private def memberCache(implicit ctx: Context): LRUCache[Name, PreDenotation] = { + if (myMemberCachePeriod != ctx.period) { + myMemberCache = new LRUCache + myMemberCachePeriod = ctx.period + } myMemberCache } From 05120fc7ea83a00485a68d2854e5b62c1dbb8c01 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 11:58:54 +0200 Subject: [PATCH 24/47] Fixes to MacroTransform and TreeTransformer 1) Make local context go to module class if the tree symbol is a package val. Perviously, this gave the wrong context owner for package definitions. 2) Add a hook to define the phase in which a transform should be run. --- .../tools/dotc/transform/MacroTransform.scala | 16 +++++++++++++--- .../tools/dotc/transform/TreeTransform.scala | 8 +++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/transform/MacroTransform.scala b/src/dotty/tools/dotc/transform/MacroTransform.scala index 1ed9e68c2bdf..0ee92bccdce3 100644 --- a/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -7,6 +7,7 @@ import Phases._ import ast.Trees._ import Contexts._ import Symbols._ +import Flags.PackageVal import Decorators._ /** A base class for transforms. @@ -18,15 +19,24 @@ abstract class MacroTransform extends Phase { override def run(implicit ctx: Context): Unit = { val unit = ctx.compilationUnit - unit.tpdTree = newTransformer.transform(unit.tpdTree) + unit.tpdTree = newTransformer.transform(unit.tpdTree)(ctx.withPhase(transformPhase)) } protected def newTransformer(implicit ctx: Context): Transformer + /** The phase in which the transformation should be run. + * By default this is the phase given by the this macro transformer, + * but it could be overridden to be the phase following that one. + */ + protected def transformPhase(implicit ctx: Context): Phase = this + class Transformer extends TreeMap { - protected def localCtx(tree: Tree)(implicit ctx: Context) = - ctx.fresh.setTree(tree).setOwner(tree.symbol) + protected def localCtx(tree: Tree)(implicit ctx: Context) = { + val sym = tree.symbol + val owner = if (sym is PackageVal) sym.moduleClass else sym + ctx.fresh.setTree(tree).setOwner(owner) + } /** The current enclosing class * @pre We must be inside a class diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 2bc7334654e1..8e7c4f6d01f3 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -5,6 +5,7 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.core.Flags.PackageVal import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.core.Decorators._ import scala.annotation.tailrec @@ -883,7 +884,12 @@ object TreeTransforms { } } else tree - def localContext(owner: Symbol)(implicit ctx: Context) = ctx.fresh.setOwner(owner) + // TODO merge with localCtx in MacroTransform + // Generally: If we will keep MacroTransform, merge common behavior with TreeTransform + def localContext(sym: Symbol)(implicit ctx: Context) = { + val owner = if (sym is PackageVal) sym.moduleClass else sym + ctx.fresh.setOwner(owner) + } final private[TreeTransforms] def transformNamed(tree: NameTree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = tree match { From cd82c859bb2fe05de257cbc81e97bedd2bbf2a4a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 14:37:19 +0200 Subject: [PATCH 25/47] Make more Definition methods depend on implicit context argument The problem is that exploration methods are run and phase dependent, whereas Definitions has an implicit context that freezes the period when Definitions ewas created. We should complement this by splitting Definitions into a global and per/run part, but that is independent of the change in this commit. --- src/dotty/tools/dotc/core/Definitions.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index d04a26883056..594f0c013588 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -409,20 +409,20 @@ class Definitions { lazy val RootImports = List[Symbol](JavaLangPackageVal, ScalaPackageVal, ScalaPredefModule, DottyPredefModule) - def isTupleType(tp: Type) = { + def isTupleType(tp: Type)(implicit ctx: Context) = { val arity = tp.dealias.argInfos.length arity <= MaxTupleArity && (tp isRef TupleClass(arity)) } - def isProductSubType(tp: Type) = + def isProductSubType(tp: Type)(implicit ctx: Context) = (tp derivesFrom ProductClass) && tp.baseClasses.exists(ProductClasses contains _) - def isFunctionType(tp: Type) = { + def isFunctionType(tp: Type)(implicit ctx: Context) = { val arity = functionArity(tp) 0 <= arity && arity <= MaxFunctionArity && (tp isRef FunctionClass(arity)) } - def functionArity(tp: Type) = tp.dealias.argInfos.length - 1 + def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1 // ----- LambdaXYZ traits ------------------------------------------ @@ -544,7 +544,7 @@ class Definitions { val BooleanEnc = 17 val UnitEnc = 19 - def isValueSubClass(cls1: Symbol, cls2: Symbol) = + def isValueSubClass(cls1: Symbol, cls2: Symbol)(implicit ctx: Context) = valueClassEnc(cls2) % valueClassEnc(cls1) == 0 // ----- Initialization --------------------------------------------------- From 34202eb4e13921190bf4992ab33d9d69975f4940 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 14:48:42 +0200 Subject: [PATCH 26/47] Avoid some classes of StaleSymbol errors If a symbol is defined in phases M..N, and that symbol is then accessed in a phase before M, but in a new run, we should not issue a stale symbol error (after all, the symbol is not defined yet). Instead we now return a NoDenotation. --- src/dotty/tools/dotc/core/Denotations.scala | 29 +++++++++++++++++---- src/dotty/tools/dotc/core/Periods.scala | 7 +++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index a2c8af8867ef..43fff62ec388 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -494,7 +494,8 @@ object Denotations { } while (d ne denot) initial.syncWithParents case _ => - staleSymbolError + if (coveredInterval.containsPhaseId(ctx.phaseId)) staleSymbolError + else NoDenotation } /** Produce a denotation that is valid for the given context. @@ -570,14 +571,16 @@ object Denotations { //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") cur = cur.nextInRun cnt += 1 - assert(cnt <= MaxPossiblePhaseId, - s"demanding denotation of $this outside defined interval: defined periods are${definedPeriodsString}") + assert(cnt <= MaxPossiblePhaseId, demandOutsideDefinedMsg) } cur } } } + private def demandOutsideDefinedMsg(implicit ctx: Context): String = + s"demanding denotation of $this at phase ${ctx.phase}(${ctx.phaseId}) outside defined interval: defined periods are${definedPeriodsString}" + /** Install this denotation to be the result of the given denotation transformer. * This is the implementation of the same-named method in SymDenotations. * It's placed here because it needs access to private fields of SingleDenotation. @@ -616,6 +619,22 @@ object Denotations { throw new StaleSymbol(msg) } + /** The period (interval of phases) for which there exists + * a valid denotation in this flock. + */ + def coveredInterval(implicit ctx: Context): Period = { + var cur = this + var cnt = 0 + var interval = validFor + do { + cur = cur.nextInRun + cnt += 1 + assert(cnt <= MaxPossiblePhaseId, demandOutsideDefinedMsg) + interval |= cur.validFor + } while (cur ne this) + interval + } + /** For ClassDenotations only: * If caches influenced by parent classes are still valid, the denotation * itself, otherwise a freshly initialized copy. @@ -643,7 +662,7 @@ object Denotations { // ------ PreDenotation ops ---------------------------------------------- final def first = this - final def toDenot(pre: Type)(implicit ctx: Context) = 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) = exists && (signature matches sig) @@ -790,7 +809,7 @@ object Denotations { else DenotUnion(this, that) } - case class DenotUnion(denots1: PreDenotation, denots2: PreDenotation) extends PreDenotation { + final case class DenotUnion(denots1: PreDenotation, denots2: PreDenotation) extends PreDenotation { assert(denots1.exists && denots2.exists, s"Union of non-existing denotations ($denots1) and ($denots2)") def exists = true def first = denots1.first diff --git a/src/dotty/tools/dotc/core/Periods.scala b/src/dotty/tools/dotc/core/Periods.scala index 4ab04fad0eb3..e0d9e3b5d47f 100644 --- a/src/dotty/tools/dotc/core/Periods.scala +++ b/src/dotty/tools/dotc/core/Periods.scala @@ -67,6 +67,8 @@ object Periods { /** The first phase of this period */ def firstPhaseId = lastPhaseId - (code & PhaseMask) + def containsPhaseId(id: PhaseId) = firstPhaseId <= id && id <= lastPhaseId + /** Does this period contain given period? */ def contains(that: Period): Boolean = { // Let this = (r1, l1, d1), that = (r2, l2, d2) @@ -106,6 +108,11 @@ object Periods { else Nowhere + def | (that: Period): Period = + Period(this.runId, + this.firstPhaseId min that.firstPhaseId, + this.lastPhaseId max that.lastPhaseId) + override def toString = s"Period($firstPhaseId..$lastPhaseId, run = $runId)" } From 1ac95a7209439116750592931e43981127950ad4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 14:54:21 +0200 Subject: [PATCH 27/47] Add unique ids for definitions If -uniqid is on, RefinedPrinter now prints unique ids in definitions. --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index a327b4e3ea44..017434aa4596 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -194,6 +194,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => toTextGlobal(arg) } + def idText = if (ctx.settings.uniqid.value) "#" + tree.symbol.id else "" + import untpd._ var txt: Text = tree match { @@ -283,18 +285,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "(" ~ toTextGlobal(patterns, ", ") ~ ")" ~ ("(" ~ toTextGlobal(implicits, ", ") ~ ")" provided implicits.nonEmpty) case ValDef(mods, name, tpt, rhs) => - modText(mods, if (mods is Mutable) "var" else "val") ~~ toText(name) ~ + modText(mods, if (mods is Mutable) "var" else "val") ~~ toText(name) ~ idText ~ optAscription(tpt) ~ optText(rhs)(" = " ~ _) case DefDef(mods, name, tparams, vparamss, tpt, rhs) => atOwner(tree) { - val first = modText(mods, "def") ~~ toText(name) ~ tparamsText(tparams) + val first = modText(mods, "def") ~~ toText(name) ~ idText ~ tparamsText(tparams) addVparamssText(first, vparamss) ~ optAscription(tpt) ~ optText(rhs)(" = " ~ _) } case tree @ TypeDef(mods, name, rhs) => atOwner(tree) { def typeDefText(rhsText: Text) = { val rhsText1 = if (tree.hasType) toText(tree.symbol.info) else rhsText - modText(mods, "type") ~~ toText(name) ~ tparamsText(tree.tparams) ~ rhsText1 + modText(mods, "type") ~~ toText(name) ~ idText ~ tparamsText(tree.tparams) ~ rhsText1 } rhs match { case impl: Template => From 1bb16c4ec5a5c677d2b606e652733cd7a3867a2b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 14:55:20 +0200 Subject: [PATCH 28/47] Small cleanups and additions. --- src/dotty/tools/dotc/ast/TreeInfo.scala | 2 +- src/dotty/tools/dotc/core/Substituters.scala | 6 +++++- src/dotty/tools/dotc/core/Types.scala | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 78fb2d80b28d..1354f737551d 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -354,7 +354,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => private def refPurity(tree: tpd.Tree)(implicit ctx: Context): PurityLevel = if (!tree.tpe.widen.isParameterless) Pure else if (!tree.symbol.is(Stable)) Impure - else if (tree.symbol.is(Lazy)) Idempotent + else if (tree.symbol.is(Lazy)) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start. else Pure def isPureRef(tree: tpd.Tree)(implicit ctx: Context) = diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 1b96de47e225..3d14317cb76a 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -205,7 +205,11 @@ trait Substituters { this: Context => final class SubstMap(from: List[Symbol], to: List[Type]) extends DeepTypeMap { def apply(tp: Type): Type = subst(tp, from, to, this) } - +/* not needed yet + final class SubstDealiasMap(from: List[Symbol], to: List[Type]) extends SubstMap(from, to) { + override def apply(tp: Type): Type = subst(tp.dealias, from, to, this) + } +*/ final class SubstSymMap(from: List[Symbol], to: List[Symbol]) extends DeepTypeMap { def apply(tp: Type): Type = substSym(tp, from, to, this) } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index dcb1ae4913eb..89bef109ba54 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -813,11 +813,15 @@ object Types { if (from1.isEmpty) ctx.subst1(this, from.head, to.head, null) else { val from2 = from1.tail - if (from2.isEmpty) ctx.subst2(this, from.head, to.head, from.tail.head, to.tail.head, null) + if (from2.isEmpty) ctx.subst2(this, from.head, to.head, from1.head, to.tail.head, null) else ctx.subst(this, from, to, null) } } +/* Not needed yet: + final def substDealias(from: List[Symbol], to: List[Type])(implicit ctx: Context): Type = + new ctx.SubstDealiasMap(from, to).apply(this) +*/ /** Substitute all types of the form `PolyParam(from, N)` by * `PolyParam(to, N)`. */ From 290343cbd04d0f97818b0bec6f227c237c1e7293 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 15:34:26 +0200 Subject: [PATCH 29/47] Fixes to TypeUtils Some fixes to toStatic. Also added a method that transforms a RepeatedType to the corresponding Seq type. --- src/dotty/tools/dotc/transform/TypeUtils.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala index 87d47e0cd55f..efc0fdd24494 100644 --- a/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -7,6 +7,7 @@ import Contexts._ import Symbols._ import Decorators._ import StdNames.nme +import NameOps._ import language.implicitConversions object TypeUtils { @@ -36,10 +37,11 @@ class TypeUtils(val self: Type) extends AnyVal { def toStatic(clazz: ClassSymbol)(implicit ctx: Context): Type = { val (mtparamCount, origResult) = self match { case self @ PolyType(mtnames) => (mtnames.length, self.resultType) + case self: ExprType => (0, self.resultType) case _ => (0, self) } val ctparams = clazz.typeParams - val ctnames = ctparams.map(_.name) + val ctnames = ctparams.map(_.name.unexpandedName()) /** The method result type, prior to mapping any type parameters */ val resultType = { @@ -112,4 +114,8 @@ class TypeUtils(val self: Type) extends AnyVal { case _ => self } + + /** The Seq type corresponding to this repeated parameter type */ + def repeatedToSeq(implicit ctx: Context) = + self.translateParameterized(defn.RepeatedParamClass, defn.SeqClass) } \ No newline at end of file From dce9d6d3b2b60c8011c2e3362abc02bed9f26e19 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 15:35:31 +0200 Subject: [PATCH 30/47] New phase: ElimRepeated Makes sure there are no references to RepeatedParamClass left in the types. Also added placeholder for ElimLocals, which remains to be written. --- src/dotty/tools/dotc/ElimLocals.scala | 13 ++++ .../tools/dotc/transform/ElimRepeated.scala | 67 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/dotty/tools/dotc/ElimLocals.scala create mode 100644 src/dotty/tools/dotc/transform/ElimRepeated.scala diff --git a/src/dotty/tools/dotc/ElimLocals.scala b/src/dotty/tools/dotc/ElimLocals.scala new file mode 100644 index 000000000000..878783ffca0e --- /dev/null +++ b/src/dotty/tools/dotc/ElimLocals.scala @@ -0,0 +1,13 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import DenotTransformers._ + +/** Widens all private[this] and protected[this] qualifiers to just private/protected */ +abstract class ElimLocals extends TreeTransform with InfoTransformer { thisTransformer => + + // TODO complete + +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/ElimRepeated.scala b/src/dotty/tools/dotc/transform/ElimRepeated.scala new file mode 100644 index 000000000000..1333095191cf --- /dev/null +++ b/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -0,0 +1,67 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import Types._ +import TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import ast.Trees.flatten +import Flags._ +import Contexts.Context +import Symbols._ +import Denotations._, SymDenotations._ +import Decorators.StringInterpolators +import scala.collection.mutable +import DenotTransformers._ +import Names.Name +import NameOps._ +import TypeUtils._ + +/** A transformer that provides a convenient way to create companion objects + */ +class ElimRepeated extends TreeTransform with InfoTransformer { thisTransformer => + import ast.tpd._ + + override def name = "elimrepeated" + + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = + elimRepeated(tp) + + /** The transformation method */ + override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = { + val ref1 = super.transform(ref) + if (ref1 ne ref) + println(i"elim repeated ${ref.getClass} for ${ref.symbol.showLocated}; ${ref.info} --> ${ref1.info}, validfor = ${ref1.validFor}") + ref1 + } + + private def elimRepeated(tp: Type)(implicit ctx: Context): Type = tp.stripTypeVar match { + case tp @ MethodType(paramNames, paramTypes) => + val resultType1 = elimRepeated(tp.resultType) + val paramTypes1 = + if (paramTypes.nonEmpty && paramTypes.last.isRepeatedParam) { + paramTypes.init :+ paramTypes.last.repeatedToSeq + } + else paramTypes + tp.derivedMethodType(paramNames, paramTypes1, resultType1) + case tp: PolyType => + tp.derivedPolyType(tp.paramNames, tp.paramBounds, elimRepeated(tp.resultType)) + case tp => + tp + } + + def transformTypeOfTree(tree: Tree)(implicit ctx: Context): Tree = + tree.withType(elimRepeated(tree.tpe)) + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) +} From 1d4c5f69de68975df3bc95e461971e1e4a779ff6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 16:40:01 +0200 Subject: [PATCH 31/47] Revamped TreeTypeMap TreeTypeMap needs to track all binding occurrences and propagate maps to all usage occurrences. Also, fixed `ref` for prefixless types (these mapped to a SelectFromType tree before, now are mapped to Idents). --- src/dotty/tools/dotc/ast/tpd.scala | 84 ++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index d9c57bd2391c..e296572fbd91 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -4,7 +4,7 @@ package ast import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, Flags._ -import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Symbols._ import CheckTrees._, Denotations._, Decorators._ import config.Printers._ import typer.ErrorReporting._ @@ -251,7 +251,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** A tree representing the same reference as the given type */ def ref(tp: NamedType)(implicit ctx: Context): NameTree = - if (tp.symbol.isStatic) Ident(tp) + if (tp.symbol.isStatic || tp.prefix == NoPrefix) Ident(tp) else tp.prefix match { case pre: TermRef => ref(pre).select(tp) case pre => SelectFromTypeTree(TypeTree(pre), tp) @@ -413,26 +413,43 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def tpes: List[Type] = xs map (_.tpe) } - class TreeTypeMap(val typeMap: Type => Type = IdentityTypeMap, val ownerMap: Symbol => Symbol = identity _)(implicit ctx: Context) extends TreeMap { + final class TreeTypeMap( + val typeMap: Type => Type = IdentityTypeMap, + val ownerMap: Symbol => Symbol = identity _, + val treeMap: Tree => Tree = identity _)(implicit ctx: Context) extends TreeMap { + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = super.transform { - tree.withType(typeMap(tree.tpe)) match { - case bind: tpd.Bind => - val sym = bind.symbol - val newOwner = ownerMap(sym.owner) - val newInfo = typeMap(sym.info) - if ((newOwner ne sym.owner) || (newInfo ne sym.info)) - bind.withType(sym.copy(owner = newOwner, info = newInfo).namedType) - else - bind + treeMap(tree).withType(typeMap(tree.tpe)) match { + case ddef @ DefDef(mods, name, tparams, vparamss, tpt, rhs) => + val (tmap1, tparams1) = transformDefs(ddef.tparams) + val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss) + cpy.DefDef(ddef, mods, name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(rhs)) + case blk @ Block(stats, expr) => + val (tmap1, stats1) = transformDefs(stats) + cpy.Block(blk, stats1, tmap1.transform(expr)) + case cdef @ CaseDef(pat, guard, rhs) => + val tmap = withMappedSyms(patVars(pat)) + cpy.CaseDef(cdef, tmap.transform(pat), tmap.transform(guard), tmap.transform(rhs)) case tree1 => tree1 } } - override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context) = { - val locals = ta.localSyms(trees) - val mapped = ctx.mapSymbols(locals, typeMap, ownerMap) - if (locals eq mapped) super.transform(trees) - else withSubstitution(locals, mapped).transform(trees) + + override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context) = + transformDefs(trees)._2 + + private def transformDefs[TT <: tpd.Tree](trees: List[TT])(implicit ctx: Context): (TreeTypeMap, List[TT]) = { + val tmap = withMappedSyms(ta.localSyms(trees)) + (tmap, tmap.transformSub(trees)) + } + + private def transformVParamss(vparamss: List[List[ValDef]]): (TreeTypeMap, List[List[ValDef]]) = vparamss match { + case vparams :: rest => + val (tmap1, vparams1) = transformDefs(vparams) + val (tmap2, vparamss2) = tmap1.transformVParamss(rest) + (tmap2, vparams1 :: vparamss2) + case nil => + (this, vparamss) } def apply[ThisTree <: tpd.Tree](tree: ThisTree): ThisTree = transform(tree).asInstanceOf[ThisTree] @@ -443,10 +460,35 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } /** The current tree map composed with a substitution [from -> to] */ - def withSubstitution(from: List[Symbol], to: List[Symbol]) = - new TreeTypeMap( - typeMap andThen ((tp: Type) => tp.substSym(from, to)), - ownerMap andThen (from zip to).toMap) + def withSubstitution(from: List[Symbol], to: List[Symbol]): TreeTypeMap = + if (from eq to) this + else new TreeTypeMap( + typeMap andThen (_.substSym(from, to)), + ownerMap andThen { sym => + val idx = from.indexOf(sym) + if (idx >= 0) to(idx) else sym + }, + treeMap) + + /** Apply `typeMap` and `ownerMap` to given symbols `syms` + * and return a treemap that contains the substitution + * between original and mapped symbols. + */ + def withMappedSyms(syms: List[Symbol]): TreeTypeMap = { + val mapped = ctx.mapSymbols(syms, typeMap, ownerMap) + withSubstitution(syms, mapped) + } + } + + /** The variables defined by a pattern, in reverse order of their appearance. */ + def patVars(tree: Tree)(implicit ctx: Context): List[Symbol] = { + val acc = new TreeAccumulator[List[Symbol]] { + def apply(syms: List[Symbol], tree: Tree) = tree match { + case Bind(_, body) => apply(tree.symbol :: syms, body) + case _ => foldOver(syms, tree) + } + } + acc(Nil, tree) } // convert a numeric with a toXXX method From fc784b8cd338eecc98e1b23ea2f27d0c82e9fe87 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 16:57:59 +0200 Subject: [PATCH 32/47] Enabled ExtensionMethods Fixed extension methods so that it now runs and passes the build. Also enables ElimRepeated, which is a prerequistite for ExtensionMethods. Exception: Tailrec is currently disabled, because it needs to run before ExtensionMethods but it fails the -Ycheck test. Therefore the current tests skip this phase. --- src/dotty/tools/dotc/Compiler.scala | 4 +- .../dotc/transform/ExtensionMethods.scala | 51 ++++---- test/dotc/tests.scala | 2 +- tests/pos/Meter.scala | 109 ++++++++++++++++++ 4 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 tests/pos/Meter.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index b51f383f770e..dfed8ce9d905 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -19,10 +19,10 @@ class Compiler { def phases: List[List[Phase]] = List( List(new FrontEnd), - List(new Companions), + List(new Companions, new ElimRepeated /*, new ElimLocals*/), + List(new TailRec), List(new SuperAccessors), List(new ExtensionMethods), - List(new TailRec), List(new PatternMatcher, new LazyValTranformContext().transformer, new Splitter), diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index b64f529bf561..d0cdf976dff1 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -12,6 +12,7 @@ import scala.collection.{ mutable, immutable } import mutable.ListBuffer import scala.annotation.tailrec import core._ +import Phases.Phase import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ import TypeUtils._ @@ -29,11 +30,15 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th /** the following two members override abstract members in Transform */ val name: String = "extmethods" + override def runsAfter: Set[String] = Set("elimrepeated") // TODO: add tailrec + def newTransformer(implicit ctx: Context): Transformer = new Extender + override def transformPhase(implicit ctx: Context): Phase = thisTransformer.next + /** Generate stream of possible names for the extension version of given instance method `imeth`. - * If the method is not overloaded, this stream consists of just "extension$imeth". - * If the method is overloaded, the stream has as first element "extensionX$imeth", where X is the + * If the method is not overloaded, this stream consists of just "imeth$extension". + * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the * index of imeth in the sequence of overloaded alternatives with the same name. This choice will * always be picked as the name of the generated extension method. * After this first choice, all other possible indices in the range of 0 until the number @@ -44,6 +49,8 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = { val decl = imeth.owner.info.decl(imeth.name) + if (imeth.name.toString == "appliedTo") println(i"resolve: $decl") + /** No longer needed for Dotty, as we are more disciplined with scopes now. // Bridge generation is done at phase `erasure`, but new scopes are only generated // for the phase after that. So bridges are visible in earlier phases. @@ -54,14 +61,15 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe */ decl match { - case decl: SingleDenotation => + case decl: MultiDenotation => val alts = decl.alternatives val index = alts indexOf imeth.denot assert(index >= 0, alts+" does not contain "+imeth) def altName(index: Int) = (imeth.name+"$extension"+index).toTermName + if (imeth.name.toString == "appliedTo") println(i"resolve: $decl ${altName(index)}") altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName) - case tpe => - assert(tpe != NoType, imeth.name+" not found in "+imeth.owner+"'s decls: "+imeth.owner.info.decls) + case decl => + assert(decl.exists, imeth.name+" not found in "+imeth.owner+"'s decls: "+imeth.owner.info.decls) Stream((imeth.name+"$extension").toTermName) } } @@ -98,20 +106,20 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th ctx.error("value class may not unbox to itself", pos) else { val unboxed = underlyingOfValueClass(clazz).typeSymbol - if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed.asClass) + if (isDerivedValueClass(unboxed)) checkNonCyclic(pos, seen + clazz, unboxed.asClass) } override def transform(tree: Tree)(implicit ctx: Context): Tree = { tree match { case tree: Template => - if (ctx.owner.isDerivedValueClass) { + if (isDerivedValueClass(ctx.owner)) { /* This is currently redundant since value classes may not wrap over other value classes anyway. checkNonCyclic(ctx.owner.pos, Set(), ctx.owner) */ - extensionDefs(ctx.owner.companionModule) = new mutable.ListBuffer[Tree] + extensionDefs(ctx.owner.linkedClass) = new mutable.ListBuffer[Tree] ctx.owner.primaryConstructor.makeNotPrivateAfter(NoSymbol, thisTransformer) // SI-7859 make param accessors accessible so the erasure can generate unbox operations. - val paramAccessors = ctx.owner.info.decls.filter(_.is(ParamAccessor)) + val paramAccessors = ctx.owner.info.decls.filter(_.is(TermParamAccessor)) paramAccessors.foreach(_.makeNotPrivateAfter(ctx.owner, thisTransformer)) super.transform(tree) } else if (ctx.owner.isStaticOwner) { @@ -128,8 +136,8 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th val origClass = ctx.owner.asClass val origTParams = tparams.map(_.symbol) ::: origClass.typeParams // method type params ++ class type params val origVParams = vparamss.flatten map (_.symbol) - val staticClass = origClass.companionClass - assert(staticClass.exists) + val staticClass = origClass.linkedClass + assert(staticClass.exists, s"$origClass lacks companion, ${origClass.owner.definedPeriodsString} ${origClass.owner.info.decls} ${origClass.owner.info.decls}") val extensionMeth = ctx.atPhase(thisTransformer.next) { implicit ctx => val extensionName = extensionNames(origMeth).head.toTermName @@ -144,15 +152,18 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}") extensionDefs(staticClass) += polyDefDef(extensionMeth, trefs => vrefss => { - def methPart(tp: Type): MethodType = tp match { - case tp: PolyType => methPart(tp.resultType) - case tp: MethodType => tp - } - val substitutions: Type => Type = _ - .subst(origTParams, trefs) - .substSym(origVParams, vrefss.flatten.map(_.symbol)) - .substThis(origClass, MethodParam(methPart(extensionMeth.info), 0)) - new TreeTypeMap(substitutions, Map(origMeth -> extensionMeth)).transform(rhs) + val thisRef :: argRefs = vrefss.flatten + new TreeTypeMap( + typeMap = _ + .subst(origTParams, trefs) + .subst(origVParams, argRefs.map(_.tpe)) + .substThis(origClass, thisRef.tpe), + ownerMap = (sym => if (sym eq origMeth) extensionMeth else sym), + treeMap = { + case tree: This if tree.symbol == origClass => thisRef + case tree => tree + } + ).transform(rhs) }) // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index b1e0a6efe774..ac4e915c2bb7 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,7 +14,7 @@ class tests extends CompilerTest { "-pagewidth", "160") implicit val defaultOptions = noCheckOptions ++ List( - "-Ycheck:extmethods"//, "-Ystop-before:terminal" + "-Yskip:tailrec, -Ycheck:extmethods"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") diff --git a/tests/pos/Meter.scala b/tests/pos/Meter.scala new file mode 100644 index 000000000000..53be6f9d0801 --- /dev/null +++ b/tests/pos/Meter.scala @@ -0,0 +1,109 @@ +package a { + abstract class BoxingConversions[Boxed, Unboxed] { + def box(x: Unboxed): Boxed + def unbox(x: Boxed): Unboxed + } + + class Meter(val underlying: Double) extends AnyVal with _root_.b.Printable { + def + (other: Meter): Meter = + new Meter(this.underlying + other.underlying) + def / (other: Meter)(implicit dummy: Meter.MeterArg = null): Double = this.underlying / other.underlying + def / (factor: Double): Meter = new Meter(this.underlying / factor) + def < (other: Meter): Boolean = this.underlying < other.underlying + def toFoot: Foot = new Foot(this.underlying * 0.3048) + override def print = { Console.print(">>>"); super.print; proprint } + override def toString: String = underlying.toString+"m" + } + + object Meter extends (Double => Meter) { + + private[a] trait MeterArg + + def apply(x: Double): Meter = new Meter(x) + + implicit val boxings: BoxingConversions[Meter, Double] = new BoxingConversions[Meter, Double] { + def box(x: Double) = new Meter(x) + def unbox(m: Meter) = m.underlying + } + } + + class Foot(val unbox: Double) extends AnyVal { + def + (other: Foot): Foot = + new Foot(this.unbox + other.unbox) + override def toString = unbox.toString+"ft" + } + object Foot { + implicit val boxings: BoxingConversions[Foot, Double] = new BoxingConversions[Foot, Double] { + def box(x: Double) = new Foot(x) + def unbox(m: Foot) = m.unbox + } + } + +} +package b { + trait Printable extends Any { + def print: Unit = Console.print(this) + protected def proprint = Console.print("<<<") + } +} +import a._ +import _root_.b._ +object Test extends App { + + { + val x: Meter = new Meter(1) + val a: Object = x.asInstanceOf[Object] + val y: Meter = a.asInstanceOf[Meter] + + val u: Double = 1 + val b: Object = u.asInstanceOf[Object] + val v: Double = b.asInstanceOf[Double] + } + + val x = new Meter(1) + val y = x + //println((x + x) / x) + println((x + x) / 0.5) + println((x < x).toString) + println("x.isInstanceOf[Meter]: "+x.isInstanceOf[Meter]) + + + println("x.hashCode: "+x.hashCode) + println("x == 1: "+(x == 1)) + println("x == y: "+(x == y)) + assert(x.hashCode == (1.0).hashCode) + + val a: Any = x + val b: Any = y + println("a == b: "+(a == b)) + + { println("testing native arrays") + val arr = Array(x, y + x) + println(arr.deep) + def foo[T <: Printable](x: Array[T]) = { + for (i <- 0 until x.length) { x(i).print; println(" "+x(i)) } + } + val m = arr(0) + println(m) + foo(arr) + } + // + // { println("testing wrapped arrays") + // import collection.mutable.FlatArray + // val arr = FlatArray(x, y + x) + // println(arr) + // def foo(x: FlatArray[Meter]) { + // for (i <- 0 until x.length) { x(i).print; println(" "+x(i)) } + // } + // val m = arr(0) + // println(m) + // foo(arr) + // val ys: Seq[Meter] = arr map (_ + new Meter(1)) + // println(ys) + // val zs = arr map (_ / Meter(1)) + // println(zs) + // val fs = arr map (_.toFoot) + // println(fs) + // } + +} From 4a08703c16bba862f1d27e1fdd7484da128696a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 17:38:46 +0200 Subject: [PATCH 33/47] Silenced ElimRepeated. Removed diagnsostics message. --- src/dotty/tools/dotc/transform/ElimRepeated.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ElimRepeated.scala b/src/dotty/tools/dotc/transform/ElimRepeated.scala index 1333095191cf..be1040cacf17 100644 --- a/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -27,14 +27,6 @@ class ElimRepeated extends TreeTransform with InfoTransformer { thisTransformer def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = elimRepeated(tp) - /** The transformation method */ - override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = { - val ref1 = super.transform(ref) - if (ref1 ne ref) - println(i"elim repeated ${ref.getClass} for ${ref.symbol.showLocated}; ${ref.info} --> ${ref1.info}, validfor = ${ref1.validFor}") - ref1 - } - private def elimRepeated(tp: Type)(implicit ctx: Context): Type = tp.stripTypeVar match { case tp @ MethodType(paramNames, paramTypes) => val resultType1 = elimRepeated(tp.resultType) From 58f249d5d5acca1da3f4f2cdd417ba271d79819f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jul 2014 14:37:06 +0200 Subject: [PATCH 34/47] Simplification: dynamicSignature instead of toDynamic When figuring out which of a number of static methods corresponds to an overloaded dynamic method, we only need to compare signatures. No need to resconstruct the full dynamic type. --- .../dotc/transform/ExtensionMethods.scala | 7 +-- .../tools/dotc/transform/TypeUtils.scala | 47 ++++--------------- 2 files changed, 12 insertions(+), 42 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index d0cdf976dff1..afbb3c1f531b 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -49,8 +49,6 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = { val decl = imeth.owner.info.decl(imeth.name) - if (imeth.name.toString == "appliedTo") println(i"resolve: $decl") - /** No longer needed for Dotty, as we are more disciplined with scopes now. // Bridge generation is done at phase `erasure`, but new scopes are only generated // for the phase after that. So bridges are visible in earlier phases. @@ -66,7 +64,6 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th val index = alts indexOf imeth.denot assert(index >= 0, alts+" does not contain "+imeth) def altName(index: Int) = (imeth.name+"$extension"+index).toTermName - if (imeth.name.toString == "appliedTo") println(i"resolve: $decl ${altName(index)}") altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName) case decl => assert(decl.exists, imeth.name+" not found in "+imeth.owner+"'s decls: "+imeth.owner.info.decls) @@ -80,7 +77,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th // FIXME use toStatic instead? val companionInfo = imeth.owner.companionModule.info val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists) - val matching = candidates filter (alt => alt.info.toDynamic(imeth.owner) matches imeth.info) + val matching = candidates filter (_.info.dynamicSignature == imeth.signature) assert(matching.nonEmpty, sm"""|no extension method found for: | @@ -92,7 +89,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th | | Candidates (signatures normalized): | - | ${candidates.map(c => c.name + ":" + c.info.toDynamic(imeth.owner)).mkString("\n")} + | ${candidates.map(c => c.name + ":" + c.info.dynamicSignature).mkString("\n")} | | Eligible Names: ${extensionNames(imeth).mkString(",")}""") matching.head diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala index efc0fdd24494..851029888508 100644 --- a/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -74,45 +74,18 @@ class TypeUtils(val self: Type) extends AnyVal { } } - /** Converts from the result of a `toStatic(clazz)` back to the original type. - * - * To do this, it removes the `$this` argument from the parameter list a method, - * and converts trailing type parameters of the method to the type parameters of - * the given `clazz`. - * - * If `stpe` is a `PolyType`, any parameters corresponding to class type parameters - * are remapped and `$this` is removed from the result type. - * If `stpe` is a `MethodType`, it may have a curried parameter list with the - * `$this` alone in the first parameter list, in which case that parameter list - * is dropped. Or, since the curried lists disappear during uncurry, it may have - * a single parameter list with `$this` as the first parameter, in which case that - * parameter is removed from the list. Note that we do not need to adjust the result - * type with substParams because at uncurry there are no more depdendent method types. + /** Assuming `self` is a result of a `toStatic` call, the signature of the + * original method type `X` such that `self = X.toStatic`. */ - def toDynamic(clazz: Symbol)(implicit ctx: Context): Type = self match { - case self: PolyType => - // contains method type parameters, followed by class type parameters - val nparams = self.paramNames.length - clazz.typeParams.length - val (mNames, cNames) = self.paramNames.splitAt(nparams) - val (mBounds, cBounds) = self.paramBounds.splitAt(nparams) - val mappedParams = - (0 until nparams).toList.map(PolyParam(self, _)) ++ clazz.typeParams.map(_.typeRef) - def mapParams(tp: Type, pt: PolyType) = { - val mapped = (0 until nparams).toList.map(PolyParam(pt, _)) ++ clazz.typeParams.map(_.typeRef) - tp.substParams(self, mapped) + def dynamicSignature(implicit ctx: Context): Signature = self match { + case self: PolyType => self.resultType.dynamicSignature + case self @ MethodType(nme.SELF, _) => + val normalizedResultType = self.resultType match { + case rtp: MethodType => rtp + case rtp => ExprType(rtp) } - val restpe = self.resultType.toDynamic(clazz).substParams(self, mappedParams) - if (nparams == 0) mapParams(restpe, self) - else PolyType(self.paramNames.take(nparams))( - pt => self.paramBounds.mapconserve(mapParams(_, pt).asInstanceOf[TypeBounds]), - pt => mapParams(restpe, pt)) - case mt @ MethodType(nme.SELF :: otherNames, thizType :: otherTypes) => - val remainder = - if (otherNames.isEmpty) mt.resultType - else MethodType(otherNames, otherTypes, mt.resultType) - remainder.substParam(MethodParam(mt, 0), clazz.thisType) - case _ => - self + normalizedResultType.signature + case _ => Signature.NotAMethod } /** The Seq type corresponding to this repeated parameter type */ From ec972b74ada750b5565028053032204e7d31417d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jul 2014 17:45:09 +0200 Subject: [PATCH 35/47] Fix problem with dynamicSignature. Pattern match needs to compare against list of names, not single name. Universal equality bites again... Also fix comment for ElimRepeated. --- src/dotty/tools/dotc/transform/ElimRepeated.scala | 5 +++-- src/dotty/tools/dotc/transform/TypeUtils.scala | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ElimRepeated.scala b/src/dotty/tools/dotc/transform/ElimRepeated.scala index be1040cacf17..713127ea01d3 100644 --- a/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -17,8 +17,9 @@ import Names.Name import NameOps._ import TypeUtils._ -/** A transformer that provides a convenient way to create companion objects - */ +/** A transformer that removes repeated parameters (T*) from all types, replacing + * them with Seq types. + */ class ElimRepeated extends TreeTransform with InfoTransformer { thisTransformer => import ast.tpd._ diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala index 851029888508..d9671a190b8f 100644 --- a/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -79,13 +79,14 @@ class TypeUtils(val self: Type) extends AnyVal { */ def dynamicSignature(implicit ctx: Context): Signature = self match { case self: PolyType => self.resultType.dynamicSignature - case self @ MethodType(nme.SELF, _) => + case self @ MethodType(nme.SELF :: Nil, _) => val normalizedResultType = self.resultType match { case rtp: MethodType => rtp case rtp => ExprType(rtp) } normalizedResultType.signature - case _ => Signature.NotAMethod + case _ => + Signature.NotAMethod } /** The Seq type corresponding to this repeated parameter type */ From 47bf26634000950a3ca0185d8b260595c41487e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jul 2014 17:52:20 +0200 Subject: [PATCH 36/47] Made ExtensionMethods work as an InfoTransformer The previous version of ExtensionMethods added extension methods manually, in the companion objetct scope, using enteredAfter. Extension methods therefore had to run before pickling, so that other modules would see the generated extension methods. This is suboptimal, for two reasons; (1) Mixin is very much like extension methods, yet it has to run after pickling. (2) The pickling info gets larger than it has to be. With this commit, extension methods are added as an InfoTransformer. So extension methods now should run after pickling. --- .../dotc/transform/ExtensionMethods.scala | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index afbb3c1f531b..3e5932cfe454 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -23,7 +23,7 @@ import Decorators._ * Perform Step 1 in the inline classes SIP: Creates extension methods for all * methods in a value class, except parameter or super accessors, or constructors. */ -class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { thisTransformer => +class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransformer => import tpd._ @@ -32,6 +32,24 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th override def runsAfter: Set[String] = Set("elimrepeated") // TODO: add tailrec + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + case tp: ClassInfo if sym is ModuleClass => + sym.linkedClass match { + case origClass: ClassSymbol if isDerivedValueClass(origClass) => + val decls1 = tp.decls.cloneScope + ctx.atPhase(thisTransformer.next) { implicit ctx => + for (decl <- origClass.classInfo.decls) { + if (isMethodWithExtension(decl)) + decls1.enter(createExtensionMethod(decl, sym)) + } + } + tp.derivedClassInfo(decls = decls1) + case _ => tp + } + case _ => + tp + } + def newTransformer(implicit ctx: Context): Transformer = new Extender override def transformPhase(implicit ctx: Context): Phase = thisTransformer.next @@ -72,7 +90,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th } /** Return the extension method that corresponds to given instance method `meth`. */ - def extensionMethod(imeth: Symbol)(implicit ctx: Context): Symbol = + def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol = ctx.atPhase(thisTransformer.next) { implicit ctx => // FIXME use toStatic instead? val companionInfo = imeth.owner.companionModule.info @@ -81,20 +99,31 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th assert(matching.nonEmpty, sm"""|no extension method found for: | - | $imeth:${imeth.info} + | $imeth:${imeth.info.show} with signature ${imeth.signature} | | Candidates: | - | ${candidates.map(c => c.name + ":" + c.info).mkString("\n")} + | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")} | | Candidates (signatures normalized): | - | ${candidates.map(c => c.name + ":" + c.info.dynamicSignature).mkString("\n")} + | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + c.info.dynamicSignature).mkString("\n")} | | Eligible Names: ${extensionNames(imeth).mkString(",")}""") - matching.head + matching.head.asTerm } + private def createExtensionMethod(imeth: Symbol, staticClass: Symbol)(implicit ctx: Context): TermSymbol = { + assert(ctx.phase == thisTransformer.next) + val extensionName = extensionNames(imeth).head.toTermName + val extensionMeth = ctx.newSymbol(staticClass, extensionName, + imeth.flags | Final &~ (Override | Protected | AbsOverride), + imeth.info.toStatic(imeth.owner.asClass), + privateWithin = imeth.privateWithin, coord = imeth.coord) + extensionMeth.addAnnotations(from = imeth) + extensionMeth + } + class Extender extends Transformer { private val extensionDefs = mutable.Map[Symbol, mutable.ListBuffer[Tree]]() @@ -136,16 +165,8 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th val staticClass = origClass.linkedClass assert(staticClass.exists, s"$origClass lacks companion, ${origClass.owner.definedPeriodsString} ${origClass.owner.info.decls} ${origClass.owner.info.decls}") - val extensionMeth = ctx.atPhase(thisTransformer.next) { implicit ctx => - val extensionName = extensionNames(origMeth).head.toTermName - val extensionMeth = ctx.newSymbol(staticClass, extensionName, - origMeth.flags | Final &~ (Override | Protected | AbsOverride), - origMeth.info.toStatic(origClass), - privateWithin = origMeth.privateWithin, coord = tree.pos) - extensionMeth.addAnnotations(from = origMeth) - origMeth.removeAnnotation(defn.TailrecAnnotationClass) // it's on the extension method, now. - extensionMeth.enteredAfter(thisTransformer) - } + val extensionMeth = extensionMethod(origMeth) + ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}") extensionDefs(staticClass) += polyDefDef(extensionMeth, trefs => vrefss => { From 90965aba05dbd1595e010e53e4958e807b6fccea Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Jul 2014 14:00:14 +0200 Subject: [PATCH 37/47] Semantic printing When refined printing a DefTree, if one is after typer, print the symbol's definition plus the rhs of the tree rather than the contents of the tree. Why: After typer, it's always the symbol's contents that should matter. Question: Can we enforce through the types that parts of DefTree reflected by the symbol disappear/are ignored? --- .../tools/dotc/printing/PlainPrinter.scala | 2 ++ .../tools/dotc/printing/RefinedPrinter.scala | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index 3a322648aab0..ec4a4db88d44 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -319,6 +319,8 @@ class PlainPrinter(_ctx: Context) extends Printer { case _ => "" } + def annotsText(sym: Symbol): Text = Text(sym.annotations.map(toText)) + def dclText(sym: Symbol): Text = (toTextFlags(sym) ~~ keyString(sym) ~~ (varianceString(sym) ~ nameString(sym)) ~ toTextRHS(sym.unforcedInfo)).close diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 017434aa4596..acba22afeda5 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -194,7 +194,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => toTextGlobal(arg) } - def idText = if (ctx.settings.uniqid.value) "#" + tree.symbol.id else "" + def dclTextOr(treeText: => Text) = + if (ctx.isAfterTyper(ctx.phase) && tree.symbol != null && tree.symbol.exists) + annotsText(tree.symbol) ~~ dclText(tree.symbol) + else treeText import untpd._ @@ -285,19 +288,24 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "(" ~ toTextGlobal(patterns, ", ") ~ ")" ~ ("(" ~ toTextGlobal(implicits, ", ") ~ ")" provided implicits.nonEmpty) case ValDef(mods, name, tpt, rhs) => - modText(mods, if (mods is Mutable) "var" else "val") ~~ toText(name) ~ idText ~ - optAscription(tpt) ~ optText(rhs)(" = " ~ _) + dclTextOr { + modText(mods, if (mods is Mutable) "var" else "val") ~~ toText(name) ~ + optAscription(tpt) + } ~ optText(rhs)(" = " ~ _) case DefDef(mods, name, tparams, vparamss, tpt, rhs) => atOwner(tree) { - val first = modText(mods, "def") ~~ toText(name) ~ idText ~ tparamsText(tparams) - addVparamssText(first, vparamss) ~ optAscription(tpt) ~ optText(rhs)(" = " ~ _) + dclTextOr { + val first = modText(mods, "def") ~~ toText(name) ~ tparamsText(tparams) + addVparamssText(first, vparamss) ~ optAscription(tpt) + } ~ optText(rhs)(" = " ~ _) } case tree @ TypeDef(mods, name, rhs) => atOwner(tree) { - def typeDefText(rhsText: Text) = { - val rhsText1 = if (tree.hasType) toText(tree.symbol.info) else rhsText - modText(mods, "type") ~~ toText(name) ~ idText ~ tparamsText(tree.tparams) ~ rhsText1 - } + 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 + } rhs match { case impl: Template => modText(mods, if (mods is Trait) "trait" else "class") ~~ toText(name) ~ toText(impl) ~ From dee5f099e9f38ae3082cd88b67dadd970b65f9e2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Jul 2014 14:01:32 +0200 Subject: [PATCH 38/47] Avoid forming SeqLiterals with singleton type element. Singleton types are normally widened before inferring types, yet they did show up as elements of SeqLiterals. --- src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 48c263085336..ae56df82f19f 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -279,7 +279,7 @@ trait TypeAssigner { def assignType(tree: untpd.SeqLiteral, elems: List[Tree])(implicit ctx: Context) = { val ownType = if (ctx.erasedTypes) defn.SeqType - else defn.SeqType.appliedTo(ctx.typeComparer.lub(elems.tpes)) + else defn.SeqType.appliedTo(ctx.typeComparer.lub(elems.tpes).widen) tree.withType(ownType) } From 8a0ce6dda986ce149982e102e6962ac7d1e70784 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Jul 2014 14:06:28 +0200 Subject: [PATCH 39/47] Fixes to TreeTypeMap 1) Avoid double transformation of a tree 2) Apply the type map to the type of the tree-mapped tree, instead of to the type of the original tree. --- src/dotty/tools/dotc/ast/tpd.scala | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index e296572fbd91..fecfefd37ac4 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -413,13 +413,23 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def tpes: List[Type] = xs map (_.tpe) } + /** A map that applies three functions together to a tree and makes sure + * they are coordinated so that the result is well-typed. The functions are + * @param typeMap A function from Type to type that gets applied to the + * type of every tree node and to all locally defined symbols + * @param ownerMap A function that translates owners of top-level local symbols + * defined in the mapped tree. + * @param treeMap A transformer that translates all encountered subtrees in + * prefix traversal order. + */ final class TreeTypeMap( val typeMap: Type => Type = IdentityTypeMap, val ownerMap: Symbol => Symbol = identity _, val treeMap: Tree => Tree = identity _)(implicit ctx: Context) extends TreeMap { - override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = super.transform { - treeMap(tree).withType(typeMap(tree.tpe)) match { + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = { + val tree1 = treeMap(tree) + tree1.withType(typeMap(tree1.tpe)) match { case ddef @ DefDef(mods, name, tparams, vparamss, tpt, rhs) => val (tmap1, tparams1) = transformDefs(ddef.tparams) val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss) @@ -431,7 +441,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { val tmap = withMappedSyms(patVars(pat)) cpy.CaseDef(cdef, tmap.transform(pat), tmap.transform(guard), tmap.transform(rhs)) case tree1 => - tree1 + super.transform(tree1) } } From 65a85700146674d64f02aed1f2ec185a8ff41c7a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Jul 2014 14:08:17 +0200 Subject: [PATCH 40/47] Fixes to ReTyper 1) Disabled insertApplyOrImplicit - it leads to untyped trees which cause assertion errors down the road. In any case, neither apply methods nor implicits can be inserted when retyping. 2) Avoid copying tree annotations into symbols when retyping. --- src/dotty/tools/dotc/typer/ReTyper.scala | 15 +++++++++++---- src/dotty/tools/dotc/typer/Typer.scala | 15 ++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 76817fd16cb6..6adfa3052d44 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -1,10 +1,11 @@ package dotty.tools.dotc package typer -import core.Contexts._ -import core.Types._ -import core.Symbols._ -import core.Decorators._ +import core._ +import Contexts._ +import Types._ +import Symbols._ +import Decorators._ import typer.ProtoTypes._ import ast.{tpd, untpd} import ast.Trees._ @@ -59,4 +60,10 @@ class ReTyper extends Typer { override def localTyper(sym: Symbol) = this override def index(trees: List[untpd.Tree])(implicit ctx: Context) = ctx + + override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: (Tree, TyperState) => Tree)(implicit ctx: Context): Tree = + fallBack(tree, ctx.typerState) + + override def addTypedModifiersAnnotations(mods: untpd.Modifiers, sym: Symbol)(implicit ctx: Context): Modifiers = + typedModifiers(mods, sym) } \ No newline at end of file diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index c01cf714f8c1..6dc9a49b1479 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -744,9 +744,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit assignType(cpy.Alternative(tree, trees1), trees1) } + def addTypedModifiersAnnotations(mods: untpd.Modifiers, sym: Symbol)(implicit ctx: Context): Modifiers = { + val mods1 = typedModifiers(mods, sym) + for (tree <- mods1.annotations) sym.addAnnotation(Annotation(tree)) + mods1 + } + def typedModifiers(mods: untpd.Modifiers, sym: Symbol)(implicit ctx: Context): Modifiers = track("typedModifiers") { val annotations1 = mods.annotations mapconserve typedAnnotation - for (tree <- annotations1) sym.addAnnotation(Annotation(tree)) if (annotations1 eq mods.annotations) mods.asInstanceOf[Modifiers] else Modifiers(mods.flags, mods.privateWithin, annotations1) } @@ -757,7 +762,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context) = track("typedValDef") { val ValDef(mods, name, tpt, rhs) = vdef - val mods1 = typedModifiers(mods, sym) + val mods1 = addTypedModifiersAnnotations(mods, sym) val tpt1 = typedType(tpt) val rhs1 = rhs match { case Ident(nme.WILDCARD) => rhs withType tpt1.tpe @@ -768,7 +773,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { val DefDef(mods, name, tparams, vparamss, tpt, rhs) = ddef - val mods1 = typedModifiers(mods, sym) + val mods1 = addTypedModifiersAnnotations(mods, sym) val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef]) val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef]) if (sym is Implicit) checkImplicitParamsNotSingletons(vparamss1) @@ -780,7 +785,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context): Tree = track("typedTypeDef") { val TypeDef(mods, name, rhs) = tdef - val mods1 = typedModifiers(mods, sym) + val mods1 = addTypedModifiersAnnotations(mods, sym) val _ = typedType(rhs) // unused, typecheck only to remove from typedTree assignType(cpy.TypeDef(tdef, mods1, name, TypeTree(sym.info)), sym) } @@ -807,7 +812,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } val TypeDef(mods, name, impl @ Template(constr, parents, self, body)) = cdef - val mods1 = typedModifiers(mods, cls) + val mods1 = addTypedModifiersAnnotations(mods, cls) val constr1 = typed(constr).asInstanceOf[DefDef] val parents1 = ensureConstrCall(ensureFirstIsClass( parents mapconserve typedParent, cdef.pos.toSynthetic)) From 3f3581c1fdefbebf06d4b3d12c6474c2997ec52d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Jul 2014 14:12:00 +0200 Subject: [PATCH 41/47] Make ExtensionMethods not rely on TailRec This acommit allows TailRec to run after ExtensionMethods. This is due to the following changes: 1) Extension methods now "rewire" calls to other extension methods of the same class, so that they go directly to other extension methods instead of to the original method in the class. 2) Tailrec annotations get removed from original method and get added to the extension method instead. With this commit all tests can be re-enabled again. --- src/dotty/tools/dotc/Compiler.scala | 2 +- .../dotc/transform/ExtensionMethods.scala | 64 +++++++++++++++---- test/dotc/tests.scala | 4 +- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index dfed8ce9d905..95f74e2904af 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -20,9 +20,9 @@ class Compiler { List( List(new FrontEnd), List(new Companions, new ElimRepeated /*, new ElimLocals*/), - List(new TailRec), List(new SuperAccessors), List(new ExtensionMethods), + List(new TailRec), List(new PatternMatcher, new LazyValTranformContext().transformer, new Splitter), diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 3e5932cfe454..917db17a1bd5 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -23,7 +23,7 @@ import Decorators._ * Perform Step 1 in the inline classes SIP: Creates extension methods for all * methods in a value class, except parameter or super accessors, or constructors. */ -class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransformer => +class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransformer => import tpd._ @@ -32,22 +32,30 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo override def runsAfter: Set[String] = Set("elimrepeated") // TODO: add tailrec - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { - case tp: ClassInfo if sym is ModuleClass => - sym.linkedClass match { + override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { + case ref: ClassDenotation if ref is ModuleClass => + ref.linkedClass match { case origClass: ClassSymbol if isDerivedValueClass(origClass) => - val decls1 = tp.decls.cloneScope + val cinfo = ref.classInfo + val decls1 = cinfo.decls.cloneScope ctx.atPhase(thisTransformer.next) { implicit ctx => for (decl <- origClass.classInfo.decls) { if (isMethodWithExtension(decl)) - decls1.enter(createExtensionMethod(decl, sym)) + decls1.enter(createExtensionMethod(decl, ref.symbol)) } } - tp.derivedClassInfo(decls = decls1) - case _ => tp + if (decls1.isEmpty) ref + else ref.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1)) + case _ => + ref } + case ref: SymDenotation + if isMethodWithExtension(ref) && ref.hasAnnotation(defn.TailrecAnnotationClass) => + val ref1 = ref.copySymDenotation() + ref1.removeAnnotation(defn.TailrecAnnotationClass) + ref1 case _ => - tp + ref } def newTransformer(implicit ctx: Context): Transformer = new Extender @@ -120,7 +128,8 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo imeth.flags | Final &~ (Override | Protected | AbsOverride), imeth.info.toStatic(imeth.owner.asClass), privateWithin = imeth.privateWithin, coord = imeth.coord) - extensionMeth.addAnnotations(from = imeth) + extensionMeth.addAnnotations(from = imeth)(ctx.withPhase(thisTransformer)) + // need to change phase to add tailrec annotation which gets removed from original method in the same phase. extensionMeth } @@ -169,8 +178,41 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}") + def needsRewiring(sym: Symbol) = isMethodWithExtension(sym) && sym.owner == origClass + extensionDefs(staticClass) += polyDefDef(extensionMeth, trefs => vrefss => { val thisRef :: argRefs = vrefss.flatten + def rewireToStatic(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { + def rewireCall(thisArg: Tree): Tree = { + val sym = tree.symbol + if (needsRewiring(sym)) { + val base = thisArg.tpe.baseTypeWithArgs(origClass) + assert(base.exists) + ref(extensionMethod(sym).termRef) + .appliedToTypeTrees(targs ++ base.argInfos.map(TypeTree(_))) + .appliedTo(thisArg) + } else EmptyTree + } + tree match { + case Ident(_) => rewireCall(thisRef) + case Select(qual, _) => rewireCall(qual) + case tree @ TypeApply(fn, targs1) => + assert(targs.isEmpty) + rewireToStatic(fn, targs1) + case Block(stats, expr) => + val expr1 = rewireToStatic(expr, targs) + if (expr1.isEmpty) EmptyTree else Block(stats, expr1) withPos tree.pos + case _ => EmptyTree + } + } + val rewireType = new TypeMap { + def apply(tp: Type) = tp match { + case ref: TermRef if needsRewiring(ref.symbol) => + extensionMethod(ref.symbol).termRef + case _ => + mapOver(tp) + } + } new TreeTypeMap( typeMap = _ .subst(origTParams, trefs) @@ -179,7 +221,7 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo ownerMap = (sym => if (sym eq origMeth) extensionMeth else sym), treeMap = { case tree: This if tree.symbol == origClass => thisRef - case tree => tree + case tree => rewireToStatic(tree, Nil) orElse tree } ).transform(rhs) }) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index ac4e915c2bb7..f1896cbdfabd 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,7 +14,7 @@ class tests extends CompilerTest { "-pagewidth", "160") implicit val defaultOptions = noCheckOptions ++ List( - "-Yskip:tailrec, -Ycheck:extmethods"//, "-Ystop-before:terminal" + "-Ycheck:extmethods"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") @@ -78,7 +78,7 @@ class tests extends CompilerTest { @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1) @Test def neg_tailcall_t1672b = compileFile(negDir, "tailcall/t1672b", xerrors = 6) @Test def neg_tailcall_t3275 = compileFile(negDir, "tailcall/t3275", xerrors = 1) - @Test def neg_tailcall_t6574 = compileFile(negDir, "tailcall/t6574", xerrors = 4) + @Test def neg_tailcall_t6574 = compileFile(negDir, "tailcall/t6574", xerrors = 2) @Test def neg_tailcall = compileFile(negDir, "tailcall/tailrec", xerrors = 7) @Test def neg_tailcall2 = compileFile(negDir, "tailcall/tailrec-2", xerrors = 2) @Test def neg_tailcall3 = compileFile(negDir, "tailcall/tailrec-3", xerrors = 2) From 57d69f8f4d00bef8cbb51d498b5139ca649a8ca6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Jul 2014 17:59:02 +0200 Subject: [PATCH 42/47] Refactored reusable full parameterization code into base trait. Methods dealing with fully parameterized versions of classes were pulled from `TypeUtils` and `ExtensionMethods` into `FullyParameterization`. `TypeUtils` is left for possible future use, but is empty right now. --- .../tools/dotc/transform/ElimRepeated.scala | 3 +- .../dotc/transform/ExtensionMethods.scala | 86 ++------- .../dotc/transform/FullParameterization.scala | 178 ++++++++++++++++++ .../tools/dotc/transform/TypeUtils.scala | 74 +------- 4 files changed, 197 insertions(+), 144 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/FullParameterization.scala diff --git a/src/dotty/tools/dotc/transform/ElimRepeated.scala b/src/dotty/tools/dotc/transform/ElimRepeated.scala index 713127ea01d3..a362aee07967 100644 --- a/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -33,7 +33,8 @@ class ElimRepeated extends TreeTransform with InfoTransformer { thisTransformer val resultType1 = elimRepeated(tp.resultType) val paramTypes1 = if (paramTypes.nonEmpty && paramTypes.last.isRepeatedParam) { - paramTypes.init :+ paramTypes.last.repeatedToSeq + paramTypes.init :+ + paramTypes.last.translateParameterized(defn.RepeatedParamClass, defn.SeqClass) } else paramTypes tp.derivedMethodType(paramNames, paramTypes1, resultType1) diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 917db17a1bd5..ba5b9fab64ee 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -10,7 +10,6 @@ import ValueClasses._ import dotty.tools.dotc.ast.{Trees, tpd} import scala.collection.{ mutable, immutable } import mutable.ListBuffer -import scala.annotation.tailrec import core._ import Phases.Phase import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ @@ -23,7 +22,7 @@ import Decorators._ * Perform Step 1 in the inline classes SIP: Creates extension methods for all * methods in a value class, except parameter or super accessors, or constructors. */ -class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransformer => +class ExtensionMethods extends MacroTransform with DenotTransformer with FullParameterization { thisTransformer => import tpd._ @@ -62,6 +61,11 @@ class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransf override def transformPhase(implicit ctx: Context): Phase = thisTransformer.next + protected def rewiredTarget(target: Symbol, derived: Symbol)(implicit ctx: Context): Symbol = + if (isMethodWithExtension(target) && + target.owner.linkedClass == derived.owner) extensionMethod(target) + else NoSymbol + /** Generate stream of possible names for the extension version of given instance method `imeth`. * If the method is not overloaded, this stream consists of just "imeth$extension". * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the @@ -103,7 +107,7 @@ class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransf // FIXME use toStatic instead? val companionInfo = imeth.owner.companionModule.info val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists) - val matching = candidates filter (_.info.dynamicSignature == imeth.signature) + val matching = candidates filter (c => memberSignature(c.info) == imeth.signature) assert(matching.nonEmpty, sm"""|no extension method found for: | @@ -115,7 +119,7 @@ class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransf | | Candidates (signatures normalized): | - | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + c.info.dynamicSignature).mkString("\n")} + | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + memberSignature(c.info)).mkString("\n")} | | Eligible Names: ${extensionNames(imeth).mkString(",")}""") matching.head.asTerm @@ -126,7 +130,7 @@ class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransf val extensionName = extensionNames(imeth).head.toTermName val extensionMeth = ctx.newSymbol(staticClass, extensionName, imeth.flags | Final &~ (Override | Protected | AbsOverride), - imeth.info.toStatic(imeth.owner.asClass), + fullyParameterizedType(imeth.info, imeth.owner.asClass), privateWithin = imeth.privateWithin, coord = imeth.coord) extensionMeth.addAnnotations(from = imeth)(ctx.withPhase(thisTransformer)) // need to change phase to add tailrec annotation which gets removed from original method in the same phase. @@ -166,74 +170,16 @@ class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransf tree1 } } else tree - case DefDef(mods, name, tparams, vparamss, tpt, rhs) if isMethodWithExtension(tree.symbol) => - val origMeth = tree.symbol - val origClass = ctx.owner.asClass - val origTParams = tparams.map(_.symbol) ::: origClass.typeParams // method type params ++ class type params - val origVParams = vparamss.flatten map (_.symbol) - val staticClass = origClass.linkedClass + case ddef: DefDef if isMethodWithExtension(tree.symbol) => + val origMeth = tree.symbol + val origClass = ctx.owner.asClass + val staticClass = origClass.linkedClass assert(staticClass.exists, s"$origClass lacks companion, ${origClass.owner.definedPeriodsString} ${origClass.owner.info.decls} ${origClass.owner.info.decls}") - val extensionMeth = extensionMethod(origMeth) - ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}") - - def needsRewiring(sym: Symbol) = isMethodWithExtension(sym) && sym.owner == origClass - - extensionDefs(staticClass) += polyDefDef(extensionMeth, trefs => vrefss => { - val thisRef :: argRefs = vrefss.flatten - def rewireToStatic(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { - def rewireCall(thisArg: Tree): Tree = { - val sym = tree.symbol - if (needsRewiring(sym)) { - val base = thisArg.tpe.baseTypeWithArgs(origClass) - assert(base.exists) - ref(extensionMethod(sym).termRef) - .appliedToTypeTrees(targs ++ base.argInfos.map(TypeTree(_))) - .appliedTo(thisArg) - } else EmptyTree - } - tree match { - case Ident(_) => rewireCall(thisRef) - case Select(qual, _) => rewireCall(qual) - case tree @ TypeApply(fn, targs1) => - assert(targs.isEmpty) - rewireToStatic(fn, targs1) - case Block(stats, expr) => - val expr1 = rewireToStatic(expr, targs) - if (expr1.isEmpty) EmptyTree else Block(stats, expr1) withPos tree.pos - case _ => EmptyTree - } - } - val rewireType = new TypeMap { - def apply(tp: Type) = tp match { - case ref: TermRef if needsRewiring(ref.symbol) => - extensionMethod(ref.symbol).termRef - case _ => - mapOver(tp) - } - } - new TreeTypeMap( - typeMap = _ - .subst(origTParams, trefs) - .subst(origVParams, argRefs.map(_.tpe)) - .substThis(origClass, thisRef.tpe), - ownerMap = (sym => if (sym eq origMeth) extensionMeth else sym), - treeMap = { - case tree: This if tree.symbol == origClass => thisRef - case tree => rewireToStatic(tree, Nil) orElse tree - } - ).transform(rhs) - }) - - // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this) - // which leaves the actual argument application for extensionCall. - val forwarder = ref(extensionMeth.termRef) - .appliedToTypes(origTParams.map(_.typeRef)) - .appliedTo(This(origClass)) - .appliedToArgss(vparamss.nestedMap(vparam => ref(vparam.symbol))) - .withPos(rhs.pos) - cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, forwarder) + extensionDefs(staticClass) += fullyParameterizedDef(extensionMeth, ddef) + cpy.DefDef(tree, ddef.mods, ddef.name, ddef.tparams, ddef.vparamss, ddef.tpt, + forwarder(extensionMeth, ddef)) case _ => super.transform(tree) } diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala new file mode 100644 index 000000000000..3f02e11a753a --- /dev/null +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -0,0 +1,178 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import Contexts._ +import Symbols._ +import Decorators._ +import StdNames.nme +import NameOps._ +import ast._ +import ast.Trees._ + +/** Provides methods to produce fully parameterized versions of instance methods, + * where the `this` of the enclosing class is abstracted out in an extra leading + * `$this` parameter and type parameters of the class become additional type + * parameters of the fully parameterized method. + * + * Example usage scenarios are: + * + * - extension methods of value classes + * - implementations of trait methods + * - static protected accessors + * - local methods produced by tailrec transform + */ +trait FullParameterization { + + import tpd._ + + /** If references to original `target` from fully parameterized method `derived` should be + * rewired to some fully parameterized method, that method symbol, + * otherwise NoSymbol. + */ + protected def rewiredTarget(target: Symbol, derived: Symbol)(implicit ctx: Context): Symbol + + /** Converts the type `info` of a member of class `clazz` to a method type that + * takes the `this` of the class and any type parameters of the class + * as additional parameters. Example: + * + * class Foo[+A <: AnyRef](val xs: List[A]) extends AnyVal { + * def baz[B >: A](x: B): List[B] = ... + * } + * + * leads to: + * + * object Foo { + * def extension$baz[B >: A <: Any, A >: Nothing <: AnyRef]($this: Foo[A])(x: B): List[B] + * } + */ + def fullyParameterizedType(info: Type, clazz: ClassSymbol)(implicit ctx: Context): Type = { + val (mtparamCount, origResult) = info match { + case info @ PolyType(mtnames) => (mtnames.length, info.resultType) + case info: ExprType => (0, info.resultType) + case _ => (0, info) + } + val ctparams = clazz.typeParams + val ctnames = ctparams.map(_.name.unexpandedName()) + + /** The method result type, prior to mapping any type parameters */ + val resultType = { + val thisParamType = clazz.typeRef.appliedTo(ctparams.map(_.typeRef)) + MethodType(nme.SELF :: Nil, thisParamType :: Nil)(mt => + origResult.substThis(clazz, MethodParam(mt, 0))) + } + + /** Replace class type parameters by the added type parameters of the polytype `pt` */ + def mapClassParams(tp: Type, pt: PolyType): Type = { + val classParamsRange = (mtparamCount until mtparamCount + ctparams.length).toList + tp.subst(clazz.typeParams, classParamsRange map (PolyParam(pt, _))) + } + + /** The bounds for the added type paraneters of the polytype `pt` */ + def mappedClassBounds(pt: PolyType): List[TypeBounds] = + ctparams.map(tparam => mapClassParams(tparam.info, pt).bounds) + + def mappedResultType(pt: PolyType): Type = mapClassParams(resultType, pt) + + info match { + case info @ PolyType(mtnames) => + PolyType(mtnames ++ ctnames)( + pt => (info.paramBounds ++ mappedClassBounds(pt)) + .mapConserve(_.subst(info, pt).bounds), + pt => mappedResultType(pt).subst(info, pt)) + case _ => + if (ctparams.isEmpty) resultType + else PolyType(ctnames)(mappedClassBounds, mappedResultType) + } + } + + /** Assuming `info` is a result of a `fullyParameterizedType` call, the signature of the + * original method type `X` such that `info = fullyParameterizedType(X, ...)`. + */ + def memberSignature(info: Type)(implicit ctx: Context): Signature = info match { + case info: PolyType => memberSignature(info.resultType) + case info @ MethodType(nme.SELF :: Nil, _) => + val normalizedResultType = info.resultType match { + case rtp: MethodType => rtp + case rtp => ExprType(rtp) + } + normalizedResultType.signature + case _ => + Signature.NotAMethod + } + + /** The type parameters (skolems) of the method definition `originalDef`, + * followed by the class parameters of its enclosing class. + */ + private def allInstanceTypeParams(originalDef: DefDef)(implicit ctx: Context): List[Symbol] = + originalDef.tparams.map(_.symbol) ::: originalDef.symbol.owner.typeParams + + /** Given an instance method definition `originalDef`, return a + * fully parameterized method definition derived from `originalDef`, which + * has`derived` as symbol and `fullyParameterizedType(originalDef.symbol.info)` + * as info. + */ + def fullyParameterizedDef(derived: TermSymbol, originalDef: DefDef)(implicit ctx: Context): Tree = { + val origMeth = originalDef.symbol + val origClass = origMeth.owner.asClass + val origTParams = allInstanceTypeParams(originalDef) + val origVParams = originalDef.vparamss.flatten map (_.symbol) + polyDefDef(derived, trefs => vrefss => { + val thisRef :: argRefs = vrefss.flatten + + /** If tree should be rewired, the rewired tree, otherwise EmptyTree. + * @param targs Any type arguments passed to the rewired tree. + */ + def rewire(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { + def rewireCall(thisArg: Tree): Tree = { + val sym = tree.symbol + val rewired = rewiredTarget(sym, derived) + if (rewired.exists) { + val base = thisArg.tpe.baseTypeWithArgs(origClass) + assert(base.exists) + ref(rewired.termRef) + .appliedToTypeTrees(targs ++ base.argInfos.map(TypeTree(_))) + .appliedTo(thisArg) + } else EmptyTree + } + tree match { + case Ident(_) => rewireCall(thisRef) + case Select(qual, _) => rewireCall(qual) + case tree @ TypeApply(fn, targs1) => + assert(targs.isEmpty) + rewire(fn, targs1) + case Block(stats, expr) => + // need special casing here, because an original termRef might leak out as + // the result of the Block if Block is copied using `cpy`. + val expr1 = rewire(expr, targs) + if (expr1.isEmpty) EmptyTree else Block(stats, expr1) withPos tree.pos + case _ => EmptyTree + } + } + + new TreeTypeMap( + typeMap = _ + .subst(origTParams, trefs) + .subst(origVParams, argRefs.map(_.tpe)) + .substThis(origClass, thisRef.tpe), + ownerMap = (sym => if (sym eq origMeth) derived else sym), + treeMap = { + case tree: This if tree.symbol == origClass => thisRef + case tree => rewire(tree, Nil) orElse tree + }).transform(originalDef.rhs) + }) + } + + /** A forwarder expression which calls `derived`, passing along + * - the type parameters and enclosing class parameters of `originalDef`, + * - the `this` of the enclosing class, + * - the value parameters of the original method `originalDef`. + */ + def forwarder(derived: TermSymbol, originalDef: DefDef)(implicit ctx: Context): Tree = + ref(derived.termRef) + .appliedToTypes(allInstanceTypeParams(originalDef).map(_.typeRef)) + .appliedTo(This(originalDef.symbol.owner.asClass)) + .appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol))) + .withPos(originalDef.rhs.pos) +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala index d9671a190b8f..f11bb980acfc 100644 --- a/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -15,81 +15,9 @@ object TypeUtils { } /** A decorator that provides methods for type transformations - * that are needed in the transofmer pipeline + * that are needed in the transofmer pipeline (not needed right now) */ class TypeUtils(val self: Type) extends AnyVal { import TypeUtils._ - /** Converts the type of a member of class `clazz` to a method type that - * takes the `this` of the class and any type parameters of the class - * as additional parameters. Example: - * - * class Foo[+A <: AnyRef](val xs: List[A]) extends AnyVal { - * def baz[B >: A](x: B): List[B] = ... - * } - * - * leads to: - * - * object Foo { - * def extension$baz[B >: A <: Any, A >: Nothing <: AnyRef]($this: Foo[A])(x: B): List[B] - * } - */ - def toStatic(clazz: ClassSymbol)(implicit ctx: Context): Type = { - val (mtparamCount, origResult) = self match { - case self @ PolyType(mtnames) => (mtnames.length, self.resultType) - case self: ExprType => (0, self.resultType) - case _ => (0, self) - } - val ctparams = clazz.typeParams - val ctnames = ctparams.map(_.name.unexpandedName()) - - /** The method result type, prior to mapping any type parameters */ - val resultType = { - val thisParamType = clazz.typeRef.appliedTo(ctparams.map(_.typeRef)) - MethodType(nme.SELF :: Nil, thisParamType :: Nil)(mt => - origResult.substThis(clazz, MethodParam(mt, 0))) - } - - /** Replace class type parameters by the added type parameters of the polytype `pt` */ - def mapClassParams(tp: Type, pt: PolyType): Type = { - val classParamsRange = (mtparamCount until mtparamCount + ctparams.length).toList - tp.subst(clazz.typeParams, classParamsRange map (PolyParam(pt, _))) - } - - /** The bounds for the added type paraneters of the polytype `pt` */ - def mappedClassBounds(pt: PolyType): List[TypeBounds] = - ctparams.map(tparam => mapClassParams(tparam.info, pt).bounds) - - def mappedResultType(pt: PolyType): Type = mapClassParams(resultType, pt) - - self match { - case self @ PolyType(mtnames) => - PolyType(mtnames ++ ctnames)( - pt => (self.paramBounds ++ mappedClassBounds(pt)) - .mapConserve(_.subst(self, pt).bounds), - pt => mappedResultType(pt).subst(self, pt)) - case _ => - if (ctparams.isEmpty) resultType - else PolyType(ctnames)(mappedClassBounds, mappedResultType) - } - } - - /** Assuming `self` is a result of a `toStatic` call, the signature of the - * original method type `X` such that `self = X.toStatic`. - */ - def dynamicSignature(implicit ctx: Context): Signature = self match { - case self: PolyType => self.resultType.dynamicSignature - case self @ MethodType(nme.SELF :: Nil, _) => - val normalizedResultType = self.resultType match { - case rtp: MethodType => rtp - case rtp => ExprType(rtp) - } - normalizedResultType.signature - case _ => - Signature.NotAMethod - } - - /** The Seq type corresponding to this repeated parameter type */ - def repeatedToSeq(implicit ctx: Context) = - self.translateParameterized(defn.RepeatedParamClass, defn.SeqClass) } \ No newline at end of file From ace968dabf8e02f725f7b77cde90f64988a7b326 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 14 Jul 2014 13:17:34 +0200 Subject: [PATCH 43/47] Fix to rewire types t6574 has a new test where we produce identical code in an if-then-else. This broke the rewiring logic before, and is fixed now. Also, more comments and test cases. --- .../dotc/transform/FullParameterization.scala | 69 ++++++++++++++----- .../class-dependent-extension-method.scala | 3 + tests/pos/enum-interop.scala | 13 ++++ tests/pos/tailcall/t6574.scala | 8 ++- 4 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 tests/pending/pos/class-dependent-extension-method.scala create mode 100644 tests/pos/enum-interop.scala diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala index 3f02e11a753a..092e16086fdb 100644 --- a/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -22,6 +22,31 @@ import ast.Trees._ * - implementations of trait methods * - static protected accessors * - local methods produced by tailrec transform + * + * Note that the methods lift out type parameters of the class containing + * the instance method, but not type parameters of enclosing classes. The + * fully instantiated method therefore needs to be put in a scope "close" + * to the original method, i.e. they need to share the same outer pointer. + * Examples of legal positions are: in the companion object, or as a local + * method inside the original method. + * + * Note: The scheme does not handle yet methods where type parameter bounds + * depend on value parameters of the enclosing class, as in: + * + * class C(val a: String) extends AnyVal { + * def foo[U <: a.type]: Unit = ... + * } + * + * The expansion of method `foo` would lead to + * + * def foo$extension[U <: $this.a.type]($this: C): Unit = ... + * + * which is not typable. Not clear yet what to do. Maybe allow PolyTypes + * to follow method parameters and translate to the following: + * + * def foo$extension($this: C)[U <: $this.a.type]: Unit = ... + * + * @see class-dependent-extension-method.scala in pending/pos. */ trait FullParameterization { @@ -110,21 +135,21 @@ trait FullParameterization { /** Given an instance method definition `originalDef`, return a * fully parameterized method definition derived from `originalDef`, which - * has`derived` as symbol and `fullyParameterizedType(originalDef.symbol.info)` + * has `derived` as symbol and `fullyParameterizedType(originalDef.symbol.info)` * as info. */ - def fullyParameterizedDef(derived: TermSymbol, originalDef: DefDef)(implicit ctx: Context): Tree = { - val origMeth = originalDef.symbol - val origClass = origMeth.owner.asClass - val origTParams = allInstanceTypeParams(originalDef) - val origVParams = originalDef.vparamss.flatten map (_.symbol) + def fullyParameterizedDef(derived: TermSymbol, originalDef: DefDef)(implicit ctx: Context): Tree = polyDefDef(derived, trefs => vrefss => { + val origMeth = originalDef.symbol + val origClass = origMeth.owner.asClass + val origTParams = allInstanceTypeParams(originalDef) + val origVParams = originalDef.vparamss.flatten map (_.symbol) val thisRef :: argRefs = vrefss.flatten /** If tree should be rewired, the rewired tree, otherwise EmptyTree. * @param targs Any type arguments passed to the rewired tree. */ - def rewire(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { + def rewireTree(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { def rewireCall(thisArg: Tree): Tree = { val sym = tree.symbol val rewired = rewiredTarget(sym, derived) @@ -141,28 +166,40 @@ trait FullParameterization { case Select(qual, _) => rewireCall(qual) case tree @ TypeApply(fn, targs1) => assert(targs.isEmpty) - rewire(fn, targs1) - case Block(stats, expr) => - // need special casing here, because an original termRef might leak out as - // the result of the Block if Block is copied using `cpy`. - val expr1 = rewire(expr, targs) - if (expr1.isEmpty) EmptyTree else Block(stats, expr1) withPos tree.pos + rewireTree(fn, targs1) case _ => EmptyTree } } + /** Type rewiring is needed because a previous reference to an instance + * method might still persist in the types of enclosing nodes. Example: + * + * if (true) this.imeth else this.imeth + * + * is rewritten to + * + * if (true) xmeth($this) else xmeth($this) + * + * but the type `this.imeth` still persists as the result type of the `if`, + * because it is kept by the `cpy` operation of the tree transformer. + * It needs to be rewritten to the common result type of `imeth` and `xmeth`. + */ + def rewireType(tpe: Type) = tpe match { + case tpe: TermRef if rewiredTarget(tpe.symbol, derived).exists => tpe.widen + case _ => tpe + } + new TreeTypeMap( - typeMap = _ + typeMap = rewireType(_) .subst(origTParams, trefs) .subst(origVParams, argRefs.map(_.tpe)) .substThis(origClass, thisRef.tpe), ownerMap = (sym => if (sym eq origMeth) derived else sym), treeMap = { case tree: This if tree.symbol == origClass => thisRef - case tree => rewire(tree, Nil) orElse tree + case tree => rewireTree(tree, Nil) orElse tree }).transform(originalDef.rhs) }) - } /** A forwarder expression which calls `derived`, passing along * - the type parameters and enclosing class parameters of `originalDef`, diff --git a/tests/pending/pos/class-dependent-extension-method.scala b/tests/pending/pos/class-dependent-extension-method.scala new file mode 100644 index 000000000000..b557dfa8f08d --- /dev/null +++ b/tests/pending/pos/class-dependent-extension-method.scala @@ -0,0 +1,3 @@ +class C(val a: String) extends AnyVal { + def foo[U <: a.type]: Unit = foo[U] +} diff --git a/tests/pos/enum-interop.scala b/tests/pos/enum-interop.scala new file mode 100644 index 000000000000..0c9aca79bbe7 --- /dev/null +++ b/tests/pos/enum-interop.scala @@ -0,0 +1,13 @@ + + +object Test { + + val cls: java.lang.Class[_] = ??? + + def myAsInstanceOf[T <: Class[T]](cls: java.lang.Class[_]): Class[T] = cls.asInstanceOf[Class[T]] + Enum.valueOf(myAsInstanceOf(cls), "") + + + + +} diff --git a/tests/pos/tailcall/t6574.scala b/tests/pos/tailcall/t6574.scala index cd0fdbb8dcd7..d8377ddbc799 100644 --- a/tests/pos/tailcall/t6574.scala +++ b/tests/pos/tailcall/t6574.scala @@ -4,8 +4,12 @@ class Bad[X, Y](val v: Int) extends AnyVal { this.foo[Z](a)(b) } - @annotation.tailrec final def differentReceiver : Unit = { - {(); new Bad[X, Y](0)}.differentReceiver + @annotation.tailrec final def differentReceiver : Unit = + {(); new Bad[X, Y](0)}.differentReceiver + + @annotation.tailrec final def differentReceiver2 : Unit = { + if (true) {(); new Bad[X, Y](0)}.differentReceiver2 + else {(); new Bad[X, Y](0)}.differentReceiver2 } @annotation.tailrec final def dependent[Z](a: Int)(b: String): b.type = { From 37c6e42cfd04c62c504a9143cb2cc4b500baf38b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jul 2014 16:04:14 +0200 Subject: [PATCH 44/47] Fixed problem with missing denotations in polyDefDef Problem was reported by @darkdimius. Test case will come in next commit. --- src/dotty/tools/dotc/core/Symbols.scala | 4 ++++ src/dotty/tools/dotc/typer/Applications.scala | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index ab083a128037..1767d7c0c5ca 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -357,6 +357,10 @@ object Symbols { final def asType(implicit ctx: Context): TypeSymbol = { assert(isType, s"isType called on not-a-Type $this"); asInstanceOf[TypeSymbol] } final def asClass: ClassSymbol = asInstanceOf[ClassSymbol] + /** Special cased here, because it may be used on naked symbols in substituters */ + final def isStatic(implicit ctx: Context): Boolean = + lastDenot != null && denot.isStatic + /** A unique, densely packed integer tag for each class symbol, -1 * for all other symbols. To save memory, this method * should be called only if class is a super class of some other class. diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 99bb8284475d..242985b57ffc 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -767,8 +767,7 @@ trait Applications extends Compatibility { self: Typer => */ def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = ctx.traceIndented(i"isAsSpecific $tp1 $tp2", overload) { tp1 match { case tp1: PolyType => - def bounds(tparamRefs: List[TypeRef]) = tp1.paramBounds map (_.substParams(tp1, tparamRefs)) - val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, bounds) + val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateBounds) isAsSpecific(alt1, tp1.instantiate(tparams map (_.typeRef)), alt2, tp2) case tp1: MethodType => def repeatedToSingle(tp: Type) = if (tp.isRepeatedParam) tp.argTypesHi.head else tp From 7c56a5bb3d0b7902dbee6f11788e2d1033b20873 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jul 2014 22:29:27 +0200 Subject: [PATCH 45/47] Make self types always include a reference to the class In a situation like class C { this: D => } the effective self type of the class is taken to be D & C. Why? First, Scala 2.x does it that way. Second, it's needed to make the FullParameterization transform go though. --- src/dotty/tools/dotc/core/Types.scala | 28 +++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 89bef109ba54..289515ae1c05 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2030,19 +2030,31 @@ object Types { decls: Scope, selfInfo: DotClass /* should be: Type | Symbol */) extends CachedGroundType with TypeType { - def selfType(implicit ctx: Context): Type = selfInfo match { - case NoType => - if (selfTypeCache == null) selfTypeCache = computeSelfType(cls.typeRef, cls.typeParams) - selfTypeCache - case tp: Type => tp - case self: Symbol => self.info + /** The self type of a class is the conjunction of + * - the explicit self type if given (or the info of a given self symbol), and + * - the fully applied reference to the class itself. + */ + def selfType(implicit ctx: Context): Type = { + if (selfTypeCache == null) { + def fullRef = fullyAppliedRef(cls.typeRef, cls.typeParams) + selfTypeCache = selfInfo match { + case NoType => + fullRef + case tp: Type => + if (cls is Module) tp else AndType(tp, fullRef) + case self: Symbol => + assert(!(cls is Module)) + AndType(self.info, fullRef) + } + } + selfTypeCache } private var selfTypeCache: Type = null - private def computeSelfType(base: Type, tparams: List[TypeSymbol])(implicit ctx: Context): Type = tparams match { + private def fullyAppliedRef(base: Type, tparams: List[TypeSymbol])(implicit ctx: Context): Type = tparams match { case tparam :: tparams1 => - computeSelfType( + fullyAppliedRef( RefinedType(base, tparam.name, TypeRef(cls.thisType, tparam).toBounds(tparam)), tparams1) case nil => From 491e07690dc2701d15544d0d77f0922180c45722 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jul 2014 22:30:31 +0200 Subject: [PATCH 46/47] Handle selftypes in FullParameterization Adapt the transformation so that self types are handled correctly. --- .../dotc/transform/FullParameterization.scala | 18 +++++++++--------- tests/pos/extmethods.scala | 6 ++++++ 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 tests/pos/extmethods.scala diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala index 092e16086fdb..a496716a5bb0 100644 --- a/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -71,6 +71,8 @@ trait FullParameterization { * object Foo { * def extension$baz[B >: A <: Any, A >: Nothing <: AnyRef]($this: Foo[A])(x: B): List[B] * } + * + * If a self type is present, $this has this self type as its type. */ def fullyParameterizedType(info: Type, clazz: ClassSymbol)(implicit ctx: Context): Type = { val (mtparamCount, origResult) = info match { @@ -81,11 +83,11 @@ trait FullParameterization { val ctparams = clazz.typeParams val ctnames = ctparams.map(_.name.unexpandedName()) - /** The method result type, prior to mapping any type parameters */ - val resultType = { - val thisParamType = clazz.typeRef.appliedTo(ctparams.map(_.typeRef)) + /** The method result type */ + def resultType(mapClassParams: Type => Type) = { + val thisParamType = mapClassParams(clazz.classInfo.selfType) MethodType(nme.SELF :: Nil, thisParamType :: Nil)(mt => - origResult.substThis(clazz, MethodParam(mt, 0))) + mapClassParams(origResult).substThis(clazz, MethodParam(mt, 0))) } /** Replace class type parameters by the added type parameters of the polytype `pt` */ @@ -98,17 +100,15 @@ trait FullParameterization { def mappedClassBounds(pt: PolyType): List[TypeBounds] = ctparams.map(tparam => mapClassParams(tparam.info, pt).bounds) - def mappedResultType(pt: PolyType): Type = mapClassParams(resultType, pt) - info match { case info @ PolyType(mtnames) => PolyType(mtnames ++ ctnames)( pt => (info.paramBounds ++ mappedClassBounds(pt)) .mapConserve(_.subst(info, pt).bounds), - pt => mappedResultType(pt).subst(info, pt)) + pt => resultType(mapClassParams(_, pt)).subst(info, pt)) case _ => - if (ctparams.isEmpty) resultType - else PolyType(ctnames)(mappedClassBounds, mappedResultType) + if (ctparams.isEmpty) resultType(identity) + else PolyType(ctnames)(mappedClassBounds, pt => resultType(mapClassParams(_, pt))) } } diff --git a/tests/pos/extmethods.scala b/tests/pos/extmethods.scala new file mode 100644 index 000000000000..c8c49b12d6d2 --- /dev/null +++ b/tests/pos/extmethods.scala @@ -0,0 +1,6 @@ +trait That1[A] +class T[A, This <: That1[A]] extends AnyVal { + self: This => + var next: This = _ + final def loop(x: This, cnt: Int): Int = loop(x, cnt + 1) +} From 736dceb837d059df35c12f2d35fbbe0bf0f9f8f5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jul 2014 10:57:58 +0200 Subject: [PATCH 47/47] Made FullParameterization more customizable Can now decide on rewiring on a node-by-nide basis. --- .../dotc/transform/FullParameterization.scala | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala index a496716a5bb0..fea0482a0d01 100644 --- a/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -52,11 +52,22 @@ trait FullParameterization { import tpd._ - /** If references to original `target` from fully parameterized method `derived` should be - * rewired to some fully parameterized method, that method symbol, + /** If references to original symbol `referenced` from within fully parameterized method + * `derived` should be rewired to some fully parameterized method, the rewiring target symbol, * otherwise NoSymbol. */ - protected def rewiredTarget(target: Symbol, derived: Symbol)(implicit ctx: Context): Symbol + protected def rewiredTarget(referenced: Symbol, derived: Symbol)(implicit ctx: Context): Symbol + + /** If references to some original symbol from given tree node within fully parameterized method + * `derived` should be rewired to some fully parameterized method, the rewiring target symbol, + * otherwise NoSymbol. By default implemented as + * + * rewiredTarget(tree.symbol, derived) + * + * but can be overridden. + */ + protected def rewiredTarget(tree: Tree, derived: Symbol)(implicit ctx: Context): Symbol = + rewiredTarget(tree.symbol, derived) /** Converts the type `info` of a member of class `clazz` to a method type that * takes the `this` of the class and any type parameters of the class @@ -151,8 +162,7 @@ trait FullParameterization { */ def rewireTree(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { def rewireCall(thisArg: Tree): Tree = { - val sym = tree.symbol - val rewired = rewiredTarget(sym, derived) + val rewired = rewiredTarget(tree, derived) if (rewired.exists) { val base = thisArg.tpe.baseTypeWithArgs(origClass) assert(base.exists)