aboutsummaryrefslogtreecommitdiff
path: root/src/dotty
diff options
context:
space:
mode:
authorDmitry Petrashko <dark@d-d.me>2015-05-01 14:28:09 +0200
committerDmitry Petrashko <dark@d-d.me>2015-05-01 14:28:09 +0200
commit2dbabca38cee0ff27c92a9a7079959b2461c9869 (patch)
tree3462a1b9ed58a1cca5f56e205c55c970966ad86c /src/dotty
parent275c340b609ae113c93d8c004a2d8bd7be1b12c1 (diff)
parentd012f93635184dc8aa6325b715a133861c74ab08 (diff)
downloaddotty-2dbabca38cee0ff27c92a9a7079959b2461c9869.tar.gz
dotty-2dbabca38cee0ff27c92a9a7079959b2461c9869.tar.bz2
dotty-2dbabca38cee0ff27c92a9a7079959b2461c9869.zip
Merge pull request #411 from smarter/add/value-classes
Implement value classes
Diffstat (limited to 'src/dotty')
-rw-r--r--src/dotty/tools/dotc/Compiler.scala4
-rw-r--r--src/dotty/tools/dotc/core/Phases.scala2
-rw-r--r--src/dotty/tools/dotc/core/StdNames.scala2
-rw-r--r--src/dotty/tools/dotc/core/SymDenotations.scala10
-rw-r--r--src/dotty/tools/dotc/core/Symbols.scala8
-rw-r--r--src/dotty/tools/dotc/core/TypeComparer.scala8
-rw-r--r--src/dotty/tools/dotc/core/TypeErasure.scala143
-rw-r--r--src/dotty/tools/dotc/printing/RefinedPrinter.scala3
-rw-r--r--src/dotty/tools/dotc/transform/ElimErasedValueType.scala82
-rw-r--r--src/dotty/tools/dotc/transform/Erasure.scala136
-rw-r--r--src/dotty/tools/dotc/transform/ExtensionMethods.scala154
-rw-r--r--src/dotty/tools/dotc/transform/FullParameterization.scala21
-rw-r--r--src/dotty/tools/dotc/transform/InterceptedMethods.scala1
-rw-r--r--src/dotty/tools/dotc/transform/TreeExtractors.scala48
-rw-r--r--src/dotty/tools/dotc/transform/TypeTestsCasts.scala7
-rw-r--r--src/dotty/tools/dotc/transform/VCInline.scala59
-rw-r--r--src/dotty/tools/dotc/transform/ValueClasses.scala15
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala2
18 files changed, 546 insertions, 159 deletions
diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala
index 102d99347..2b5748229 100644
--- a/src/dotty/tools/dotc/Compiler.scala
+++ b/src/dotty/tools/dotc/Compiler.scala
@@ -56,7 +56,9 @@ class Compiler {
new ElimByName,
new ResolveSuper),
List(new Erasure),
- List(new Mixin,
+ List(new ElimErasedValueType,
+ new VCInline,
+ new Mixin,
new LazyVals,
new Memoize,
new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here
diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala
index 96066db5e..406a3457a 100644
--- a/src/dotty/tools/dotc/core/Phases.scala
+++ b/src/dotty/tools/dotc/core/Phases.scala
@@ -232,6 +232,7 @@ object Phases {
private val typerCache = new PhaseCache(classOf[FrontEnd])
private val refChecksCache = new PhaseCache(classOf[RefChecks])
+ private val extensionMethodsCache = new PhaseCache(classOf[ExtensionMethods])
private val erasureCache = new PhaseCache(classOf[Erasure])
private val patmatCache = new PhaseCache(classOf[PatternMatcher])
private val flattenCache = new PhaseCache(classOf[Flatten])
@@ -241,6 +242,7 @@ object Phases {
def typerPhase = typerCache.phase
def refchecksPhase = refChecksCache.phase
+ def extensionMethodsPhase = extensionMethodsCache.phase
def erasurePhase = erasureCache.phase
def patmatPhase = patmatCache.phase
def flattenPhase = flattenCache.phase
diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala
index 829ff8b8f..74a121b47 100644
--- a/src/dotty/tools/dotc/core/StdNames.scala
+++ b/src/dotty/tools/dotc/core/StdNames.scala
@@ -226,6 +226,7 @@ object StdNames {
val ANYname: N = "<anyname>"
val CONSTRUCTOR: N = Names.CONSTRUCTOR.toString
val DEFAULT_CASE: N = "defaultCase$"
+ val EVT2U: N = "evt2u$"
val EQEQ_LOCAL_VAR: N = "eqEqTemp$"
val FAKE_LOCAL_THIS: N = "this$"
val IMPLCLASS_CONSTRUCTOR: N = "$init$"
@@ -257,6 +258,7 @@ object StdNames {
val SKOLEM: N = "<skolem>"
val SPECIALIZED_INSTANCE: N = "specInstance$"
val THIS: N = "_$this"
+ val U2EVT: N = "u2evt$"
final val Nil: N = "Nil"
final val Predef: N = "Predef"
diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala
index bcd46810e..14be606a1 100644
--- a/src/dotty/tools/dotc/core/SymDenotations.scala
+++ b/src/dotty/tools/dotc/core/SymDenotations.scala
@@ -1447,10 +1447,16 @@ object SymDenotations {
def inCache(tp: Type) = baseTypeRefCache.containsKey(tp)
- /** Can't cache types containing type variables which are uninstantiated
- * or whose instances can change, depending on typerstate.
+ /** We cannot cache:
+ * - type variables which are uninstantiated or whose instances can
+ * change, depending on typerstate.
+ * - types where the underlying type is an ErasedValueType, because
+ * this underlying type will change after ElimErasedValueType,
+ * and this changes subtyping relations. As a shortcut, we do not
+ * cache ErasedValueType at all.
*/
def isCachable(tp: Type): Boolean = tp match {
+ case _: TypeErasure.ErasedValueType => false
case tp: TypeVar => tp.inst.exists && inCache(tp.inst)
case tp: TypeProxy => inCache(tp.underlying)
case tp: AndOrType => inCache(tp.tp1) && inCache(tp.tp2)
diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala
index 9f18e723c..2b91efbcd 100644
--- a/src/dotty/tools/dotc/core/Symbols.scala
+++ b/src/dotty/tools/dotc/core/Symbols.scala
@@ -434,14 +434,6 @@ object Symbols {
/** If this symbol satisfies predicate `p` this symbol, otherwise `NoSymbol` */
def filter(p: Symbol => Boolean): Symbol = if (p(this)) this else NoSymbol
- /** Is this symbol a user-defined value class? */
- final def isDerivedValueClass(implicit ctx: Context): Boolean = {
- this.derivesFrom(defn.AnyValClass)(ctx.withPhase(denot.validFor.firstPhaseId))
- // Simulate ValueClasses.isDerivedValueClass
- false // will migrate to ValueClasses.isDerivedValueClass;
- // unsupported value class code will continue to use this stub while it exists
- }
-
/** The current name of this symbol */
final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName]
diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala
index a59a64a91..18f9f08bb 100644
--- a/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -229,6 +229,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi
compareSuper
case AndType(tp21, tp22) =>
isSubType(tp1, tp21) && isSubType(tp1, tp22)
+ case TypeErasure.ErasedValueType(cls2, underlying2) =>
+ def compareErasedValueType = tp1 match {
+ case TypeErasure.ErasedValueType(cls1, underlying1) =>
+ (cls1 eq cls2) && isSameType(underlying1, underlying2)
+ case _ =>
+ secondTry(tp1, tp2)
+ }
+ compareErasedValueType
case ErrorType =>
true
case _ =>
diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala
index 20cf816c2..e695e6721 100644
--- a/src/dotty/tools/dotc/core/TypeErasure.scala
+++ b/src/dotty/tools/dotc/core/TypeErasure.scala
@@ -3,12 +3,15 @@ package dotc
package core
import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._, Flags.JavaDefined
+import Uniques.unique
import dotc.transform.ExplicitOuter._
+import dotc.transform.ValueClasses._
import typer.Mode
import util.DotClass
/** Erased types are:
*
+ * ErasedValueType
* TypeRef(prefix is ignored, denot is ClassDenotation)
* TermRef(prefix is ignored, denot is SymDenotation)
* JavaArrayType
@@ -29,8 +32,12 @@ object TypeErasure {
/** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and
* isInstanceOf may have types that do not satisfy the predicate.
+ * ErasedValueType is considered an erased type because it is valid after Erasure (it is
+ * eliminated by ElimErasedValueType).
*/
def isErasedType(tp: Type)(implicit ctx: Context): Boolean = tp match {
+ case _: ErasedValueType =>
+ true
case tp: TypeRef =>
tp.symbol.isClass && tp.symbol != defn.AnyClass
case _: TermRef =>
@@ -51,13 +58,35 @@ object TypeErasure {
false
}
- case class ErasedValueType(cls: ClassSymbol, underlying: Type) extends CachedGroundType {
- override def computeHash = doHash(cls, underlying)
+ /** A type representing the semi-erasure of a derived value class, see SIP-15
+ * where it's called "C$unboxed" for a class C.
+ * Derived value classes are erased to this type during Erasure (when
+ * semiEraseVCs = true) and subsequently erased to their underlying type
+ * during ElimErasedValueType. This type is outside the normal Scala class
+ * hierarchy: it is a subtype of no other type and is a supertype only of
+ * Nothing. This is because this type is only useful for type adaptation (see
+ * [[Erasure.Boxing#adaptToType]]).
+ *
+ * @param cls The value class symbol
+ * @param erasedUnderlying The erased type of the single field of the value class
+ */
+ abstract case class ErasedValueType(cls: ClassSymbol, erasedUnderlying: Type)
+ extends CachedGroundType with ValueType {
+ override def computeHash = doHash(cls, erasedUnderlying)
}
- private def erasureIdx(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
+ final class CachedErasedValueType(cls: ClassSymbol, erasedUnderlying: Type)
+ extends ErasedValueType(cls, erasedUnderlying)
+
+ object ErasedValueType {
+ def apply(cls: ClassSymbol, erasedUnderlying: Type)(implicit ctx: Context) = {
+ unique(new CachedErasedValueType(cls, erasedUnderlying))
+ }
+ }
+
+ private def erasureIdx(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
(if (isJava) 1 else 0) +
- (if (isSemi) 2 else 0) +
+ (if (semiEraseVCs) 2 else 0) +
(if (isConstructor) 4 else 0) +
(if (wildcardOK) 8 else 0)
@@ -65,41 +94,32 @@ object TypeErasure {
for {
isJava <- List(false, true)
- isSemi <- List(false, true)
+ semiEraseVCs <- List(false, true)
isConstructor <- List(false, true)
wildcardOK <- List(false, true)
- } erasures(erasureIdx(isJava, isSemi, isConstructor, wildcardOK)) =
- new TypeErasure(isJava, isSemi, isConstructor, wildcardOK)
+ } erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) =
+ new TypeErasure(isJava, semiEraseVCs, isConstructor, wildcardOK)
- /** Produces an erasure function.
- * @param isJava Arguments should be treated the way Java does it
- * @param isSemi Value classes are mapped in an intermediate step to
- * ErasedValueClass types, instead of going directly to
- * the erasure of the underlying type.
- * @param isConstructor Argument forms part of the type of a constructor
- * @param wildcardOK Wildcards are acceptable (true when using the erasure
- * for computing a signature name).
+ /** Produces an erasure function. See the documentation of the class [[TypeErasure]]
+ * for a description of each parameter.
*/
- private def erasureFn(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure =
- erasures(erasureIdx(isJava, isSemi, isConstructor, wildcardOK))
-
- private val scalaErasureFn = erasureFn(isJava = false, isSemi = false, isConstructor = false, wildcardOK = false)
- private val scalaSigFn = erasureFn(isJava = false, isSemi = false, isConstructor = false, wildcardOK = true)
- private val javaSigFn = erasureFn(isJava = true, isSemi = false, isConstructor = false, wildcardOK = true)
- private val semiErasureFn = erasureFn(isJava = false, isSemi = true, isConstructor = false, wildcardOK = false)
+ private def erasureFn(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure =
+ erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK))
/** The current context with a phase no later than erasure */
private def erasureCtx(implicit ctx: Context) =
if (ctx.erasedTypes) ctx.withPhase(ctx.erasurePhase).addMode(Mode.FutureDefsOK) else ctx
- def erasure(tp: Type)(implicit ctx: Context): Type = scalaErasureFn(tp)(erasureCtx)
- def semiErasure(tp: Type)(implicit ctx: Context): Type = semiErasureFn(tp)(erasureCtx)
+ def erasure(tp: Type, semiEraseVCs: Boolean = true)(implicit ctx: Context): Type =
+ erasureFn(isJava = false, semiEraseVCs, isConstructor = false, wildcardOK = false)(tp)(erasureCtx)
+
def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): TypeName = {
val seqClass = if (isJava) defn.ArrayClass else defn.SeqClass
val normTp =
if (tp.isRepeatedParam) tp.translateParameterized(defn.RepeatedParamClass, seqClass)
else tp
- (if (isJava) javaSigFn else scalaSigFn).sigName(normTp)(erasureCtx)
+ val erase = erasureFn(isJava, semiEraseVCs = false, isConstructor = false, wildcardOK = true)
+ erase.sigName(normTp)(erasureCtx)
}
/** The erasure of a top-level reference. Differs from normal erasure in that
@@ -117,29 +137,20 @@ object TypeErasure {
erasure(tp)
}
- /** The erasure of a symbol's info. This is different of `erasure` in the way `ExprType`s are
- * treated. `eraseInfo` maps them them to nullary method types, whereas `erasure` maps them
- * to `Function0`.
- */
- def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type =
- scalaErasureFn.eraseInfo(tp, sym)(erasureCtx)
-
- /** The erasure of a function result type. Differs from normal erasure in that
- * Unit is kept instead of being mapped to BoxedUnit.
- */
- def eraseResult(tp: Type)(implicit ctx: Context): Type =
- scalaErasureFn.eraseResult(tp)(erasureCtx)
-
/** The symbol's erased info. This is the type's erasure, except for the following symbols:
*
* - For $asInstanceOf : [T]T
* - For $isInstanceOf : [T]Boolean
* - For all abstract types : = ?
+ * - For COMPANION_CLASS_METHOD : the erasure of their type with semiEraseVCs = false,
+ * this is needed to keep [[SymDenotation#companionClass]]
+ * working after erasure for value classes.
* - For all other symbols : the semi-erasure of their types, with
* isJava, isConstructor set according to symbol.
*/
def transformInfo(sym: Symbol, tp: Type)(implicit ctx: Context): Type = {
- val erase = erasureFn(sym is JavaDefined, isSemi = true, sym.isConstructor, wildcardOK = false)
+ val semiEraseVCs = sym.name ne nme.COMPANION_CLASS_METHOD
+ val erase = erasureFn(sym is JavaDefined, semiEraseVCs, sym.isConstructor, wildcardOK = false)
def eraseParamBounds(tp: PolyType): Type =
tp.derivedPolyType(
@@ -148,7 +159,7 @@ object TypeErasure {
if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType])
else if (sym.isAbstractType) TypeAlias(WildcardType)
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx))
- else eraseInfo(tp, sym)(erasureCtx) match {
+ else erase.eraseInfo(tp, sym)(erasureCtx) match {
case einfo: MethodType if sym.isGetter && einfo.resultType.isRef(defn.UnitClass) =>
defn.BoxedUnitClass.typeRef
case einfo =>
@@ -241,12 +252,15 @@ object TypeErasure {
import TypeErasure._
/**
- * This is used as the Scala erasure during the erasure phase itself
- * It differs from normal erasure in that value classes are erased to ErasedValueTypes which
- * are then later converted to the underlying parameter type in phase posterasure.
- *
+ * @param isJava Arguments should be treated the way Java does it
+ * @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType
+ * (they will be fully erased in [[ElimErasedValueType]]).
+ * If false, they are erased like normal classes.
+ * @param isConstructor Argument forms part of the type of a constructor
+ * @param wildcardOK Wildcards are acceptable (true when using the erasure
+ * for computing a signature name).
*/
-class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) extends DotClass {
+class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) extends DotClass {
/** The erasure |T| of a type T. This is:
*
@@ -279,10 +293,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
* - For any other type, exception.
*/
private def apply(tp: Type)(implicit ctx: Context): Type = tp match {
+ case _: ErasedValueType =>
+ tp
case tp: TypeRef =>
val sym = tp.symbol
if (!sym.isClass) this(tp.info)
- else if (sym.isDerivedValueClass) eraseDerivedValueClassRef(tp)
+ else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
else eraseNormalClassRef(tp)
case tp: RefinedType =>
val parent = tp.parent
@@ -291,7 +307,9 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
case tp: TermRef =>
this(tp.widen)
case tp: ThisType =>
- this(tp.cls.typeRef)
+ def thisTypeErasure(tpToErase: Type) =
+ erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
+ thisTypeErasure(tp.cls.typeRef)
case SuperType(thistpe, supertpe) =>
SuperType(this(thistpe), this(supertpe))
case ExprType(rt) =>
@@ -303,7 +321,8 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
case OrType(tp1, tp2) =>
ctx.typeComparer.orType(this(tp1), this(tp2), erased = true)
case tp: MethodType =>
- val paramErasure = erasureFn(tp.isJava, isSemi, isConstructor, wildcardOK)(_)
+ def paramErasure(tpToErase: Type) =
+ erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
val formals = tp.paramTypes.mapConserve(paramErasure)
eraseResult(tp.resultType) match {
case rt: MethodType =>
@@ -341,11 +360,17 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
private def eraseArray(tp: RefinedType)(implicit ctx: Context) = {
val defn.ArrayType(elemtp) = tp
+ def arrayErasure(tpToErase: Type) =
+ erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType)
else if (isUnboundedGeneric(elemtp)) defn.ObjectType
- else JavaArrayType(this(elemtp))
+ else JavaArrayType(arrayErasure(elemtp))
}
+ /** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s are
+ * treated. `eraseInfo` maps them them to nullary method types, whereas `apply` maps them
+ * to `Function0`.
+ */
def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match {
case ExprType(rt) =>
if (sym is Param) apply(tp)
@@ -354,22 +379,30 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
// forwarders to mixin methods.
// See doc comment for ElimByName for speculation how we could improve this.
else MethodType(Nil, Nil, eraseResult(rt))
- case tp => erasure(tp)
+ case tp => this(tp)
+ }
+
+ private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
+ val cls = tref.symbol.asClass
+ val underlying = underlyingOfValueClass(cls)
+ ErasedValueType(cls, erasure(underlying))
}
- private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type =
- unsupported("eraseDerivedValueClass")
private def eraseNormalClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
val cls = tref.symbol.asClass
(if (cls.owner is Package) normalizeClass(cls) else cls).typeRef
}
+ /** The erasure of a function result type. */
private def eraseResult(tp: Type)(implicit ctx: Context): Type = tp match {
case tp: TypeRef =>
val sym = tp.typeSymbol
if (sym eq defn.UnitClass) sym.typeRef
- else if (sym.isDerivedValueClass) eraseNormalClassRef(tp)
+ // For a value class V, "new V(x)" should have type V for type adaptation to work
+ // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the return type of a
+ // constructor method should not be semi-erased.
+ else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
else this(tp)
case RefinedType(parent, _) if !(parent isRef defn.ArrayClass) =>
eraseResult(parent)
@@ -391,10 +424,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
* Need to ensure correspondence with erasure!
*/
private def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match {
+ case ErasedValueType(_, underlying) =>
+ sigName(underlying)
case tp: TypeRef =>
val sym = tp.symbol
if (!sym.isClass) sigName(tp.info)
- else if (sym.isDerivedValueClass) sigName(eraseDerivedValueClassRef(tp))
+ else if (isDerivedValueClass(sym)) sigName(eraseDerivedValueClassRef(tp))
else normalizeClass(sym.asClass).fullName.asTypeName
case defn.ArrayType(elem) =>
sigName(this(tp))
diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala
index 64c818964..11d451255 100644
--- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala
+++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala
@@ -3,6 +3,7 @@ package printing
import core._
import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._
+import TypeErasure.ErasedValueType
import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation
import StdNames.nme
import ast.{Trees, untpd, tpd}
@@ -132,6 +133,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
return toText(tp.info)
case ExprType(result) =>
return "=> " ~ toText(result)
+ case ErasedValueType(clazz, underlying) =>
+ return "ErasedValueType(" ~ toText(clazz.typeRef) ~ ", " ~ toText(underlying) ~ ")"
case tp: ClassInfo =>
return toTextParents(tp.instantiatedParents) ~ "{...}"
case JavaArrayType(elemtp) =>
diff --git a/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala
new file mode 100644
index 000000000..8a18c9c17
--- /dev/null
+++ b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala
@@ -0,0 +1,82 @@
+package dotty.tools.dotc
+package transform
+
+import ast.{Trees, tpd}
+import core._, core.Decorators._
+import TreeTransforms._, Phases.Phase
+import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._
+import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._
+import TypeErasure.ErasedValueType, ValueClasses._
+
+/** This phase erases ErasedValueType to their underlying type.
+ * It also removes the synthetic cast methods u2evt$ and evt2u$ which are
+ * no longer needed afterwards.
+ */
+class ElimErasedValueType extends MiniPhaseTransform with InfoTransformer {
+
+ import tpd._
+
+ override def phaseName: String = "elimErasedValueType"
+
+ override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure])
+
+ def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = sym match {
+ case sym: ClassSymbol if sym is ModuleClass =>
+ sym.companionClass match {
+ case origClass: ClassSymbol if isDerivedValueClass(origClass) =>
+ val cinfo = tp.asInstanceOf[ClassInfo]
+ val decls1 = cinfo.decls.cloneScope
+ ctx.atPhase(this.next) { implicit ctx =>
+ // Remove synthetic cast methods introduced by ExtensionMethods,
+ // they are no longer needed after this phase.
+ decls1.unlink(cinfo.decl(nme.U2EVT).symbol)
+ decls1.unlink(cinfo.decl(nme.EVT2U).symbol)
+ }
+ cinfo.derivedClassInfo(decls = decls1)
+ case _ =>
+ tp
+ }
+ case _ =>
+ elimEVT(tp)
+ }
+
+ def elimEVT(tp: Type)(implicit ctx: Context): Type = tp match {
+ case ErasedValueType(_, underlying) =>
+ elimEVT(underlying)
+ case tp: MethodType =>
+ val paramTypes = tp.paramTypes.mapConserve(elimEVT)
+ val retType = elimEVT(tp.resultType)
+ tp.derivedMethodType(tp.paramNames, paramTypes, retType)
+ case _ =>
+ tp
+ }
+
+ def transformTypeOfTree(tree: Tree)(implicit ctx: Context): Tree =
+ tree.withType(elimEVT(tree.tpe))
+
+ override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
+ val Apply(fun, args) = tree
+ val name = fun.symbol.name
+
+ // The casts to and from ErasedValueType are no longer needed once ErasedValueType
+ // has been eliminated.
+ val t =
+ if ((name eq nme.U2EVT) || (name eq nme.EVT2U))
+ args.head
+ else
+ tree
+ transformTypeOfTree(t)
+ }
+
+ // FIXME: transformIf and transformBlock won't be required anymore once #444 is fixed.
+ override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree =
+ transformTypeOfTree(tree)
+ override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree =
+ transformTypeOfTree(tree)
+ override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree =
+ transformTypeOfTree(tree)
+ override def transformIf(tree: If)(implicit ctx: Context, info: TransformerInfo): Tree =
+ transformTypeOfTree(tree)
+ override def transformTypeTree(tree: TypeTree)(implicit ctx: Context, info: TransformerInfo): Tree =
+ transformTypeOfTree(tree)
+}
diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala
index 51a06f9ff..996c480ce 100644
--- a/src/dotty/tools/dotc/transform/Erasure.scala
+++ b/src/dotty/tools/dotc/transform/Erasure.scala
@@ -62,7 +62,7 @@ class Erasure extends Phase with DenotTransformer { thisTransformer =>
}
}
case ref =>
- ref.derivedSingleDenotation(ref.symbol, eraseInfo(ref.info, ref.symbol))
+ ref.derivedSingleDenotation(ref.symbol, transformInfo(ref.symbol, ref.info))
}
val eraser = new Erasure.Typer
@@ -170,15 +170,29 @@ object Erasure extends TypeTestsCasts{
def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") {
pt match {
case ErasedValueType(clazz, underlying) =>
+ def unboxedTree(t: Tree) =
+ adaptToType(t, clazz.typeRef)
+ .select(valueClassUnbox(clazz))
+ .appliedToNone
+
+ // Null unboxing needs to be treated separately since we cannot call a method on null.
+ // "Unboxing" null to underlying is equivalent to doing null.asInstanceOf[underlying]
+ // See tests/pos/valueclasses/nullAsInstanceOfVC.scala for cases where this might happen.
val tree1 =
- if ((tree.tpe isRef defn.NullClass) && underlying.isPrimitiveValueType)
- // convert `null` directly to underlying type, as going
- // via the unboxed type would yield a NPE (see SI-5866)
- unbox(tree, underlying)
- else
- adaptToType(tree, clazz.typeRef)
- .select(valueClassUnbox(clazz))
- .appliedToNone
+ if (tree.tpe isRef defn.NullClass)
+ adaptToType(tree, underlying)
+ else if (!(tree.tpe <:< clazz.typeRef)) {
+ assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
+ val nullTree = Literal(Constant(null))
+ val unboxedNull = adaptToType(nullTree, underlying)
+
+ evalOnce(tree) { t =>
+ If(t.select(defn.Object_eq).appliedTo(nullTree),
+ unboxedNull,
+ unboxedTree(t))
+ }
+ } else unboxedTree(tree)
+
cast(tree1, pt)
case _ =>
val cls = pt.widen.classSymbol
@@ -192,6 +206,8 @@ object Erasure extends TypeTestsCasts{
/** Generate a synthetic cast operation from tree.tpe to pt.
* Does not do any boxing/unboxing (this is handled upstream).
+ * Casts from and to ErasedValueType are special, see the explanation
+ * in ExtensionMethods#transform.
*/
def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree = {
// TODO: The commented out assertion fails for tailcall/t6574.scala
@@ -203,9 +219,18 @@ object Erasure extends TypeTestsCasts{
if treeElem.widen.isPrimitiveValueType && !ptElem.isPrimitiveValueType =>
// See SI-2386 for one example of when this might be necessary.
cast(ref(defn.runtimeMethod(nme.toObjectArray)).appliedTo(tree), pt)
+ case (_, ErasedValueType(cls, _)) =>
+ ref(u2evt(cls)).appliedTo(tree)
case _ =>
- if (pt.isPrimitiveValueType) primitiveConversion(tree, pt.classSymbol)
- else tree.asInstance(pt)
+ tree.tpe.widen match {
+ case ErasedValueType(cls, _) =>
+ ref(evt2u(cls)).appliedTo(tree)
+ case _ =>
+ if (pt.isPrimitiveValueType)
+ primitiveConversion(tree, pt.classSymbol)
+ else
+ tree.asInstance(pt)
+ }
}
}
@@ -243,18 +268,39 @@ object Erasure extends TypeTestsCasts{
class Typer extends typer.ReTyper with NoChecking {
import Boxing._
- def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = tree.typeOpt match {
- case tp: TermRef if tree.isTerm => erasedRef(tp)
- case tp => erasure(tp)
- }
+ def erasedType(tree: untpd.Tree, semiEraseVCs: Boolean = true)(implicit ctx: Context): Type =
+ tree.typeOpt match {
+ case tp: TermRef if tree.isTerm => erasedRef(tp)
+ case tp => erasure(tp, semiEraseVCs)
+ }
- override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
+ def promote(tree: untpd.Tree, semiEraseVCs: Boolean)(implicit ctx: Context): tree.ThisTree[Type] = {
assert(tree.hasType)
- val erased = erasedType(tree)
+ val erased = erasedType(tree, semiEraseVCs)
ctx.log(s"promoting ${tree.show}: ${erased.showWithUnderlying()}")
tree.withType(erased)
}
+ override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
+ promote(tree, true)
+ }
+
+ /** When erasing most TypeTrees we should not semi-erase value types.
+ * This is not the case for [[DefDef#tpt]], [[ValDef#tpt]] and [[Typed#tpt]], they
+ * are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]].
+ */
+ override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = {
+ promote(tree, semiEraseVCs = false)
+ }
+
+ /** This override is only needed to semi-erase type ascriptions */
+ override def typedTyped(tree: untpd.Typed, pt: Type)(implicit ctx: Context): Tree = {
+ val Typed(expr, tpt) = tree
+ val tpt1 = promote(tpt)
+ val expr1 = typed(expr, tpt1.tpe)
+ assignType(untpd.cpy.Typed(tree)(expr1, tpt1), tpt1)
+ }
+
override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Literal =
if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt)
else super.typedLiteral(tree)
@@ -319,7 +365,7 @@ object Erasure extends TypeTestsCasts{
assert(sym.isConstructor, s"${sym.showLocated}")
select(qual, defn.ObjectClass.info.decl(sym.name).symbol)
}
- else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.isErasedValueType)
+ else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType)
recur(box(qual))
else if (!qualIsPrimitive && symIsPrimitive)
recur(unbox(qual, sym.owner.typeRef))
@@ -338,7 +384,7 @@ object Erasure extends TypeTestsCasts{
}
override def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context) =
- untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree))
+ untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree, semiEraseVCs = false))
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
if (tree.symbol == ctx.owner.enclosingClass || tree.symbol.isStaticOwner) promote(tree)
@@ -435,6 +481,58 @@ object Erasure extends TypeTestsCasts{
super.typedDefDef(ddef1, sym)
}
+ /** After erasure, we may have to replace the closure method by a bridge.
+ * LambdaMetaFactory handles this automatically for most types, but we have
+ * to deal with boxing and unboxing of value classes ourselves.
+ */
+ override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = {
+ val implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt)
+ implClosure.tpe match {
+ case SAMType(sam) =>
+ val implType = meth.tpe.widen
+
+ val List(implParamTypes) = implType.paramTypess
+ val List(samParamTypes) = sam.info.paramTypess
+ val implResultType = implType.resultType
+ val samResultType = sam.info.resultType
+
+ // Given a value class V with an underlying type U, the following code:
+ // val f: Function1[V, V] = x => ...
+ // results in the creation of a closure and a method:
+ // def $anonfun(v1: V): V = ...
+ // val f: Function1[V, V] = closure($anonfun)
+ // After [[Erasure]] this method will look like:
+ // def $anonfun(v1: ErasedValueType(V, U)): ErasedValueType(V, U) = ...
+ // And after [[ElimErasedValueType]] it will look like:
+ // def $anonfun(v1: U): U = ...
+ // This method does not implement the SAM of Function1[V, V] anymore and
+ // needs to be replaced by a bridge:
+ // def $anonfun$2(v1: V): V = new V($anonfun(v1.underlying))
+ // val f: Function1 = closure($anonfun$2)
+ // In general, a bridge is needed when the signature of the closure method after
+ // Erasure contains an ErasedValueType but the corresponding type in the functional
+ // interface is not an ErasedValueType.
+ val bridgeNeeded =
+ (implResultType :: implParamTypes, samResultType :: samParamTypes).zipped.forall(
+ (implType, samType) => implType.isErasedValueType && !samType.isErasedValueType
+ )
+
+ if (bridgeNeeded) {
+ val bridge = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Flags.Synthetic | Flags.Method, sam.info)
+ val bridgeCtx = ctx.withOwner(bridge)
+ Closure(bridge, bridgeParamss => {
+ implicit val ctx: Context = bridgeCtx
+
+ val List(bridgeParams) = bridgeParamss
+ val rhs = Apply(meth, (bridgeParams, implParamTypes).zipped.map(adapt(_, _)))
+ adapt(rhs, sam.info.resultType)
+ })
+ } else implClosure
+ case _ =>
+ implClosure
+ }
+ }
+
override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context) =
EmptyTree
diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala
index ae22adc39..b2f402bc5 100644
--- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala
+++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala
@@ -14,17 +14,29 @@ import core._
import Phases.Phase
import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._
import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._
+import TypeErasure.{ erasure, ErasedValueType }
import TypeUtils._
import util.Positions._
import Decorators._
+import SymUtils._
/**
* Perform Step 1 in the inline classes SIP: Creates extension methods for all
* methods in a value class, except parameter or super accessors, or constructors.
+ *
+ * Additionally, for a value class V, let U be the underlying type after erasure. We add
+ * to the companion module of V two cast methods:
+ * def u2evt$(x0: U): ErasedValueType(V, U)
+ * def evt2u$(x0: ErasedValueType(V, U)): U
+ * The casts are used in [[Erasure]] to make it typecheck, they are then removed
+ * in [[ElimErasedValueType]].
+ * This is different from the implementation of value classes in Scala 2
+ * (see SIP-15) which uses `asInstanceOf` which does not typecheck.
*/
class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with FullParameterization { thisTransformer =>
import tpd._
+ import ExtensionMethods._
/** the following two members override abstract members in Transform */
override def phaseName: String = "extmethods"
@@ -37,13 +49,27 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful
case ref: ClassDenotation if ref is ModuleClass =>
ref.linkedClass match {
case origClass: ClassSymbol if isDerivedValueClass(origClass) =>
- val cinfo = ref.classInfo // ./tests/pos/t2667.scala dies here for module class AnyVal$
+ val cinfo = ref.classInfo
val decls1 = cinfo.decls.cloneScope
ctx.atPhase(thisTransformer.next) { implicit ctx =>
- for (decl <- origClass.classInfo.decls) {
- if (isMethodWithExtension(decl))
- decls1.enter(createExtensionMethod(decl, ref.symbol))
+ // In Scala 2, extension methods are added before pickling so we should
+ // not generate them again.
+ if (!(origClass is Scala2x)) {
+ for (decl <- origClass.classInfo.decls) {
+ if (isMethodWithExtension(decl))
+ decls1.enter(createExtensionMethod(decl, ref.symbol))
+ }
}
+
+ val sym = ref.symbol
+ val underlying = erasure(underlyingOfValueClass(origClass))
+ val evt = ErasedValueType(origClass, underlying)
+ val u2evtSym = ctx.newSymbol(sym, nme.U2EVT, Synthetic | Method,
+ MethodType(List(nme.x_0), List(underlying), evt))
+ val evt2uSym = ctx.newSymbol(sym, nme.EVT2U, Synthetic | Method,
+ MethodType(List(nme.x_0), List(evt), underlying))
+ decls1.enter(u2evtSym)
+ decls1.enter(evt2uSym)
}
if (decls1.isEmpty) ref
else ref.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1))
@@ -64,65 +90,6 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful
target.owner.linkedClass == derived.owner) extensionMethod(target)
else NoSymbol
- /** Generate stream of possible names for the extension version of given instance method `imeth`.
- * If the method is not overloaded, this stream consists of just "imeth$extension".
- * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the
- * index of imeth in the sequence of overloaded alternatives with the same name. This choice will
- * always be picked as the name of the generated extension method.
- * After this first choice, all other possible indices in the range of 0 until the number
- * of overloaded alternatives are returned. The secondary choices are used to find a matching method
- * in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity
- * of how overloaded types are ordered between phases and picklings.
- */
- private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = {
- val decl = imeth.owner.info.decl(imeth.name)
-
- /** No longer needed for Dotty, as we are more disciplined with scopes now.
- // Bridge generation is done at phase `erasure`, but new scopes are only generated
- // for the phase after that. So bridges are visible in earlier phases.
- //
- // `info.member(imeth.name)` filters these out, but we need to use `decl`
- // to restrict ourselves to members defined in the current class, so we
- // must do the filtering here.
- val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe
- */
- decl match {
- case decl: MultiDenotation =>
- val alts = decl.alternatives
- val index = alts indexOf imeth.denot
- assert(index >= 0, alts + " does not contain " + imeth)
- def altName(index: Int) = (imeth.name + "$extension" + index).toTermName
- altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName)
- case decl =>
- assert(decl.exists, imeth.name + " not found in " + imeth.owner + "'s decls: " + imeth.owner.info.decls)
- Stream((imeth.name + "$extension").toTermName)
- }
- }
-
- /** Return the extension method that corresponds to given instance method `meth`. */
- def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol =
- ctx.atPhase(thisTransformer.next) { implicit ctx =>
- // FIXME use toStatic instead?
- val companionInfo = imeth.owner.companionModule.info
- val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists)
- val matching = candidates filter (c => memberSignature(c.info) == imeth.signature)
- assert(matching.nonEmpty,
- sm"""|no extension method found for:
- |
- | $imeth:${imeth.info.show} with signature ${imeth.signature}
- |
- | Candidates:
- |
- | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")}
- |
- | Candidates (signatures normalized):
- |
- | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + memberSignature(c.info)).mkString("\n")}
- |
- | Eligible Names: ${extensionNames(imeth).mkString(",")}""")
- matching.head.asTerm
- }
-
private def createExtensionMethod(imeth: Symbol, staticClass: Symbol)(implicit ctx: Context): TermSymbol = {
assert(ctx.phase == thisTransformer.next)
val extensionName = extensionNames(imeth).head.toTermName
@@ -184,3 +151,64 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful
} else tree
}
}
+
+object ExtensionMethods {
+ /** Generate stream of possible names for the extension version of given instance method `imeth`.
+ * If the method is not overloaded, this stream consists of just "imeth$extension".
+ * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the
+ * index of imeth in the sequence of overloaded alternatives with the same name. This choice will
+ * always be picked as the name of the generated extension method.
+ * After this first choice, all other possible indices in the range of 0 until the number
+ * of overloaded alternatives are returned. The secondary choices are used to find a matching method
+ * in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity
+ * of how overloaded types are ordered between phases and picklings.
+ */
+ private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = {
+ val decl = imeth.owner.info.decl(imeth.name)
+
+ /** No longer needed for Dotty, as we are more disciplined with scopes now.
+ // Bridge generation is done at phase `erasure`, but new scopes are only generated
+ // for the phase after that. So bridges are visible in earlier phases.
+ //
+ // `info.member(imeth.name)` filters these out, but we need to use `decl`
+ // to restrict ourselves to members defined in the current class, so we
+ // must do the filtering here.
+ val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe
+ */
+ decl match {
+ case decl: MultiDenotation =>
+ val alts = decl.alternatives
+ val index = alts indexOf imeth.denot
+ assert(index >= 0, alts + " does not contain " + imeth)
+ def altName(index: Int) = (imeth.name + "$extension" + index).toTermName
+ altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName)
+ case decl =>
+ assert(decl.exists, imeth.name + " not found in " + imeth.owner + "'s decls: " + imeth.owner.info.decls)
+ Stream((imeth.name + "$extension").toTermName)
+ }
+ }
+
+ /** Return the extension method that corresponds to given instance method `meth`. */
+ def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol =
+ ctx.atPhase(ctx.extensionMethodsPhase.next) { implicit ctx =>
+ // FIXME use toStatic instead?
+ val companionInfo = imeth.owner.companionModule.info
+ val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists)
+ val matching = candidates filter (c => FullParameterization.memberSignature(c.info) == imeth.signature)
+ assert(matching.nonEmpty,
+ sm"""|no extension method found for:
+ |
+ | $imeth:${imeth.info.show} with signature ${imeth.signature}
+ |
+ | Candidates:
+ |
+ | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")}
+ |
+ | Candidates (signatures normalized):
+ |
+ | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + FullParameterization.memberSignature(c.info)).mkString("\n")}
+ |
+ | Eligible Names: ${extensionNames(imeth).mkString(",")}""")
+ matching.head.asTerm
+ }
+}
diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala
index f46942fb3..d402c2e7f 100644
--- a/src/dotty/tools/dotc/transform/FullParameterization.scala
+++ b/src/dotty/tools/dotc/transform/FullParameterization.scala
@@ -52,6 +52,7 @@ import ast.Trees._
trait FullParameterization {
import tpd._
+ import FullParameterization._
/** If references to original symbol `referenced` from within fully parameterized method
* `derived` should be rewired to some fully parameterized method, the rewiring target symbol,
@@ -124,15 +125,6 @@ trait 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
- }
-
/** The type parameters (skolems) of the method definition `originalDef`,
* followed by the class parameters of its enclosing class.
*/
@@ -230,3 +222,14 @@ trait FullParameterization {
.appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol)))
.withPos(originalDef.rhs.pos)
}
+
+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
+ }
+}
diff --git a/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/src/dotty/tools/dotc/transform/InterceptedMethods.scala
index 725910949..ff354a54c 100644
--- a/src/dotty/tools/dotc/transform/InterceptedMethods.scala
+++ b/src/dotty/tools/dotc/transform/InterceptedMethods.scala
@@ -27,6 +27,7 @@ import scala.collection.mutable.ListBuffer
import dotty.tools.dotc.core.Denotations.SingleDenotation
import dotty.tools.dotc.core.SymDenotations.SymDenotation
import StdNames._
+import Phases.Phase
/** Replace member references as follows:
*
diff --git a/src/dotty/tools/dotc/transform/TreeExtractors.scala b/src/dotty/tools/dotc/transform/TreeExtractors.scala
new file mode 100644
index 000000000..7a5c5df9d
--- /dev/null
+++ b/src/dotty/tools/dotc/transform/TreeExtractors.scala
@@ -0,0 +1,48 @@
+package dotty.tools.dotc
+package transform
+
+import ast.{Trees, tpd}
+import core._, core.Decorators._
+import Contexts._, Flags._, Trees._, Types._, StdNames._, Symbols._
+import ValueClasses._
+
+object TreeExtractors {
+ import tpd._
+
+ /** Match arg1.op(arg2) and extract (arg1, op.symbol, arg2) */
+ object BinaryOp {
+ def unapply(t: Tree)(implicit ctx: Context): Option[(Tree, Symbol, Tree)] = t match {
+ case Apply(sel @ Select(arg1, _), List(arg2)) =>
+ Some((arg1, sel.symbol, arg2))
+ case _ =>
+ None
+ }
+ }
+
+ /** Match new C(args) and extract (C, args) */
+ object NewWithArgs {
+ def unapply(t: Tree)(implicit ctx: Context): Option[(Type, List[Tree])] = t match {
+ case Apply(Select(New(_), nme.CONSTRUCTOR), args) =>
+ Some((t.tpe, args))
+ case _ =>
+ None
+ }
+ }
+
+ /** For an instance v of a value class like:
+ * class V(val underlying: X) extends AnyVal
+ * Match v.underlying() and extract v
+ */
+ object ValueClassUnbox {
+ def unapply(t: Tree)(implicit ctx: Context): Option[Tree] = t match {
+ case Apply(sel @ Select(ref, _), Nil) =>
+ val d = ref.tpe.widenDealias.typeSymbol.denot
+ if (isDerivedValueClass(d) && (sel.symbol eq valueClassUnbox(d.asClass))) {
+ Some(ref)
+ } else
+ None
+ case _ =>
+ None
+ }
+ }
+}
diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala
index 9d827d3e0..d7fa9feaf 100644
--- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala
+++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala
@@ -14,6 +14,7 @@ import typer.ErrorReporting._
import ast.Trees._
import Erasure.Boxing._
import core.TypeErasure._
+import ValueClasses._
/** This transform normalizes type tests and type casts,
* also replacing type tests with singleton argument type with reference equality check
@@ -90,10 +91,12 @@ trait TypeTestsCasts {
}
else if (argCls.isPrimitiveValueClass)
unbox(qual.ensureConforms(defn.ObjectType), argType)
- else
+ else if (isDerivedValueClass(argCls)) {
+ qual // adaptToType in Erasure will do the necessary type adaptation
+ } else
derivedTree(qual, defn.Any_asInstanceOf, argType)
}
- def erasedArg = erasure(tree.args.head.tpe)
+ def erasedArg = erasure(tree.args.head.tpe, semiEraseVCs = false)
if (sym eq defn.Any_isInstanceOf)
transformIsInstanceOf(qual, erasedArg)
else if (sym eq defn.Any_asInstanceOf)
diff --git a/src/dotty/tools/dotc/transform/VCInline.scala b/src/dotty/tools/dotc/transform/VCInline.scala
new file mode 100644
index 000000000..e7b16f59e
--- /dev/null
+++ b/src/dotty/tools/dotc/transform/VCInline.scala
@@ -0,0 +1,59 @@
+package dotty.tools.dotc
+package transform
+
+import ast.{Trees, tpd}
+import core._, core.Decorators._
+import Contexts._, Trees._, StdNames._, Symbols._
+import DenotTransformers._, TreeTransforms._, Phases.Phase
+import ExtensionMethods._, TreeExtractors._, ValueClasses._
+
+/** This phase inlines calls to methods and fields of value classes.
+ *
+ * For a value class V defined as:
+ * case class V(val underlying: U) extends AnyVal
+ * We replace method calls by calls to the corresponding extension method:
+ * v.foo(args) => V.foo$extension(v.underlying(), args)
+ * And we avoid unnecessary allocations:
+ * new V(u1) == new V(u2) => u1 == u2
+ * (new V(u)).underlying() => u
+ */
+class VCInline extends MiniPhaseTransform with IdentityDenotTransformer {
+ import tpd._
+
+ override def phaseName: String = "vcInline"
+
+ override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimErasedValueType])
+
+ override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree =
+ tree match {
+ // new V(u1) == new V(u2) => u1 == u2
+ // (We don't handle != because it has been eliminated by InterceptedMethods)
+ case BinaryOp(NewWithArgs(tp1, List(u1)), op, NewWithArgs(tp2, List(u2)))
+ if (tp1 eq tp2) && (op eq defn.Any_==) && isDerivedValueClass(tp1.typeSymbol) =>
+ // == is overloaded in primitive classes
+ applyOverloaded(u1, nme.EQ, List(u2), Nil, defn.BooleanType)
+
+ // (new V(u)).underlying() => u
+ case ValueClassUnbox(NewWithArgs(_, List(u))) =>
+ u
+
+ // (new V(u)).foo(args) => V.foo$extension(u, args)
+ // v.foo(args) => V.foo$extension(v.underlying(), args)
+ case Apply(sel @ Select(receiver, _), args) =>
+ val classMeth = sel.symbol
+ if (isMethodWithExtension(classMeth)) {
+ val classSym = receiver.tpe.widenDealias.typeSymbol.asClass
+ val unboxedReceiver = receiver match {
+ case NewWithArgs(_, List(u)) =>
+ u
+ case _ =>
+ receiver.select(valueClassUnbox(classSym)).appliedToNone
+ }
+ val extensionMeth = extensionMethod(classMeth)
+ ref(extensionMeth).appliedToArgs(unboxedReceiver :: args)
+ } else tree
+
+ case _ =>
+ tree
+ }
+}
diff --git a/src/dotty/tools/dotc/transform/ValueClasses.scala b/src/dotty/tools/dotc/transform/ValueClasses.scala
index a7a0db97c..8969b9321 100644
--- a/src/dotty/tools/dotc/transform/ValueClasses.scala
+++ b/src/dotty/tools/dotc/transform/ValueClasses.scala
@@ -13,6 +13,7 @@ import StdNames._
object ValueClasses {
def isDerivedValueClass(d: SymDenotation)(implicit ctx: Context) = {
+ !d.isRefinementClass &&
d.isValueClass &&
(d.initial.symbol ne defn.AnyValClass) && // Compare the initial symbol because AnyVal does not exist after erasure
!d.isPrimitiveValueClass
@@ -34,6 +35,20 @@ object ValueClasses {
.map(_.symbol)
.getOrElse(NoSymbol)
+ /** For a value class `d`, this returns the synthetic cast from the underlying type to
+ * ErasedValueType defined in the companion module. This method is added to the module
+ * and further described in [[ExtensionMethods]].
+ */
+ def u2evt(d: ClassDenotation)(implicit ctx: Context): Symbol =
+ d.linkedClass.info.decl(nme.U2EVT).symbol
+
+ /** For a value class `d`, this returns the synthetic cast from ErasedValueType to the
+ * underlying type defined in the companion module. This method is added to the module
+ * and further described in [[ExtensionMethods]].
+ */
+ def evt2u(d: ClassDenotation)(implicit ctx: Context): Symbol =
+ d.linkedClass.info.decl(nme.EVT2U).symbol
+
/** The unboxed type that underlies a derived value class */
def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type =
valueClassUnbox(d).info.resultType
diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala
index a2b280c6e..b58f48728 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -595,7 +595,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
}
}
- def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = track("typedClosure") {
+ def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = track("typedClosure") {
val env1 = tree.env mapconserve (typed(_))
val meth1 = typedUnadapted(tree.meth)
val target =