diff options
author | Dmitry Petrashko <dark@d-d.me> | 2015-05-01 14:28:09 +0200 |
---|---|---|
committer | Dmitry Petrashko <dark@d-d.me> | 2015-05-01 14:28:09 +0200 |
commit | 2dbabca38cee0ff27c92a9a7079959b2461c9869 (patch) | |
tree | 3462a1b9ed58a1cca5f56e205c55c970966ad86c /src/dotty | |
parent | 275c340b609ae113c93d8c004a2d8bd7be1b12c1 (diff) | |
parent | d012f93635184dc8aa6325b715a133861c74ab08 (diff) | |
download | dotty-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')
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 = |