From 54d4d3d96e0771b0aafd12d6349b952fe368f16e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:01:23 +0200 Subject: [PATCH 01/22] Merge postConditions of memoize and constructors If memoize and constructors are run in different groups, memoize's previous postcondition "all concerete methods are implemented" is wrong, because constructors are not implemengted yet. Solved by moving the postcondition to phase Constructors. --- src/dotty/tools/dotc/transform/Constructors.scala | 14 +++++++++----- src/dotty/tools/dotc/transform/Memoize.scala | 6 ------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index fa60ad277c5c..100e9ff211c8 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -30,15 +30,19 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor import tpd._ override def phaseName: String = "constructors" - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize]) - /** All initializers should be moved into constructor - */ + /** All initializers for non-lazy fields should be moved into constructor. + * All non-abstract methods should be implemented (this is assured for constructors + * in this phase and for other methods in memoize). + */ override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = { tree match { - case t: ValDef if ((t.rhs ne EmptyTree) && !(t.symbol is Flags.Lazy) && t.symbol.owner.isClass) => - assert(false, i"$t initializers should be moved to constructors") + case tree: ValDef if tree.symbol.exists && tree.symbol.owner.isClass && !tree.symbol.is(Lazy) => + assert(tree.rhs.isEmpty, i"$tree: initializer should be moved to constructors") + case tree: DefDef if !tree.symbol.is(LazyOrDeferred) => + assert(!tree.rhs.isEmpty, i"unimplemented: $tree") case _ => } } diff --git a/src/dotty/tools/dotc/transform/Memoize.scala b/src/dotty/tools/dotc/transform/Memoize.scala index cbde1ef8ae33..590cf4667ae5 100644 --- a/src/dotty/tools/dotc/transform/Memoize.scala +++ b/src/dotty/tools/dotc/transform/Memoize.scala @@ -42,12 +42,6 @@ import Decorators._ */ override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Mixin]) - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { - case tree: DefDef if !tree.symbol.is(Lazy | Deferred) => - assert(!tree.rhs.isEmpty, i"unimplemented: $tree") - case _ => - } - override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { val sym = tree.symbol From 069010b36cdad9cbaaf2d48e70b17e08ee5eb224 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:07:29 +0200 Subject: [PATCH 02/22] Make Mutable a ModifierFlag. It definitely does appear in trees, so should be included in the set. Affects how things are printed. Before, typed var's would still show up as vals. --- src/dotty/tools/dotc/core/Flags.scala | 8 +++++++- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index cfa0faef9bee..3efadcb00406 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -427,7 +427,7 @@ object Flags { /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Module | Param | Synthetic | Package | Local + SourceModifierFlags | Module | Param | Synthetic | Package | Local | commonFlags(Mutable) // | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags) @@ -520,12 +520,18 @@ object Flags { /** A private method */ final val PrivateMethod = allOf(Private, Method) + /** A private accessor */ + final val PrivateAccessor = allOf(Private, Accessor) + /** A type parameter with synthesized name */ final val ExpandedTypeParam = allOf(ExpandedName, TypeParam) /** A parameter or parameter accessor */ final val ParamOrAccessor = Param | ParamAccessor + /** A lazy or deferred value */ + final val LazyOrDeferred = Lazy | Deferred + /** A type parameter or type parameter accessor */ final val TypeParamOrAccessor = TypeParam | TypeParamAccessor diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index cc8f0bef8193..fa238f32cadd 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -172,7 +172,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { * parent { type Apply = body; argBindings? } * * split it into - + * * - the `parent` * - the simplified `body` * - the bindings HK$ members, if there are any From 11848a8c620be514ea43ea4383ca2b91a1aa8627 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:09:15 +0200 Subject: [PATCH 03/22] New NameOps methods for Scala2 traits Also: generalize expandedName so that it can cater for trait setters. --- src/dotty/tools/dotc/core/NameOps.scala | 14 ++++++++++---- src/dotty/tools/dotc/core/StdNames.scala | 1 + .../dotc/transform/FullParameterization.scala | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index bf5e219cfd7c..dc94f6db12da 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -155,13 +155,17 @@ object NameOps { /** The expanded name of `name` relative to given class `base`. */ - def expandedName(base: Symbol)(implicit ctx: Context): N = - expandedName(if (base is Flags.ExpandedName) base.name else base.fullNameSeparated("$")) + def expandedName(base: Symbol, separator: Name)(implicit ctx: Context): N = + expandedName(if (base is Flags.ExpandedName) base.name else base.fullNameSeparated("$"), separator) + + def expandedName(base: Symbol)(implicit ctx: Context): N = expandedName(base, nme.EXPAND_SEPARATOR) /** The expanded name of `name` relative to `basename` with given `separator` */ - def expandedName(prefix: Name): N = - name.fromName(prefix ++ nme.EXPAND_SEPARATOR ++ name).asInstanceOf[N] + def expandedName(prefix: Name, separator: Name = nme.EXPAND_SEPARATOR): N = + name.fromName(prefix ++ separator ++ name).asInstanceOf[N] + + def expandedName(prefix: Name): N = expandedName(prefix, nme.EXPAND_SEPARATOR) def unexpandedName: N = { val idx = name.lastIndexOfSlice(nme.EXPAND_SEPARATOR) @@ -178,6 +182,8 @@ object NameOps { def revertShadowed: N = likeTyped(name.drop(nme.SHADOWED.length)) + def implClassName: N = likeTyped(name ++ tpnme.IMPL_CLASS_SUFFIX) + /** Translate a name into a list of simple TypeNames and TermNames. * In all segments before the last, type/term is determined by whether * the following separator char is '.' or '#'. The last segment diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 6273612c779a..eaf4ce1e2080 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -126,6 +126,7 @@ object StdNames { val INITIALIZER_PREFIX: N = "initial$" val COMPANION_MODULE_METHOD: N = "companion$module" val COMPANION_CLASS_METHOD: N = "companion$class" + val TRAIT_SETTER_SEPARATOR: N = "$_setter_$" // value types (and AnyRef) are all used as terms as well // as (at least) arguments to the @specialize annotation. diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala index 87d49206217d..6389f981e39f 100644 --- a/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -86,6 +86,7 @@ trait FullParameterization { * } * * If a self type is present, $this has this self type as its type. + * @param abstractOverClass if true, include the type parameters of the class in the method's list of type parameters. */ def fullyParameterizedType(info: Type, clazz: ClassSymbol, abstractOverClass: Boolean = true)(implicit ctx: Context): Type = { val (mtparamCount, origResult) = info match { From 9a167ded3bdecc16eb9636fc3f1b58d3405e2248 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:13:08 +0200 Subject: [PATCH 04/22] Move needsForwarder logic from ResolveSuper to MixinOps. We'd like to make it reusable for a phase that treats Scala2 traits. --- src/dotty/tools/dotc/transform/MixinOps.scala | 11 +++++++++++ src/dotty/tools/dotc/transform/ResolveSuper.scala | 10 ++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/transform/MixinOps.scala b/src/dotty/tools/dotc/transform/MixinOps.scala index 1dce85eaa9e7..57c6bd93a42a 100644 --- a/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/src/dotty/tools/dotc/transform/MixinOps.scala @@ -31,6 +31,17 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx: //sup.select(target) } + def needsForwarder(meth: Symbol): Boolean = { + def needsDisambiguation = !meth.allOverriddenSymbols.forall(_ is Deferred) + def isOverridden = meth.overridingSymbol(cls).is(Method, butNot = Deferred) + meth.is(Method, butNot = PrivateOrAccessorOrDeferred) && + !isOverridden && + !meth.isConstructor && + (needsDisambiguation/* || meth.owner.is(Scala2x)*/) + } + + final val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred + def forwarder(target: Symbol) = (targs: List[Type]) => (vrefss: List[List[Tree]]) => superRef(target).appliedToTypes(targs).appliedToArgss(vrefss) } diff --git a/src/dotty/tools/dotc/transform/ResolveSuper.scala b/src/dotty/tools/dotc/transform/ResolveSuper.scala index 0f1c448d9956..d23e81ab31d3 100644 --- a/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -80,15 +80,9 @@ class ResolveSuper extends MiniPhaseTransform with IdentityDenotTransformer { th for (superAcc <- mixin.info.decls.filter(_ is SuperAccessor).toList) yield polyDefDef(implementation(superAcc.asTerm), forwarder(rebindSuper(cls, superAcc))) - def methodOverrides(mixin: ClassSymbol): List[Tree] = { - def isOverridden(meth: Symbol) = meth.overridingSymbol(cls).is(Method, butNot = Deferred) - def needsDisambiguation(meth: Symbol): Boolean = - meth.is(Method, butNot = PrivateOrAccessorOrDeferred) && - !isOverridden(meth) && - !meth.allOverriddenSymbols.forall(_ is Deferred) - for (meth <- mixin.info.decls.toList if needsDisambiguation(meth)) + def methodOverrides(mixin: ClassSymbol): List[Tree] = + for (meth <- mixin.info.decls.toList if needsForwarder(meth)) yield polyDefDef(implementation(meth.asTerm), forwarder(meth)) - } val overrides = mixins.flatMap(mixin => superAccessors(mixin) ::: methodOverrides(mixin)) From 4f0af8ec7916503d878712eb99a7a64ea8191bc8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:14:14 +0200 Subject: [PATCH 05/22] Use $init$ as the name of trait constructors This brings it in line with Scala2 conventions. --- src/dotty/tools/dotc/transform/TraitConstructors.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/TraitConstructors.scala b/src/dotty/tools/dotc/transform/TraitConstructors.scala index 32c4b9da4e2f..cdfbe62e5d3e 100644 --- a/src/dotty/tools/dotc/transform/TraitConstructors.scala +++ b/src/dotty/tools/dotc/transform/TraitConstructors.scala @@ -23,7 +23,7 @@ class TraitConstructors extends MiniPhaseTransform with SymTransformer { def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { if (sym.isPrimaryConstructor && (sym.owner is Flags.Trait)) // TODO: Someone needs to carefully check if name clashes are possible with this mangling scheme - sym.copySymDenotation(name = nme.INITIALIZER_PREFIX ++ sym.owner.fullNameSeparated("$")) + sym.copySymDenotation(name = nme.IMPLCLASS_CONSTRUCTOR) else sym } From 6cdb3d45bf2b0b9ebfad73991be37a80f0c8639f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:15:04 +0200 Subject: [PATCH 06/22] Make ensureMethodic work after erasure. Previously it didn't, because it created an ExprType, which is illegal after erasure. --- src/dotty/tools/dotc/core/TypeErasure.scala | 2 +- src/dotty/tools/dotc/transform/TypeUtils.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index fac795ef837d..92e32d4b1c12 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -164,7 +164,7 @@ object TypeErasure { else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx)) else erase.eraseInfo(tp, sym)(erasureCtx) match { case einfo: MethodType if sym.isGetter && einfo.resultType.isRef(defn.UnitClass) => - MethodType(Nil, Nil, defn.BoxedUnitClass.typeRef) + MethodType(Nil, defn.BoxedUnitClass.typeRef) case einfo => einfo } diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala index c01b6478cfa6..d474c77b4621 100644 --- a/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -29,6 +29,6 @@ class TypeUtils(val self: Type) extends AnyVal { def ensureMethodic(implicit ctx: Context): Type = self match { case self: MethodicType => self - case _ => ExprType(self) + case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self) } } From 31a7f6492729ad1ddf7f8e233a452a6ccd517af5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:27:05 +0200 Subject: [PATCH 07/22] New utility method tpd.Underscore A typed `_'. This is needed in a few places. --- src/dotty/tools/dotc/ast/tpd.scala | 3 +++ src/dotty/tools/dotc/transform/ExpandSAMs.scala | 2 +- src/dotty/tools/dotc/transform/LazyVals.scala | 2 +- src/dotty/tools/dotc/transform/PatternMatcher.scala | 2 +- src/dotty/tools/dotc/transform/SyntheticMethods.scala | 3 +-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index a38a238c80e8..c50567dd54ac 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -418,6 +418,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Thicket(valdef, clsdef) } + /** A `_' with given type */ + def Underscore(tp: Type)(implicit ctx: Context) = untpd.Ident(nme.WILDCARD).withType(tp) + def defaultValue(tpe: Types.Type)(implicit ctx: Context) = { val tpw = tpe.widen diff --git a/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/src/dotty/tools/dotc/transform/ExpandSAMs.scala index bba42f403de3..2416e46246fb 100644 --- a/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -74,7 +74,7 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer => val defaultSym = ctx.newSymbol(isDefinedAtFn, nme.WILDCARD, Synthetic, selector.tpe.widen) val defaultCase = CaseDef( - Bind(defaultSym, untpd.Ident(nme.WILDCARD).withType(selector.tpe.widen)), + Bind(defaultSym, Underscore(selector.tpe.widen)), EmptyTree, Literal(Constant(false))) cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase) diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index 5b146a7856d5..7d319bbb7f0f 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -278,7 +278,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer { CaseDef(computedState, EmptyTree, body) } - val default = CaseDef(untpd.Ident(nme.WILDCARD).withType(defn.LongType), EmptyTree, Literal(Constant(()))) + val default = CaseDef(Underscore(defn.LongType), EmptyTree, Literal(Constant(()))) val cases = Match(stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord))), List(compute, waitFirst, waitSecond, computed, default)) //todo: annotate with @switch diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 507dbb0ce38d..5fa17921f53a 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -60,7 +60,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val sel = Ident(selector.termRef).withPos(tree.pos) val rethrow = tpd.CaseDef(EmptyTree, EmptyTree, Throw(ref(selector))) val newCases = tpd.CaseDef( - Bind(selector,untpd.Ident(nme.WILDCARD).withPos(tree.pos).withType(selector.info)), + Bind(selector, Underscore(selector.info).withPos(tree.pos)), EmptyTree, transformMatch(tpd.Match(sel, tree.cases ::: rethrow :: Nil))) cpy.Try(tree)(tree.expr, newCases :: Nil, tree.finalizer) diff --git a/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/src/dotty/tools/dotc/transform/SyntheticMethods.scala index 9d0aebe450d9..b33bbd6a481c 100644 --- a/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -105,8 +105,7 @@ class SyntheticMethods(thisTransformer: DenotTransformer) { */ def equalsBody(that: Tree)(implicit ctx: Context): Tree = { val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.pos) // x$0 - def wildcardAscription(tp: Type) = - Typed(untpd.Ident(nme.WILDCARD).withType(tp), TypeTree(tp)) + def wildcardAscription(tp: Type) = Typed(Underscore(tp), TypeTree(tp)) val pattern = Bind(thatAsClazz, wildcardAscription(clazzType)) // x$0 @ (_: C) val comparisons = accessors map (accessor => This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor))) From 4f50e8fa5b48b5a931224d40b7c6fc0aed3da44b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 11:29:24 +0200 Subject: [PATCH 08/22] Avoid referring to initial$x methods when implementing Scala2 traits They don't exist for Scala2 traits. Instead we let the initializer be `_' and rely on trait setters (to be implemented) to initialize the field. --- src/dotty/tools/dotc/transform/Mixin.scala | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Mixin.scala b/src/dotty/tools/dotc/transform/Mixin.scala index d2e7943f82b4..8f19faf68611 100644 --- a/src/dotty/tools/dotc/transform/Mixin.scala +++ b/src/dotty/tools/dotc/transform/Mixin.scala @@ -14,6 +14,7 @@ import DenotTransformers._ import StdNames._ import NameOps._ import Phases._ +import ast.untpd import ast.Trees._ import collection.mutable @@ -150,16 +151,20 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => ctx.atPhase(thisTransform) { implicit ctx => sym is Deferred } def traitInits(mixin: ClassSymbol): List[Tree] = - for (getter <- mixin.info.decls.filter(getr => getr.isGetter && !wasDeferred(getr)).toList) - yield { - // transformFollowing call is needed to make memoize & lazy vals run - val rhs = transformFollowing(superRef(initializer(getter)).appliedToNone) + for (getter <- mixin.info.decls.filter(getr => getr.isGetter && !wasDeferred(getr)).toList) yield { // isCurrent: getter is a member of implementing class val isCurrent = getter.is(ExpandedName) || ctx.atPhase(thisTransform) { implicit ctx => cls.info.member(getter.name).suchThat(_.isGetter).symbol == getter } - if (isCurrent) transformFollowing(DefDef(implementation(getter.asTerm), rhs)) - else rhs + val isScala2x = mixin.is(Scala2x) + def default = Underscore(getter.info.resultType) + def initial = transformFollowing(superRef(initializer(getter)).appliedToNone) + if (isCurrent) + // transformFollowing call is needed to make memoize & lazy vals run + transformFollowing( + DefDef(implementation(getter.asTerm), if (isScala2x) default else initial)) + else if (isScala2x) EmptyTree + else initial } def setters(mixin: ClassSymbol): List[Tree] = From 71067e64900b212068328d114edc741c6e12049b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 12:23:19 +0200 Subject: [PATCH 09/22] New phase: AugmentScala2Traits --- src/dotty/tools/dotc/Compiler.scala | 2 + .../dotc/transform/AugmentScala2Traits.scala | 96 +++++++++++++++++++ .../tools/dotc/transform/ResolveSuper.scala | 3 +- src/dotty/tools/dotc/transform/SymUtils.scala | 6 +- 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/AugmentScala2Traits.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 41e77c61f95a..31fabb28b0cb 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -56,6 +56,7 @@ class Compiler { new Getters, new ClassTags, new ElimByName, + new AugmentScala2Traits, new ResolveSuper), List(new Erasure), List(new ElimErasedValueType, @@ -63,6 +64,7 @@ class Compiler { new Mixin, new LazyVals, new Memoize, + //new LinkScala2ImplClasses, new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here new Constructors, new FunctionalInterfaces), diff --git a/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala b/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala new file mode 100644 index 000000000000..939b4105b612 --- /dev/null +++ b/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala @@ -0,0 +1,96 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import Contexts.Context +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import StdNames._ +import NameOps._ +import ast.Trees._ + +/** This phase augments Scala2 traits with implementation classes and with additional members + * needed for mixin composition. + * These symbols would have been added between Unpickling and Mixin in the Scala2 pipeline. + * Specifcally, it adds + * + * - an implementation class which defines a trait constructor and trait method implementations + * - trait setters for vals defined in traits + * + * Furthermore, it expands the names of all private getters and setters in the trait and makes + * them not-private. + */ +class AugmentScala2Traits extends MiniPhaseTransform with IdentityDenotTransformer with FullParameterization { thisTransform => + import ast.tpd._ + + override def phaseName: String = "augmentScala2Traits" + + override def rewiredTarget(referenced: Symbol, derived: Symbol)(implicit ctx: Context) = NoSymbol + + override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = { + val cls = impl.symbol.owner.asClass + for (mixin <- cls.mixins) + if (mixin.is(Scala2x)) + augmentScala2Trait(mixin, cls) + impl + } + + private def augmentScala2Trait(mixin: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context): Unit = { + if (mixin.implClass.is(Scala2x)) () // nothing to do, mixin was already augmented + else { + //println(i"creating new implclass for $mixin ${mixin.implClass}") + val ops = new MixinOps(cls, thisTransform) + import ops._ + + val implClass = ctx.newCompleteClassSymbol( + owner = mixin.owner, + name = mixin.name.implClassName, + flags = Abstract | Scala2x, + parents = defn.ObjectClass.typeRef :: Nil, + assocFile = mixin.assocFile).enteredAfter(thisTransform) + + def implMethod(meth: TermSymbol): Symbol = { + val mold = + if (meth.isConstructor) + meth.copySymDenotation( + name = nme.IMPLCLASS_CONSTRUCTOR, + info = MethodType(Nil, defn.UnitType)) + else meth.ensureNotPrivate + meth.copy( + owner = implClass, + name = mold.name.asTermName, + flags = Method | JavaStatic | mold.flags & ExpandedName, + info = fullyParameterizedType(mold.info, mixin)) + } + + def traitSetter(getter: TermSymbol) = { + val separator = if (getter.is(Private)) nme.EXPAND_SEPARATOR else nme.TRAIT_SETTER_SEPARATOR + val expandedGetterName = + if (getter.is(ExpandedName)) getter.name + else getter.name.expandedName(getter.owner, separator) + getter.copy( + name = expandedGetterName.setterName, + flags = Method | Accessor | ExpandedName, + info = MethodType(getter.info.resultType :: Nil, defn.UnitType)) + } + + for (sym <- mixin.info.decls) { + if (needsForwarder(sym) || sym.isConstructor || sym.isGetter && sym.is(Lazy)) + implClass.enter(implMethod(sym.asTerm)) + if (sym.isGetter && !sym.is(LazyOrDeferred) && !sym.setter.exists) + traitSetter(sym.asTerm).enteredAfter(thisTransform) + if (sym.is(PrivateAccessor, butNot = ExpandedName) && + (sym.isGetter || sym.isSetter)) // strangely, Scala 2 fields are also methods that have Accessor set. + sym.ensureNotPrivate.installAfter(thisTransform) + } + ctx.log(i"Scala2x trait decls of $mixin = ${mixin.info.decls.toList.map(_.showDcl)}%\n %") + ctx.log(i"Scala2x impl decls of $mixin = ${implClass.info.decls.toList.map(_.showDcl)}%\n %") + } + } +} diff --git a/src/dotty/tools/dotc/transform/ResolveSuper.scala b/src/dotty/tools/dotc/transform/ResolveSuper.scala index d23e81ab31d3..27387bca48a3 100644 --- a/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -48,7 +48,8 @@ class ResolveSuper extends MiniPhaseTransform with IdentityDenotTransformer { th override def phaseName: String = "resolveSuper" - override def runsAfter = Set(classOf[ElimByName]) // verified empirically, need to figure out what the reason is. + override def runsAfter = Set(classOf[ElimByName], // verified empirically, need to figure out what the reason is. + classOf[AugmentScala2Traits]) /** Returns the symbol that is accessed by a super-accessor in a mixin composition. * diff --git a/src/dotty/tools/dotc/transform/SymUtils.scala b/src/dotty/tools/dotc/transform/SymUtils.scala index df3b183a956b..d3e029a74767 100644 --- a/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/src/dotty/tools/dotc/transform/SymUtils.scala @@ -85,9 +85,9 @@ class SymUtils(val self: Symbol) extends AnyVal { def field(implicit ctx: Context): Symbol = self.owner.info.decl(self.asTerm.name.fieldName).suchThat(!_.is(Method)).symbol - def initializer(implicit ctx: Context): TermSymbol = - self.owner.info.decl(InitializerName(self.asTerm.name)).symbol.asTerm - def isField(implicit ctx: Context): Boolean = self.isTerm && !self.is(Method) + + def implClass(implicit ctx: Context): Symbol = + self.owner.info.decl(self.name.implClassName).symbol } From e4dc4fabd3e34e96ce60311b60219fa8c0064bc2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 13:08:53 +0200 Subject: [PATCH 10/22] Make memberSignature work after erasure. Erasure uncurries arguments, need to track that in memberSignature. --- .../tools/dotc/transform/FullParameterization.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala index 6389f981e39f..e9057e885c47 100644 --- a/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -226,12 +226,18 @@ trait FullParameterization { } object FullParameterization { + /** Assuming `info` is a result of a `fullyParameterizedType` call, the signature of the * original method type `X` such that `info = fullyParameterizedType(X, ...)`. */ def memberSignature(info: Type)(implicit ctx: Context): Signature = info match { - case info: PolyType => memberSignature(info.resultType) - case info @ MethodType(nme.SELF :: Nil, _) => info.resultType.ensureMethodic.signature - case _ => Signature.NotAMethod + case info: PolyType => + memberSignature(info.resultType) + case info @ MethodType(nme.SELF :: Nil, _) => + info.resultType.ensureMethodic.signature + case info @ MethodType(nme.SELF :: otherNames, thisType :: otherTypes) => + info.derivedMethodType(otherNames, otherTypes, info.resultType).signature + case _ => + Signature.NotAMethod } } From 8263d33b047b926ef9750b73fe1a97ea09237df3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 13:09:45 +0200 Subject: [PATCH 11/22] New phase: LinkScala2ImplClasses This phase rewrites supercalls to calls to static implementation class methods. --- src/dotty/tools/dotc/Compiler.scala | 2 +- .../transform/LinkScala2ImplClasses.scala | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 31fabb28b0cb..0f0da76ee79c 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -64,7 +64,7 @@ class Compiler { new Mixin, new LazyVals, new Memoize, - //new LinkScala2ImplClasses, + new LinkScala2ImplClasses, new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here new Constructors, new FunctionalInterfaces), diff --git a/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala b/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala new file mode 100644 index 000000000000..8c247130a50f --- /dev/null +++ b/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala @@ -0,0 +1,58 @@ +package dotty.tools.dotc +package transform + +import core._ +import TreeTransforms._ +import Contexts.Context +import Flags._ +import SymUtils._ +import Symbols._ +import SymDenotations._ +import Types._ +import Decorators._ +import DenotTransformers._ +import StdNames._ +import NameOps._ +import Phases._ +import ast.untpd +import ast.Trees._ +import collection.mutable + +/** Rewrite calls + * + * super[M].f(args) + * + * where M is a Scala2 trait implemented by the current class to + * + * M$class.f(this, args) + * + * provided the implementation class M$class defines a corresponding function `f`. + */ +class LinkScala2ImplClasses extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => + import ast.tpd._ + + override def phaseName: String = "linkScala2ImplClasses" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Mixin]) + + override def transformApply(app: Apply)(implicit ctx: Context, info: TransformerInfo) = { + def currentClass = ctx.owner.enclosingClass.asClass + app match { + case Apply(sel @ Select(Super(_, _), _), args) + if sel.symbol.owner.is(Scala2xTrait) && currentClass.mixins.contains(sel.symbol.owner) => + val impl = implMethod(sel.symbol) + if (impl.exists) Apply(ref(impl), This(currentClass) :: args).withPos(app.pos) + else app // could have been an abstract method in a trait linked to from a super constructor + case _ => + app + } + } + + private def implMethod(meth: Symbol)(implicit ctx: Context): Symbol = + meth.owner.implClass.info + .decl(if (meth.isConstructor) nme.IMPLCLASS_CONSTRUCTOR else meth.name) + .suchThat(c => FullParameterization.memberSignature(c.info) == meth.signature) + .symbol + + private val Scala2xTrait = allOf(Scala2x, Trait) +} \ No newline at end of file From 0b34395ab28180d6e3fcd621e399d840f8303714 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2015 16:30:14 +0200 Subject: [PATCH 12/22] Improve comments --- src/dotty/tools/dotc/transform/Mixin.scala | 15 ++++++++++++++- .../tools/dotc/transform/TraitConstructors.scala | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Mixin.scala b/src/dotty/tools/dotc/transform/Mixin.scala index 8f19faf68611..bd9c410ac2f7 100644 --- a/src/dotty/tools/dotc/transform/Mixin.scala +++ b/src/dotty/tools/dotc/transform/Mixin.scala @@ -47,10 +47,23 @@ import collection.mutable * reverse linearization order, add the following definitions to C: * * 3.1 (done in `traitInits`) For every concrete trait getter ` def x(): T` in M, - * in order of textual occurrence: + * in order of textual occurrence, produce the following: + * + * 3.1.1 If `x` is also a member of `C`, and M is a Dotty trait: * * def x(): T = super[M].initial$x() * + * 3.1.2 If `x` is also a member of `C`, and M is a Scala 2.x trait: + * + * def x(): T = _ + * + * 3.1.3 If `x` is not a member of `C`, and M is a Dotty trait: + * + * super[M].initial$x() + * + * 3.1.4 If `x` is not a member of `C`, and M is a Scala2.x trait, nothing gets added. + * + * * 3.2 (done in `superCallOpt`) The call: * * super[M]. diff --git a/src/dotty/tools/dotc/transform/TraitConstructors.scala b/src/dotty/tools/dotc/transform/TraitConstructors.scala index cdfbe62e5d3e..9fea468dac82 100644 --- a/src/dotty/tools/dotc/transform/TraitConstructors.scala +++ b/src/dotty/tools/dotc/transform/TraitConstructors.scala @@ -22,7 +22,6 @@ class TraitConstructors extends MiniPhaseTransform with SymTransformer { def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { if (sym.isPrimaryConstructor && (sym.owner is Flags.Trait)) - // TODO: Someone needs to carefully check if name clashes are possible with this mangling scheme sym.copySymDenotation(name = nme.IMPLCLASS_CONSTRUCTOR) else sym } From 9b85e308cfae87b9decd13ac4862668e7720648e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 May 2015 13:14:49 +0200 Subject: [PATCH 13/22] Make elimWildcardIdents apply to Assignments Fixes junit failure in dotty where a lazy val was initialized with a "...$lazy = _" assignment. Moved ElimWiildcard to one group before. It does not really matter where it goes so it might as well go someshere in the middle of the pack. --- src/dotty/tools/dotc/Compiler.scala | 2 +- src/dotty/tools/dotc/transform/ElimWildcardIdents.scala | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 0f0da76ee79c..8b0e08a4c22e 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -71,12 +71,12 @@ class Compiler { List(new LambdaLift, // in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here new Flatten, new ElimStaticThis, + new ElimWildcardIdents, new RestoreScopes), List(/*new PrivateToStatic,*/ new ExpandPrivate, new CollectEntryPoints, new LabelDefs, - new ElimWildcardIdents, new TraitConstructors), List(new GenBCode) ) diff --git a/src/dotty/tools/dotc/transform/ElimWildcardIdents.scala b/src/dotty/tools/dotc/transform/ElimWildcardIdents.scala index 29194d2353c0..300f468f62b2 100644 --- a/src/dotty/tools/dotc/transform/ElimWildcardIdents.scala +++ b/src/dotty/tools/dotc/transform/ElimWildcardIdents.scala @@ -29,6 +29,9 @@ class ElimWildcardIdents extends MiniPhaseTransform { recur(tree) } + override def transformAssign(tree: tpd.Assign)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = + cpy.Assign(tree)(tree.lhs, wildcardToDefaultValue(tree.rhs)) + override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = cpy.ValDef(tree)(rhs = wildcardToDefaultValue(tree.rhs)) From 922084456e5fd8497c3ed924102d9ed0911c88be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Jun 2015 05:45:07 +0200 Subject: [PATCH 14/22] Elim ElimWildcardIdents Instead of cleaning up, generate sensical code in the first place. This is shorter and (I would argue) clearer, and also has the advantage that some initializing assignments are not generated at all. --- src/dotty/tools/dotc/Compiler.scala | 1 - src/dotty/tools/dotc/ast/tpd.scala | 6 +++ .../dotc/transform/ElimWildcardIdents.scala | 40 ------------------- src/dotty/tools/dotc/transform/LazyVals.scala | 4 +- src/dotty/tools/dotc/transform/Memoize.scala | 5 ++- src/dotty/tools/dotc/transform/Mixin.scala | 2 +- 6 files changed, 12 insertions(+), 46 deletions(-) delete mode 100644 src/dotty/tools/dotc/transform/ElimWildcardIdents.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 8b0e08a4c22e..2a3bdc1caf47 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -71,7 +71,6 @@ class Compiler { List(new LambdaLift, // in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here new Flatten, new ElimStaticThis, - new ElimWildcardIdents, new RestoreScopes), List(/*new PrivateToStatic,*/ new ExpandPrivate, diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index c50567dd54ac..e38de458a556 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -723,6 +723,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def ensureConforms(tp: Type)(implicit ctx: Context): Tree = if (tree.tpe <:< tp) tree else asInstance(tp) + /** If inititializer tree is `_', the default value of its type, + * otherwise the tree itself. + */ + def wildcardToDefault(implicit ctx: Context) = + if (isWildcardArg(tree)) defaultValue(tree.tpe) else tree + /** `this && that`, for boolean trees `this`, `that` */ def and(that: Tree)(implicit ctx: Context): Tree = tree.select(defn.Boolean_&&).appliedTo(that) diff --git a/src/dotty/tools/dotc/transform/ElimWildcardIdents.scala b/src/dotty/tools/dotc/transform/ElimWildcardIdents.scala deleted file mode 100644 index 300f468f62b2..000000000000 --- a/src/dotty/tools/dotc/transform/ElimWildcardIdents.scala +++ /dev/null @@ -1,40 +0,0 @@ -package dotty.tools.dotc -package transform - -import TreeTransforms.{MiniPhaseTransform, TransformerInfo} -import ast.tpd -import ast.Trees._ -import core._ -import Contexts.Context -import Symbols._ -import Types._ -import StdNames._ - -/** - * Replace Ident("_") in tree with default values of corresponding type: - * numerics: `0` - * booleans: `false` - * classes: `null` - */ -class ElimWildcardIdents extends MiniPhaseTransform { - import ast.tpd._ - def phaseName: String = "elimWildcardIdents" - - def wildcardToDefaultValue(tree: Tree)(implicit ctx: Context) = { - def recur(x: Tree): Tree = x match { - case x: Ident if x.name == nme.WILDCARD && x.symbol.isClass => defaultValue(tree.tpe) - case Block(Nil, y) => recur(y) - case _ => tree - } - recur(tree) - } - - override def transformAssign(tree: tpd.Assign)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = - cpy.Assign(tree)(tree.lhs, wildcardToDefaultValue(tree.rhs)) - - override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = - cpy.ValDef(tree)(rhs = wildcardToDefaultValue(tree.rhs)) - - override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = - cpy.DefDef(tree)(rhs = wildcardToDefaultValue(tree.rhs)) -} diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index 7d319bbb7f0f..ccae24ef7463 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -146,8 +146,8 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer { def mkNonThreadSafeDef(target: Tree, flag: Tree, rhs: Tree)(implicit ctx: Context) = { val setFlag = flag.becomes(Literal(Constants.Constant(true))) - val setTarget = target.becomes(rhs) - val init = Block(List(setFlag, setTarget), target.ensureApplied) + val setTargets = if (isWildcardArg(rhs)) Nil else target.becomes(rhs) :: Nil + val init = Block(setFlag :: setTargets, target.ensureApplied) If(flag.ensureApplied, target.ensureApplied, init) } diff --git a/src/dotty/tools/dotc/transform/Memoize.scala b/src/dotty/tools/dotc/transform/Memoize.scala index 590cf4667ae5..6b19b6d13722 100644 --- a/src/dotty/tools/dotc/transform/Memoize.scala +++ b/src/dotty/tools/dotc/transform/Memoize.scala @@ -55,8 +55,9 @@ import Decorators._ lazy val field = sym.field.orElse(newField).asTerm if (sym.is(Accessor, butNot = NoFieldNeeded)) if (sym.isGetter) { - tree.rhs.changeOwnerAfter(sym, field, thisTransform) - val fieldDef = transformFollowing(ValDef(field, tree.rhs)) + var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform) + if (isWildcardArg(rhs)) rhs = EmptyTree + val fieldDef = transformFollowing(ValDef(field, rhs)) val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))) Thicket(fieldDef, getterDef) } diff --git a/src/dotty/tools/dotc/transform/Mixin.scala b/src/dotty/tools/dotc/transform/Mixin.scala index bd9c410ac2f7..786759f89ee4 100644 --- a/src/dotty/tools/dotc/transform/Mixin.scala +++ b/src/dotty/tools/dotc/transform/Mixin.scala @@ -118,7 +118,7 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => val isym = initializer(vsym) val rhs = Block( initBuf.toList.map(_.changeOwner(impl.symbol, isym)), - stat.rhs.changeOwner(vsym, isym)) + stat.rhs.changeOwner(vsym, isym).wildcardToDefault) initBuf.clear() cpy.DefDef(stat)(rhs = EmptyTree) :: DefDef(isym, rhs) :: Nil case stat: DefDef if stat.symbol.isSetter => From 86f954cb0bf518086688640bb610db7018592655 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Jun 2015 15:32:03 +0200 Subject: [PATCH 15/22] Fix missing implementation class forward A transformFollowingDeep was missing, so LinkScala2ImplClasses never got to see the call. --- src/dotty/tools/dotc/transform/Mixin.scala | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Mixin.scala b/src/dotty/tools/dotc/transform/Mixin.scala index 786759f89ee4..e3215cf6d98b 100644 --- a/src/dotty/tools/dotc/transform/Mixin.scala +++ b/src/dotty/tools/dotc/transform/Mixin.scala @@ -148,15 +148,7 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => if (baseCls.is(NoInitsTrait) || defn.PhantomClasses.contains(baseCls)) Nil else { //println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}") - superRef(baseCls.primaryConstructor).appliedToNone :: Nil -/* constr.tpe.widen match { - case tpe: PolyType => - val targs = cls.thisType.baseTypeWithArgs(baseCls).argTypes - constr = constr.appliedToTypes(targs) - case _ => - } - constr.ensureApplied :: Nil -*/ + transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil } } From 2ab65fdee6c8d3c1c15bf137818c611c0a2e985d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Jun 2015 15:33:05 +0200 Subject: [PATCH 16/22] Uncomment important test for Scala trait forwarding Scala 2 doe snot generate default methods, so we always need forwarders. --- src/dotty/tools/dotc/transform/MixinOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/MixinOps.scala b/src/dotty/tools/dotc/transform/MixinOps.scala index 57c6bd93a42a..c21c98017903 100644 --- a/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/src/dotty/tools/dotc/transform/MixinOps.scala @@ -37,7 +37,7 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx: meth.is(Method, butNot = PrivateOrAccessorOrDeferred) && !isOverridden && !meth.isConstructor && - (needsDisambiguation/* || meth.owner.is(Scala2x)*/) + (needsDisambiguation || meth.owner.is(Scala2x)) } final val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred From 9e56405ee979831cdccef6ed26dbe445c6808877 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Jun 2015 15:33:25 +0200 Subject: [PATCH 17/22] Avoid generating trait setters for constants. Scalac does not generate them either. --- src/dotty/tools/dotc/transform/AugmentScala2Traits.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala b/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala index 939b4105b612..c3e205f83306 100644 --- a/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala +++ b/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala @@ -83,7 +83,8 @@ class AugmentScala2Traits extends MiniPhaseTransform with IdentityDenotTransform for (sym <- mixin.info.decls) { if (needsForwarder(sym) || sym.isConstructor || sym.isGetter && sym.is(Lazy)) implClass.enter(implMethod(sym.asTerm)) - if (sym.isGetter && !sym.is(LazyOrDeferred) && !sym.setter.exists) + if (sym.isGetter && !sym.is(LazyOrDeferred) && + !sym.setter.exists && !sym.info.resultType.isInstanceOf[ConstantType]) traitSetter(sym.asTerm).enteredAfter(thisTransform) if (sym.is(PrivateAccessor, butNot = ExpandedName) && (sym.isGetter || sym.isSetter)) // strangely, Scala 2 fields are also methods that have Accessor set. From 5aaa40607f65d0f3c1af121df226d715c0d02673 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Jun 2015 15:34:19 +0200 Subject: [PATCH 18/22] Add test case. We are still lacking the setup to do this right for mixed Scala 2/Dotty runtime tests. So I checked into `pos` for now. --- tests/pos/scala2traits/dotty-subclass.scala | 27 ++++++++++++++++++ tests/pos/scala2traits/scala-trait.scala | 31 +++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/pos/scala2traits/dotty-subclass.scala create mode 100644 tests/pos/scala2traits/scala-trait.scala diff --git a/tests/pos/scala2traits/dotty-subclass.scala b/tests/pos/scala2traits/dotty-subclass.scala new file mode 100644 index 000000000000..4e162dd145e2 --- /dev/null +++ b/tests/pos/scala2traits/dotty-subclass.scala @@ -0,0 +1,27 @@ +// This is supposed to be compiled by Dotty +class Sub extends T + +class A extends S2T with S2Tprivate { + val a: Int = 3 + var b = 2 +} + +object Main { + def main(args: Array[String]): Unit = { + val sub = new Sub + println(sub.d) + println(sub.v) + println(sub.O) + println(sub.w) + + val a = new A + a.x += a.y + println(a.x) + println(a.f(a.a + a.b)) + + a.xx += a.yy + println(a.x) + println(a.ff(a.xx)) + } +} + diff --git a/tests/pos/scala2traits/scala-trait.scala b/tests/pos/scala2traits/scala-trait.scala new file mode 100644 index 000000000000..db05bc941d3a --- /dev/null +++ b/tests/pos/scala2traits/scala-trait.scala @@ -0,0 +1,31 @@ +// This is supposed to be compiled by Scala 2.11 +trait T { + def d = 42 + val v = "" + object O + final val w = 33 +} + +trait S2T { + var x: Int = 0 + lazy val y: Int = 1 +// val z: Int = 2 + val a: Int + var b: Int + + def f(x: Int): Int = x + y +} + +trait S2Tprivate { + private var x: Int = 0 + private lazy val y: Int = 1 +// private val z: Int = 2 // @darkdimius uncomment once lazy vals can be inherited. + + private def f(x: Int): Int = x + y + def xx = x + def xx_=(x: Int) = this.x = x + def yy = y +// def zz = z + def ff(x: Int) = f(x) +} + From 0a0d3dd1a79cb211e5caf93f37b7ef6f484508b2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Jun 2015 21:26:16 +0200 Subject: [PATCH 19/22] More precise and uniform modelling of "isCurrent" For implemented getters and forwarded methods we need a notion of "isCurrent", which means: would the getter or method before the implementation is added be a member of the implementing class? Only in this case do we need to do anything. The method formulation was previously weaker than the getter formulation, which led to an error when compiling core (duplicate methods: andThen and size). --- src/dotty/tools/dotc/transform/Mixin.scala | 6 +----- src/dotty/tools/dotc/transform/MixinOps.scala | 7 ++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Mixin.scala b/src/dotty/tools/dotc/transform/Mixin.scala index e3215cf6d98b..63e680414e94 100644 --- a/src/dotty/tools/dotc/transform/Mixin.scala +++ b/src/dotty/tools/dotc/transform/Mixin.scala @@ -157,14 +157,10 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => def traitInits(mixin: ClassSymbol): List[Tree] = for (getter <- mixin.info.decls.filter(getr => getr.isGetter && !wasDeferred(getr)).toList) yield { - // isCurrent: getter is a member of implementing class - val isCurrent = getter.is(ExpandedName) || ctx.atPhase(thisTransform) { implicit ctx => - cls.info.member(getter.name).suchThat(_.isGetter).symbol == getter - } val isScala2x = mixin.is(Scala2x) def default = Underscore(getter.info.resultType) def initial = transformFollowing(superRef(initializer(getter)).appliedToNone) - if (isCurrent) + if (isCurrent(getter) || getter.is(ExpandedName)) // transformFollowing call is needed to make memoize & lazy vals run transformFollowing( DefDef(implementation(getter.asTerm), if (isScala2x) default else initial)) diff --git a/src/dotty/tools/dotc/transform/MixinOps.scala b/src/dotty/tools/dotc/transform/MixinOps.scala index c21c98017903..64f0edfb8822 100644 --- a/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/src/dotty/tools/dotc/transform/MixinOps.scala @@ -31,12 +31,13 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx: //sup.select(target) } + /** Is `sym` a member of implementing class `cls`? */ + def isCurrent(sym: Symbol) = cls.info.member(sym.name).hasAltWith(_.symbol == sym) + def needsForwarder(meth: Symbol): Boolean = { def needsDisambiguation = !meth.allOverriddenSymbols.forall(_ is Deferred) - def isOverridden = meth.overridingSymbol(cls).is(Method, butNot = Deferred) meth.is(Method, butNot = PrivateOrAccessorOrDeferred) && - !isOverridden && - !meth.isConstructor && + isCurrent(meth) && (needsDisambiguation || meth.owner.is(Scala2x)) } From 4182fb78ce173ed234a184cee4fc4493b706c77f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Jun 2015 12:56:11 +0200 Subject: [PATCH 20/22] Refine "&" for denotations. The policy is now made clear in a doc comment. The new part is that we will prefer a symbol defined in a subclass over a symbol defined in a superclass. With the previous commit 0a0d3dd1 on "More precise and uniform modelling of isCurrent" we got runtime test failures for Course-2002-03.scala because the new definition isCurrent assumed a behavior of `member` which was not assured: Namely that the merged denotation would prefer symbols in subclasses over symbols in superclasses. --- src/dotty/tools/dotc/core/Denotations.scala | 54 +++++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 6ac15dbca925..4d475fe2baf7 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -227,16 +227,36 @@ object Denotations { else asSingleDenotation - /** Form a denotation by conjoining with denotation `that` */ + /** Form a denotation by conjoining with denotation `that`. + * + * NoDenotations are dropped. MultiDenotations are handled by merging + * parts with same signatures. SingleDenotations with equal signatures + * are joined as follows: + * + * In a first step, consider only those denotations which have symbols + * that are accessible from prefix `pre`. + * + * If there are several such denotations, try to pick one by applying the following + * three precedence rules in decreasing order of priority: + * + * 1. Prefer denotations with more specific infos. + * 2. If infos are equally specific, prefer denotations with concrete symbols over denotations + * with abstract symbols. + * 3. If infos are equally specific and symbols are equally concrete, + * prefer denotations with symbols defined in subclasses + * over denotations with symbols defined in proper superclasses. + * + * If there is exactly one (preferred) accessible denotation, return it. + * + * If there is no preferred accessible denotation, return a JointRefDenotation + * with one of the operand symbols (unspecified which one), and an info which + * is intersection (&) of the infos of the operand denotations. + * + * If SingleDenotations with different signatures are joined, return NoDenotation. + */ def & (that: Denotation, pre: Type)(implicit ctx: Context): Denotation = { - /** Try to merge denot1 and denot2 without adding a new signature. - * Prefer denotations with more specific types, provided the symbol stays accessible - * Prefer denotations with accessible symbols over denotations with - * existing, but inaccessible symbols. - * If there's no preference, produce a JointRefDenotation with the intersection of both infos. - * If unsuccessful, return NoDenotation. - */ + /** Try to merge denot1 and denot2 without adding a new signature. */ def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match { case denot1 @ MultiDenotation(denot11, denot12) => val d1 = mergeDenot(denot11, denot2) @@ -254,8 +274,24 @@ object Denotations { val sym1 = denot1.symbol val sym2 = denot2.symbol val sym2Accessible = sym2.isAccessibleFrom(pre) + def shadows(sym1: Symbol, sym2: Symbol) = { + val owner1 = sym1.owner + val owner2 = sym2.owner + owner1.derivesFrom(owner2) && owner1.ne(owner2) + } + /** Preference according to order (overrides, isAsConcrete, shadows)*/ def prefer(info1: Type, sym1: Symbol, info2: Type, sym2: Symbol) = - info1.overrides(info2) && (sym1.isAsConcrete(sym2) || !info2.overrides(info1)) + info1.overrides(info2) && ( + // non-standard ordering of tests for efficiency - + // overrides is costlier to compute than the others, so its 2nd test comes last. + sym1.isAsConcrete(sym2) && ( + !sym2.isAsConcrete(sym1) + || + shadows(sym1, sym2) + ) + || + !info2.overrides(info1) + ) if (sym2Accessible && prefer(info2, sym2, info1, sym1)) denot2 else { val sym1Accessible = sym1.isAccessibleFrom(pre) From 82f8a81e60fde5750301457d5f2319ee58038d40 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Jun 2015 12:59:58 +0200 Subject: [PATCH 21/22] Make LazyVals respect scope hygiene. The previius commit on "Refine & for Denotations" caused NotDefinedHere errors in the backend when compiling dotc/typer. These were tracked down to three occurrences in LazyVals where `enter` instead of `enteredAfter` was used. `enter` will enter a symbol in an unknown set of previous phases. Transformations that traverse scope (like erasedMembers in TypeErasure will then see the denotations of these symbols outside the scope in which they are defined. --- src/dotty/tools/dotc/transform/LazyVals.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index ccae24ef7463..e52e2537c8dd 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -175,7 +175,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer { val containerSymbol = ctx.newSymbol(claz, containerName, x.symbol.flags &~ containerFlagsMask | containerFlags | Flags.Private, tpe, coord = x.symbol.coord - ).entered + ).enteredAfter(this) val containerTree = ValDef(containerSymbol, defaultValue(tpe)) if (x.tpe.isNotNull && tpe <:< defn.ObjectType) { // can use 'null' value instead of flag @@ -184,7 +184,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer { } else { val flagName = ctx.freshName(x.name ++ StdNames.nme.BITMAP_PREFIX).toTermName - val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags | Flags.Private, defn.BooleanType).entered + val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags | Flags.Private, defn.BooleanType).enteredAfter(this) val flag = ValDef(flagSymbol, Literal(Constants.Constant(false))) val slowPath = DefDef(x.symbol.asTerm, mkNonThreadSafeDef(ref(containerSymbol), ref(flagSymbol), x.rhs)) Thicket(List(containerTree, flag, slowPath)) @@ -331,7 +331,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer { } val containerName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL).toTermName - val containerSymbol = ctx.newSymbol(claz, containerName, (x.mods &~ containerFlagsMask | containerFlags).flags, tpe, coord = x.symbol.coord).entered + val containerSymbol = ctx.newSymbol(claz, containerName, (x.mods &~ containerFlagsMask | containerFlags).flags, tpe, coord = x.symbol.coord).enteredAfter(this) val containerTree = ValDef(containerSymbol, defaultValue(tpe)) val offset = ref(companion).ensureApplied.select(offsetSymbol) From 0cdb29904affbf55d5f08a005e8b96acfd73aa81 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Jun 2015 13:43:52 +0200 Subject: [PATCH 22/22] Fixed test. Until the previous fix to Denotation-& the test here spuriously passed. The Dotty spec is that value classes synthesize equals and hashCode only if no concrete definitions are inherited except the ones from Any. That was previously miscalculated. The test has been updated to reflect the specified behavior. --- tests/run/t6534.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/run/t6534.scala b/tests/run/t6534.scala index 33df97e41e44..b5789f52c4fa 100644 --- a/tests/run/t6534.scala +++ b/tests/run/t6534.scala @@ -8,7 +8,13 @@ object Test { def main(args: Array[String]): Unit = { val b1 = new Bippy1(71) val b2 = new Bippy2(71) - assert(b1 == b1 && b1.## == b1.x.##, ((b1, b1.##))) - assert(b2 == b2 && b2.## == b2.x.##, ((b2, b2.##))) + assert(b1 == b1) + assert(b1.## == b1.x.##, "hash differs1 " + ((b1, b1.##))) + assert(b2 == b2) + // assert(b2.## == b2.x.##, "hash differs2 " + ((b2, b2.##, b2.x.##))) + // Disabled, this does not hold. Because the value class inherits + // a different hashCode, no code is generated for it. Replaced by: + assert(b2.## == -1) + assert(!b1.equals(b1)) } }