Skip to content

Commit 2dbabca

Browse files
committed
Merge pull request #411 from smarter/add/value-classes
Implement value classes
2 parents 275c340 + d012f93 commit 2dbabca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+617
-192
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ class Compiler {
5656
new ElimByName,
5757
new ResolveSuper),
5858
List(new Erasure),
59-
List(new Mixin,
59+
List(new ElimErasedValueType,
60+
new VCInline,
61+
new Mixin,
6062
new LazyVals,
6163
new Memoize,
6264
new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ object Phases {
232232

233233
private val typerCache = new PhaseCache(classOf[FrontEnd])
234234
private val refChecksCache = new PhaseCache(classOf[RefChecks])
235+
private val extensionMethodsCache = new PhaseCache(classOf[ExtensionMethods])
235236
private val erasureCache = new PhaseCache(classOf[Erasure])
236237
private val patmatCache = new PhaseCache(classOf[PatternMatcher])
237238
private val flattenCache = new PhaseCache(classOf[Flatten])
@@ -241,6 +242,7 @@ object Phases {
241242

242243
def typerPhase = typerCache.phase
243244
def refchecksPhase = refChecksCache.phase
245+
def extensionMethodsPhase = extensionMethodsCache.phase
244246
def erasurePhase = erasureCache.phase
245247
def patmatPhase = patmatCache.phase
246248
def flattenPhase = flattenCache.phase

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ object StdNames {
226226
val ANYname: N = "<anyname>"
227227
val CONSTRUCTOR: N = Names.CONSTRUCTOR.toString
228228
val DEFAULT_CASE: N = "defaultCase$"
229+
val EVT2U: N = "evt2u$"
229230
val EQEQ_LOCAL_VAR: N = "eqEqTemp$"
230231
val FAKE_LOCAL_THIS: N = "this$"
231232
val IMPLCLASS_CONSTRUCTOR: N = "$init$"
@@ -257,6 +258,7 @@ object StdNames {
257258
val SKOLEM: N = "<skolem>"
258259
val SPECIALIZED_INSTANCE: N = "specInstance$"
259260
val THIS: N = "_$this"
261+
val U2EVT: N = "u2evt$"
260262

261263
final val Nil: N = "Nil"
262264
final val Predef: N = "Predef"

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,10 +1447,16 @@ object SymDenotations {
14471447

14481448
def inCache(tp: Type) = baseTypeRefCache.containsKey(tp)
14491449

1450-
/** Can't cache types containing type variables which are uninstantiated
1451-
* or whose instances can change, depending on typerstate.
1450+
/** We cannot cache:
1451+
* - type variables which are uninstantiated or whose instances can
1452+
* change, depending on typerstate.
1453+
* - types where the underlying type is an ErasedValueType, because
1454+
* this underlying type will change after ElimErasedValueType,
1455+
* and this changes subtyping relations. As a shortcut, we do not
1456+
* cache ErasedValueType at all.
14521457
*/
14531458
def isCachable(tp: Type): Boolean = tp match {
1459+
case _: TypeErasure.ErasedValueType => false
14541460
case tp: TypeVar => tp.inst.exists && inCache(tp.inst)
14551461
case tp: TypeProxy => inCache(tp.underlying)
14561462
case tp: AndOrType => inCache(tp.tp1) && inCache(tp.tp2)

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -434,14 +434,6 @@ object Symbols {
434434
/** If this symbol satisfies predicate `p` this symbol, otherwise `NoSymbol` */
435435
def filter(p: Symbol => Boolean): Symbol = if (p(this)) this else NoSymbol
436436

437-
/** Is this symbol a user-defined value class? */
438-
final def isDerivedValueClass(implicit ctx: Context): Boolean = {
439-
this.derivesFrom(defn.AnyValClass)(ctx.withPhase(denot.validFor.firstPhaseId))
440-
// Simulate ValueClasses.isDerivedValueClass
441-
false // will migrate to ValueClasses.isDerivedValueClass;
442-
// unsupported value class code will continue to use this stub while it exists
443-
}
444-
445437
/** The current name of this symbol */
446438
final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName]
447439

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi
229229
compareSuper
230230
case AndType(tp21, tp22) =>
231231
isSubType(tp1, tp21) && isSubType(tp1, tp22)
232+
case TypeErasure.ErasedValueType(cls2, underlying2) =>
233+
def compareErasedValueType = tp1 match {
234+
case TypeErasure.ErasedValueType(cls1, underlying1) =>
235+
(cls1 eq cls2) && isSameType(underlying1, underlying2)
236+
case _ =>
237+
secondTry(tp1, tp2)
238+
}
239+
compareErasedValueType
232240
case ErrorType =>
233241
true
234242
case _ =>

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

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package dotc
33
package core
44

55
import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._, Flags.JavaDefined
6+
import Uniques.unique
67
import dotc.transform.ExplicitOuter._
8+
import dotc.transform.ValueClasses._
79
import typer.Mode
810
import util.DotClass
911

1012
/** Erased types are:
1113
*
14+
* ErasedValueType
1215
* TypeRef(prefix is ignored, denot is ClassDenotation)
1316
* TermRef(prefix is ignored, denot is SymDenotation)
1417
* JavaArrayType
@@ -29,8 +32,12 @@ object TypeErasure {
2932

3033
/** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and
3134
* isInstanceOf may have types that do not satisfy the predicate.
35+
* ErasedValueType is considered an erased type because it is valid after Erasure (it is
36+
* eliminated by ElimErasedValueType).
3237
*/
3338
def isErasedType(tp: Type)(implicit ctx: Context): Boolean = tp match {
39+
case _: ErasedValueType =>
40+
true
3441
case tp: TypeRef =>
3542
tp.symbol.isClass && tp.symbol != defn.AnyClass
3643
case _: TermRef =>
@@ -51,55 +58,68 @@ object TypeErasure {
5158
false
5259
}
5360

54-
case class ErasedValueType(cls: ClassSymbol, underlying: Type) extends CachedGroundType {
55-
override def computeHash = doHash(cls, underlying)
61+
/** A type representing the semi-erasure of a derived value class, see SIP-15
62+
* where it's called "C$unboxed" for a class C.
63+
* Derived value classes are erased to this type during Erasure (when
64+
* semiEraseVCs = true) and subsequently erased to their underlying type
65+
* during ElimErasedValueType. This type is outside the normal Scala class
66+
* hierarchy: it is a subtype of no other type and is a supertype only of
67+
* Nothing. This is because this type is only useful for type adaptation (see
68+
* [[Erasure.Boxing#adaptToType]]).
69+
*
70+
* @param cls The value class symbol
71+
* @param erasedUnderlying The erased type of the single field of the value class
72+
*/
73+
abstract case class ErasedValueType(cls: ClassSymbol, erasedUnderlying: Type)
74+
extends CachedGroundType with ValueType {
75+
override def computeHash = doHash(cls, erasedUnderlying)
5676
}
5777

58-
private def erasureIdx(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
78+
final class CachedErasedValueType(cls: ClassSymbol, erasedUnderlying: Type)
79+
extends ErasedValueType(cls, erasedUnderlying)
80+
81+
object ErasedValueType {
82+
def apply(cls: ClassSymbol, erasedUnderlying: Type)(implicit ctx: Context) = {
83+
unique(new CachedErasedValueType(cls, erasedUnderlying))
84+
}
85+
}
86+
87+
private def erasureIdx(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
5988
(if (isJava) 1 else 0) +
60-
(if (isSemi) 2 else 0) +
89+
(if (semiEraseVCs) 2 else 0) +
6190
(if (isConstructor) 4 else 0) +
6291
(if (wildcardOK) 8 else 0)
6392

6493
private val erasures = new Array[TypeErasure](16)
6594

6695
for {
6796
isJava <- List(false, true)
68-
isSemi <- List(false, true)
97+
semiEraseVCs <- List(false, true)
6998
isConstructor <- List(false, true)
7099
wildcardOK <- List(false, true)
71-
} erasures(erasureIdx(isJava, isSemi, isConstructor, wildcardOK)) =
72-
new TypeErasure(isJava, isSemi, isConstructor, wildcardOK)
100+
} erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) =
101+
new TypeErasure(isJava, semiEraseVCs, isConstructor, wildcardOK)
73102

74-
/** Produces an erasure function.
75-
* @param isJava Arguments should be treated the way Java does it
76-
* @param isSemi Value classes are mapped in an intermediate step to
77-
* ErasedValueClass types, instead of going directly to
78-
* the erasure of the underlying type.
79-
* @param isConstructor Argument forms part of the type of a constructor
80-
* @param wildcardOK Wildcards are acceptable (true when using the erasure
81-
* for computing a signature name).
103+
/** Produces an erasure function. See the documentation of the class [[TypeErasure]]
104+
* for a description of each parameter.
82105
*/
83-
private def erasureFn(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure =
84-
erasures(erasureIdx(isJava, isSemi, isConstructor, wildcardOK))
85-
86-
private val scalaErasureFn = erasureFn(isJava = false, isSemi = false, isConstructor = false, wildcardOK = false)
87-
private val scalaSigFn = erasureFn(isJava = false, isSemi = false, isConstructor = false, wildcardOK = true)
88-
private val javaSigFn = erasureFn(isJava = true, isSemi = false, isConstructor = false, wildcardOK = true)
89-
private val semiErasureFn = erasureFn(isJava = false, isSemi = true, isConstructor = false, wildcardOK = false)
106+
private def erasureFn(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure =
107+
erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK))
90108

91109
/** The current context with a phase no later than erasure */
92110
private def erasureCtx(implicit ctx: Context) =
93111
if (ctx.erasedTypes) ctx.withPhase(ctx.erasurePhase).addMode(Mode.FutureDefsOK) else ctx
94112

95-
def erasure(tp: Type)(implicit ctx: Context): Type = scalaErasureFn(tp)(erasureCtx)
96-
def semiErasure(tp: Type)(implicit ctx: Context): Type = semiErasureFn(tp)(erasureCtx)
113+
def erasure(tp: Type, semiEraseVCs: Boolean = true)(implicit ctx: Context): Type =
114+
erasureFn(isJava = false, semiEraseVCs, isConstructor = false, wildcardOK = false)(tp)(erasureCtx)
115+
97116
def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): TypeName = {
98117
val seqClass = if (isJava) defn.ArrayClass else defn.SeqClass
99118
val normTp =
100119
if (tp.isRepeatedParam) tp.translateParameterized(defn.RepeatedParamClass, seqClass)
101120
else tp
102-
(if (isJava) javaSigFn else scalaSigFn).sigName(normTp)(erasureCtx)
121+
val erase = erasureFn(isJava, semiEraseVCs = false, isConstructor = false, wildcardOK = true)
122+
erase.sigName(normTp)(erasureCtx)
103123
}
104124

105125
/** The erasure of a top-level reference. Differs from normal erasure in that
@@ -117,29 +137,20 @@ object TypeErasure {
117137
erasure(tp)
118138
}
119139

120-
/** The erasure of a symbol's info. This is different of `erasure` in the way `ExprType`s are
121-
* treated. `eraseInfo` maps them them to nullary method types, whereas `erasure` maps them
122-
* to `Function0`.
123-
*/
124-
def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type =
125-
scalaErasureFn.eraseInfo(tp, sym)(erasureCtx)
126-
127-
/** The erasure of a function result type. Differs from normal erasure in that
128-
* Unit is kept instead of being mapped to BoxedUnit.
129-
*/
130-
def eraseResult(tp: Type)(implicit ctx: Context): Type =
131-
scalaErasureFn.eraseResult(tp)(erasureCtx)
132-
133140
/** The symbol's erased info. This is the type's erasure, except for the following symbols:
134141
*
135142
* - For $asInstanceOf : [T]T
136143
* - For $isInstanceOf : [T]Boolean
137144
* - For all abstract types : = ?
145+
* - For COMPANION_CLASS_METHOD : the erasure of their type with semiEraseVCs = false,
146+
* this is needed to keep [[SymDenotation#companionClass]]
147+
* working after erasure for value classes.
138148
* - For all other symbols : the semi-erasure of their types, with
139149
* isJava, isConstructor set according to symbol.
140150
*/
141151
def transformInfo(sym: Symbol, tp: Type)(implicit ctx: Context): Type = {
142-
val erase = erasureFn(sym is JavaDefined, isSemi = true, sym.isConstructor, wildcardOK = false)
152+
val semiEraseVCs = sym.name ne nme.COMPANION_CLASS_METHOD
153+
val erase = erasureFn(sym is JavaDefined, semiEraseVCs, sym.isConstructor, wildcardOK = false)
143154

144155
def eraseParamBounds(tp: PolyType): Type =
145156
tp.derivedPolyType(
@@ -148,7 +159,7 @@ object TypeErasure {
148159
if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType])
149160
else if (sym.isAbstractType) TypeAlias(WildcardType)
150161
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx))
151-
else eraseInfo(tp, sym)(erasureCtx) match {
162+
else erase.eraseInfo(tp, sym)(erasureCtx) match {
152163
case einfo: MethodType if sym.isGetter && einfo.resultType.isRef(defn.UnitClass) =>
153164
defn.BoxedUnitClass.typeRef
154165
case einfo =>
@@ -241,12 +252,15 @@ object TypeErasure {
241252
import TypeErasure._
242253

243254
/**
244-
* This is used as the Scala erasure during the erasure phase itself
245-
* It differs from normal erasure in that value classes are erased to ErasedValueTypes which
246-
* are then later converted to the underlying parameter type in phase posterasure.
247-
*
255+
* @param isJava Arguments should be treated the way Java does it
256+
* @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType
257+
* (they will be fully erased in [[ElimErasedValueType]]).
258+
* If false, they are erased like normal classes.
259+
* @param isConstructor Argument forms part of the type of a constructor
260+
* @param wildcardOK Wildcards are acceptable (true when using the erasure
261+
* for computing a signature name).
248262
*/
249-
class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) extends DotClass {
263+
class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) extends DotClass {
250264

251265
/** The erasure |T| of a type T. This is:
252266
*
@@ -279,10 +293,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
279293
* - For any other type, exception.
280294
*/
281295
private def apply(tp: Type)(implicit ctx: Context): Type = tp match {
296+
case _: ErasedValueType =>
297+
tp
282298
case tp: TypeRef =>
283299
val sym = tp.symbol
284300
if (!sym.isClass) this(tp.info)
285-
else if (sym.isDerivedValueClass) eraseDerivedValueClassRef(tp)
301+
else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
286302
else eraseNormalClassRef(tp)
287303
case tp: RefinedType =>
288304
val parent = tp.parent
@@ -291,7 +307,9 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
291307
case tp: TermRef =>
292308
this(tp.widen)
293309
case tp: ThisType =>
294-
this(tp.cls.typeRef)
310+
def thisTypeErasure(tpToErase: Type) =
311+
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
312+
thisTypeErasure(tp.cls.typeRef)
295313
case SuperType(thistpe, supertpe) =>
296314
SuperType(this(thistpe), this(supertpe))
297315
case ExprType(rt) =>
@@ -303,7 +321,8 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
303321
case OrType(tp1, tp2) =>
304322
ctx.typeComparer.orType(this(tp1), this(tp2), erased = true)
305323
case tp: MethodType =>
306-
val paramErasure = erasureFn(tp.isJava, isSemi, isConstructor, wildcardOK)(_)
324+
def paramErasure(tpToErase: Type) =
325+
erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
307326
val formals = tp.paramTypes.mapConserve(paramErasure)
308327
eraseResult(tp.resultType) match {
309328
case rt: MethodType =>
@@ -341,11 +360,17 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
341360

342361
private def eraseArray(tp: RefinedType)(implicit ctx: Context) = {
343362
val defn.ArrayType(elemtp) = tp
363+
def arrayErasure(tpToErase: Type) =
364+
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
344365
if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType)
345366
else if (isUnboundedGeneric(elemtp)) defn.ObjectType
346-
else JavaArrayType(this(elemtp))
367+
else JavaArrayType(arrayErasure(elemtp))
347368
}
348369

370+
/** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s are
371+
* treated. `eraseInfo` maps them them to nullary method types, whereas `apply` maps them
372+
* to `Function0`.
373+
*/
349374
def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match {
350375
case ExprType(rt) =>
351376
if (sym is Param) apply(tp)
@@ -354,22 +379,30 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
354379
// forwarders to mixin methods.
355380
// See doc comment for ElimByName for speculation how we could improve this.
356381
else MethodType(Nil, Nil, eraseResult(rt))
357-
case tp => erasure(tp)
382+
case tp => this(tp)
383+
}
384+
385+
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
386+
val cls = tref.symbol.asClass
387+
val underlying = underlyingOfValueClass(cls)
388+
ErasedValueType(cls, erasure(underlying))
358389
}
359390

360-
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type =
361-
unsupported("eraseDerivedValueClass")
362391

363392
private def eraseNormalClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
364393
val cls = tref.symbol.asClass
365394
(if (cls.owner is Package) normalizeClass(cls) else cls).typeRef
366395
}
367396

397+
/** The erasure of a function result type. */
368398
private def eraseResult(tp: Type)(implicit ctx: Context): Type = tp match {
369399
case tp: TypeRef =>
370400
val sym = tp.typeSymbol
371401
if (sym eq defn.UnitClass) sym.typeRef
372-
else if (sym.isDerivedValueClass) eraseNormalClassRef(tp)
402+
// For a value class V, "new V(x)" should have type V for type adaptation to work
403+
// correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the return type of a
404+
// constructor method should not be semi-erased.
405+
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
373406
else this(tp)
374407
case RefinedType(parent, _) if !(parent isRef defn.ArrayClass) =>
375408
eraseResult(parent)
@@ -391,10 +424,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
391424
* Need to ensure correspondence with erasure!
392425
*/
393426
private def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match {
427+
case ErasedValueType(_, underlying) =>
428+
sigName(underlying)
394429
case tp: TypeRef =>
395430
val sym = tp.symbol
396431
if (!sym.isClass) sigName(tp.info)
397-
else if (sym.isDerivedValueClass) sigName(eraseDerivedValueClassRef(tp))
432+
else if (isDerivedValueClass(sym)) sigName(eraseDerivedValueClassRef(tp))
398433
else normalizeClass(sym.asClass).fullName.asTypeName
399434
case defn.ArrayType(elem) =>
400435
sigName(this(tp))

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package printing
33

44
import core._
55
import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._
6+
import TypeErasure.ErasedValueType
67
import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation
78
import StdNames.nme
89
import ast.{Trees, untpd, tpd}
@@ -132,6 +133,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
132133
return toText(tp.info)
133134
case ExprType(result) =>
134135
return "=> " ~ toText(result)
136+
case ErasedValueType(clazz, underlying) =>
137+
return "ErasedValueType(" ~ toText(clazz.typeRef) ~ ", " ~ toText(underlying) ~ ")"
135138
case tp: ClassInfo =>
136139
return toTextParents(tp.instantiatedParents) ~ "{...}"
137140
case JavaArrayType(elemtp) =>

0 commit comments

Comments
 (0)