Skip to content

Commit 6ca52b0

Browse files
committed
Merge pull request #622 from dotty-staging/add/implement-scala2-traits
Implement Scala2 traits
2 parents c0770ed + 0cdb299 commit 6ca52b0

27 files changed

+376
-118
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,15 @@ class Compiler {
5656
new Getters,
5757
new ClassTags,
5858
new ElimByName,
59+
new AugmentScala2Traits,
5960
new ResolveSuper),
6061
List(new Erasure),
6162
List(new ElimErasedValueType,
6263
new VCInline,
6364
new Mixin,
6465
new LazyVals,
6566
new Memoize,
67+
new LinkScala2ImplClasses,
6668
new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here
6769
new Constructors,
6870
new FunctionalInterfaces),
@@ -74,7 +76,6 @@ class Compiler {
7476
new ExpandPrivate,
7577
new CollectEntryPoints,
7678
new LabelDefs,
77-
new ElimWildcardIdents,
7879
new TraitConstructors),
7980
List(new GenBCode)
8081
)

src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
418418
Thicket(valdef, clsdef)
419419
}
420420

421+
/** A `_' with given type */
422+
def Underscore(tp: Type)(implicit ctx: Context) = untpd.Ident(nme.WILDCARD).withType(tp)
423+
421424
def defaultValue(tpe: Types.Type)(implicit ctx: Context) = {
422425
val tpw = tpe.widen
423426

@@ -720,6 +723,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
720723
def ensureConforms(tp: Type)(implicit ctx: Context): Tree =
721724
if (tree.tpe <:< tp) tree else asInstance(tp)
722725

726+
/** If inititializer tree is `_', the default value of its type,
727+
* otherwise the tree itself.
728+
*/
729+
def wildcardToDefault(implicit ctx: Context) =
730+
if (isWildcardArg(tree)) defaultValue(tree.tpe) else tree
731+
723732
/** `this && that`, for boolean trees `this`, `that` */
724733
def and(that: Tree)(implicit ctx: Context): Tree =
725734
tree.select(defn.Boolean_&&).appliedTo(that)

src/dotty/tools/dotc/core/Denotations.scala

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -227,16 +227,36 @@ object Denotations {
227227
else
228228
asSingleDenotation
229229

230-
/** Form a denotation by conjoining with denotation `that` */
230+
/** Form a denotation by conjoining with denotation `that`.
231+
*
232+
* NoDenotations are dropped. MultiDenotations are handled by merging
233+
* parts with same signatures. SingleDenotations with equal signatures
234+
* are joined as follows:
235+
*
236+
* In a first step, consider only those denotations which have symbols
237+
* that are accessible from prefix `pre`.
238+
*
239+
* If there are several such denotations, try to pick one by applying the following
240+
* three precedence rules in decreasing order of priority:
241+
*
242+
* 1. Prefer denotations with more specific infos.
243+
* 2. If infos are equally specific, prefer denotations with concrete symbols over denotations
244+
* with abstract symbols.
245+
* 3. If infos are equally specific and symbols are equally concrete,
246+
* prefer denotations with symbols defined in subclasses
247+
* over denotations with symbols defined in proper superclasses.
248+
*
249+
* If there is exactly one (preferred) accessible denotation, return it.
250+
*
251+
* If there is no preferred accessible denotation, return a JointRefDenotation
252+
* with one of the operand symbols (unspecified which one), and an info which
253+
* is intersection (&) of the infos of the operand denotations.
254+
*
255+
* If SingleDenotations with different signatures are joined, return NoDenotation.
256+
*/
231257
def & (that: Denotation, pre: Type)(implicit ctx: Context): Denotation = {
232258

233-
/** Try to merge denot1 and denot2 without adding a new signature.
234-
* Prefer denotations with more specific types, provided the symbol stays accessible
235-
* Prefer denotations with accessible symbols over denotations with
236-
* existing, but inaccessible symbols.
237-
* If there's no preference, produce a JointRefDenotation with the intersection of both infos.
238-
* If unsuccessful, return NoDenotation.
239-
*/
259+
/** Try to merge denot1 and denot2 without adding a new signature. */
240260
def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match {
241261
case denot1 @ MultiDenotation(denot11, denot12) =>
242262
val d1 = mergeDenot(denot11, denot2)
@@ -254,8 +274,24 @@ object Denotations {
254274
val sym1 = denot1.symbol
255275
val sym2 = denot2.symbol
256276
val sym2Accessible = sym2.isAccessibleFrom(pre)
277+
def shadows(sym1: Symbol, sym2: Symbol) = {
278+
val owner1 = sym1.owner
279+
val owner2 = sym2.owner
280+
owner1.derivesFrom(owner2) && owner1.ne(owner2)
281+
}
282+
/** Preference according to order (overrides, isAsConcrete, shadows)*/
257283
def prefer(info1: Type, sym1: Symbol, info2: Type, sym2: Symbol) =
258-
info1.overrides(info2) && (sym1.isAsConcrete(sym2) || !info2.overrides(info1))
284+
info1.overrides(info2) && (
285+
// non-standard ordering of tests for efficiency -
286+
// overrides is costlier to compute than the others, so its 2nd test comes last.
287+
sym1.isAsConcrete(sym2) && (
288+
!sym2.isAsConcrete(sym1)
289+
||
290+
shadows(sym1, sym2)
291+
)
292+
||
293+
!info2.overrides(info1)
294+
)
259295
if (sym2Accessible && prefer(info2, sym2, info1, sym1)) denot2
260296
else {
261297
val sym1Accessible = sym1.isAccessibleFrom(pre)

src/dotty/tools/dotc/core/Flags.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ object Flags {
427427

428428
/** Flags representing modifiers that can appear in trees */
429429
final val ModifierFlags =
430-
SourceModifierFlags | Module | Param | Synthetic | Package | Local
430+
SourceModifierFlags | Module | Param | Synthetic | Package | Local | commonFlags(Mutable)
431431
// | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags
432432

433433
assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags)
@@ -520,12 +520,18 @@ object Flags {
520520
/** A private method */
521521
final val PrivateMethod = allOf(Private, Method)
522522

523+
/** A private accessor */
524+
final val PrivateAccessor = allOf(Private, Accessor)
525+
523526
/** A type parameter with synthesized name */
524527
final val ExpandedTypeParam = allOf(ExpandedName, TypeParam)
525528

526529
/** A parameter or parameter accessor */
527530
final val ParamOrAccessor = Param | ParamAccessor
528531

532+
/** A lazy or deferred value */
533+
final val LazyOrDeferred = Lazy | Deferred
534+
529535
/** A type parameter or type parameter accessor */
530536
final val TypeParamOrAccessor = TypeParam | TypeParamAccessor
531537

src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,17 @@ object NameOps {
155155

156156
/** The expanded name of `name` relative to given class `base`.
157157
*/
158-
def expandedName(base: Symbol)(implicit ctx: Context): N =
159-
expandedName(if (base is Flags.ExpandedName) base.name else base.fullNameSeparated("$"))
158+
def expandedName(base: Symbol, separator: Name)(implicit ctx: Context): N =
159+
expandedName(if (base is Flags.ExpandedName) base.name else base.fullNameSeparated("$"), separator)
160+
161+
def expandedName(base: Symbol)(implicit ctx: Context): N = expandedName(base, nme.EXPAND_SEPARATOR)
160162

161163
/** The expanded name of `name` relative to `basename` with given `separator`
162164
*/
163-
def expandedName(prefix: Name): N =
164-
name.fromName(prefix ++ nme.EXPAND_SEPARATOR ++ name).asInstanceOf[N]
165+
def expandedName(prefix: Name, separator: Name = nme.EXPAND_SEPARATOR): N =
166+
name.fromName(prefix ++ separator ++ name).asInstanceOf[N]
167+
168+
def expandedName(prefix: Name): N = expandedName(prefix, nme.EXPAND_SEPARATOR)
165169

166170
def unexpandedName: N = {
167171
val idx = name.lastIndexOfSlice(nme.EXPAND_SEPARATOR)
@@ -178,6 +182,8 @@ object NameOps {
178182

179183
def revertShadowed: N = likeTyped(name.drop(nme.SHADOWED.length))
180184

185+
def implClassName: N = likeTyped(name ++ tpnme.IMPL_CLASS_SUFFIX)
186+
181187
/** Translate a name into a list of simple TypeNames and TermNames.
182188
* In all segments before the last, type/term is determined by whether
183189
* the following separator char is '.' or '#'. The last segment

src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ object StdNames {
126126
val INITIALIZER_PREFIX: N = "initial$"
127127
val COMPANION_MODULE_METHOD: N = "companion$module"
128128
val COMPANION_CLASS_METHOD: N = "companion$class"
129+
val TRAIT_SETTER_SEPARATOR: N = "$_setter_$"
129130

130131
// value types (and AnyRef) are all used as terms as well
131132
// as (at least) arguments to the @specialize annotation.

src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ object TypeErasure {
164164
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx))
165165
else erase.eraseInfo(tp, sym)(erasureCtx) match {
166166
case einfo: MethodType if sym.isGetter && einfo.resultType.isRef(defn.UnitClass) =>
167-
MethodType(Nil, Nil, defn.BoxedUnitClass.typeRef)
167+
MethodType(Nil, defn.BoxedUnitClass.typeRef)
168168
case einfo =>
169169
einfo
170170
}

src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
172172
* parent { type Apply = body; argBindings? }
173173
*
174174
* split it into
175-
175+
*
176176
* - the `parent`
177177
* - the simplified `body`
178178
* - the bindings HK$ members, if there are any
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import TreeTransforms._
6+
import Contexts.Context
7+
import Flags._
8+
import SymUtils._
9+
import Symbols._
10+
import SymDenotations._
11+
import Types._
12+
import Decorators._
13+
import DenotTransformers._
14+
import StdNames._
15+
import NameOps._
16+
import ast.Trees._
17+
18+
/** This phase augments Scala2 traits with implementation classes and with additional members
19+
* needed for mixin composition.
20+
* These symbols would have been added between Unpickling and Mixin in the Scala2 pipeline.
21+
* Specifcally, it adds
22+
*
23+
* - an implementation class which defines a trait constructor and trait method implementations
24+
* - trait setters for vals defined in traits
25+
*
26+
* Furthermore, it expands the names of all private getters and setters in the trait and makes
27+
* them not-private.
28+
*/
29+
class AugmentScala2Traits extends MiniPhaseTransform with IdentityDenotTransformer with FullParameterization { thisTransform =>
30+
import ast.tpd._
31+
32+
override def phaseName: String = "augmentScala2Traits"
33+
34+
override def rewiredTarget(referenced: Symbol, derived: Symbol)(implicit ctx: Context) = NoSymbol
35+
36+
override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = {
37+
val cls = impl.symbol.owner.asClass
38+
for (mixin <- cls.mixins)
39+
if (mixin.is(Scala2x))
40+
augmentScala2Trait(mixin, cls)
41+
impl
42+
}
43+
44+
private def augmentScala2Trait(mixin: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context): Unit = {
45+
if (mixin.implClass.is(Scala2x)) () // nothing to do, mixin was already augmented
46+
else {
47+
//println(i"creating new implclass for $mixin ${mixin.implClass}")
48+
val ops = new MixinOps(cls, thisTransform)
49+
import ops._
50+
51+
val implClass = ctx.newCompleteClassSymbol(
52+
owner = mixin.owner,
53+
name = mixin.name.implClassName,
54+
flags = Abstract | Scala2x,
55+
parents = defn.ObjectClass.typeRef :: Nil,
56+
assocFile = mixin.assocFile).enteredAfter(thisTransform)
57+
58+
def implMethod(meth: TermSymbol): Symbol = {
59+
val mold =
60+
if (meth.isConstructor)
61+
meth.copySymDenotation(
62+
name = nme.IMPLCLASS_CONSTRUCTOR,
63+
info = MethodType(Nil, defn.UnitType))
64+
else meth.ensureNotPrivate
65+
meth.copy(
66+
owner = implClass,
67+
name = mold.name.asTermName,
68+
flags = Method | JavaStatic | mold.flags & ExpandedName,
69+
info = fullyParameterizedType(mold.info, mixin))
70+
}
71+
72+
def traitSetter(getter: TermSymbol) = {
73+
val separator = if (getter.is(Private)) nme.EXPAND_SEPARATOR else nme.TRAIT_SETTER_SEPARATOR
74+
val expandedGetterName =
75+
if (getter.is(ExpandedName)) getter.name
76+
else getter.name.expandedName(getter.owner, separator)
77+
getter.copy(
78+
name = expandedGetterName.setterName,
79+
flags = Method | Accessor | ExpandedName,
80+
info = MethodType(getter.info.resultType :: Nil, defn.UnitType))
81+
}
82+
83+
for (sym <- mixin.info.decls) {
84+
if (needsForwarder(sym) || sym.isConstructor || sym.isGetter && sym.is(Lazy))
85+
implClass.enter(implMethod(sym.asTerm))
86+
if (sym.isGetter && !sym.is(LazyOrDeferred) &&
87+
!sym.setter.exists && !sym.info.resultType.isInstanceOf[ConstantType])
88+
traitSetter(sym.asTerm).enteredAfter(thisTransform)
89+
if (sym.is(PrivateAccessor, butNot = ExpandedName) &&
90+
(sym.isGetter || sym.isSetter)) // strangely, Scala 2 fields are also methods that have Accessor set.
91+
sym.ensureNotPrivate.installAfter(thisTransform)
92+
}
93+
ctx.log(i"Scala2x trait decls of $mixin = ${mixin.info.decls.toList.map(_.showDcl)}%\n %")
94+
ctx.log(i"Scala2x impl decls of $mixin = ${implClass.info.decls.toList.map(_.showDcl)}%\n %")
95+
}
96+
}
97+
}

src/dotty/tools/dotc/transform/Constructors.scala

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,19 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
3030
import tpd._
3131

3232
override def phaseName: String = "constructors"
33-
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure])
33+
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize])
3434

3535

36-
/** All initializers should be moved into constructor
37-
*/
36+
/** All initializers for non-lazy fields should be moved into constructor.
37+
* All non-abstract methods should be implemented (this is assured for constructors
38+
* in this phase and for other methods in memoize).
39+
*/
3840
override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
3941
tree match {
40-
case t: ValDef if ((t.rhs ne EmptyTree) && !(t.symbol is Flags.Lazy) && t.symbol.owner.isClass) =>
41-
assert(false, i"$t initializers should be moved to constructors")
42+
case tree: ValDef if tree.symbol.exists && tree.symbol.owner.isClass && !tree.symbol.is(Lazy) =>
43+
assert(tree.rhs.isEmpty, i"$tree: initializer should be moved to constructors")
44+
case tree: DefDef if !tree.symbol.is(LazyOrDeferred) =>
45+
assert(!tree.rhs.isEmpty, i"unimplemented: $tree")
4246
case _ =>
4347
}
4448
}

src/dotty/tools/dotc/transform/ElimWildcardIdents.scala

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/dotty/tools/dotc/transform/ExpandSAMs.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
7474
val defaultSym = ctx.newSymbol(isDefinedAtFn, nme.WILDCARD, Synthetic, selector.tpe.widen)
7575
val defaultCase =
7676
CaseDef(
77-
Bind(defaultSym, untpd.Ident(nme.WILDCARD).withType(selector.tpe.widen)),
77+
Bind(defaultSym, Underscore(selector.tpe.widen)),
7878
EmptyTree,
7979
Literal(Constant(false)))
8080
cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase)

0 commit comments

Comments
 (0)