From b9a98a61cdbbc4ffe106fa0965b5175dbfc172f2 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:52:45 +0100 Subject: [PATCH 01/18] Remove obsolete comment about t2667 failing It was fixed by #390 and the test was added back in #408. --- src/dotty/tools/dotc/transform/ExtensionMethods.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index ae22adc39d93..787d68430349 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -37,7 +37,7 @@ 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) { From 00b4fceb8e15f816b517226bd4b1c609d03e5aa6 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 31 Mar 2015 23:50:17 +0200 Subject: [PATCH 02/18] Don't consider the temporary refinement classes as derived value classes --- src/dotty/tools/dotc/transform/ValueClasses.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dotty/tools/dotc/transform/ValueClasses.scala b/src/dotty/tools/dotc/transform/ValueClasses.scala index a7a0db97c5d3..9cd0e1ef77f3 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 From 57c893e5ddb2c976ae2e6098b06a3ec854996504 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:54:52 +0100 Subject: [PATCH 03/18] Don't create extension methods for Scala2x value classes Fixes #387 --- src/dotty/tools/dotc/transform/ExtensionMethods.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 787d68430349..e6260bde2a0c 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -36,7 +36,8 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { case ref: ClassDenotation if ref is ModuleClass => ref.linkedClass match { - case origClass: ClassSymbol if isDerivedValueClass(origClass) => + // In Scala 2, extension methods are added before pickling so we should not generate them again + case origClass: ClassSymbol if isDerivedValueClass(origClass) && !(origClass is Scala2x) => val cinfo = ref.classInfo val decls1 = cinfo.decls.cloneScope ctx.atPhase(thisTransformer.next) { implicit ctx => From f2c5b50e75e49c1947378d2e9c29dda559ea5cb9 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:55:04 +0100 Subject: [PATCH 04/18] TypeErasure: simplify and fix bugs This commit tries to disentangle the TypeErasure class and the TypeErasure object thereby fixing #386. - Remove the `eraseInfo` method in the TypeErasure object, use `transformInfo` instead which takes care of using the correct instance of TypeErasure depending on the symbol to erase. - Remove the unused method `eraseResult` in the TypeErasure class. - In `transformInfo`, use the correct instance of the TypeErasure class when calling `eraseInfo`. - In the `eraseInfo` method of the TypeErasure class, do not call the `erasure` method of the TypeErasure object, instead use the `apply` method of the current instance of TypeErasure. --- src/dotty/tools/dotc/core/TypeErasure.scala | 22 +++++++------------- src/dotty/tools/dotc/transform/Erasure.scala | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 20cf816c2539..3afc5b9f9069 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -117,19 +117,6 @@ 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 @@ -148,7 +135,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 => @@ -346,6 +333,10 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild else JavaArrayType(this(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,7 +345,7 @@ 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 = @@ -365,6 +356,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild (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 diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 51a06f9ff37b..65e5081593ff 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 From a1f31a56705243b4a90d512b1bd6083120a776d5 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:55:21 +0100 Subject: [PATCH 05/18] TypeErasure: replace isSemi by semiEraseVCs and simplify the code - isSemi is replaced by semiEraseVCs with a different meaning (but is still unimplemented): * If true, value classes are semi-erased to ErasedValueType (they will be fully erased in ElimErasedValueType which is not yet present in this commit). * If false, they are erased like normal classes. - Fix the documentation of the TypeErasure class which was wrong. - Remove intermediate functions scalaErasureFn, scalaSigFn, javaSigFn and semiErasureFn. It's clearer to just use erasureFn directly instead. - Add an optional parameter semiEraseVCs to TypeErasure#erasure which will be used in Erasure#Typer when we need to disable semi-erasure. --- src/dotty/tools/dotc/core/TypeErasure.scala | 54 +++++++++------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 3afc5b9f9069..6a8d22bf9d5c 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -55,9 +55,9 @@ object TypeErasure { override def computeHash = doHash(cls, underlying) } - private def erasureIdx(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) = + 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 +65,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 @@ -126,7 +117,7 @@ object TypeErasure { * 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 erase = erasureFn(sym is JavaDefined, semiEraseVCs = true, sym.isConstructor, wildcardOK = false) def eraseParamBounds(tp: PolyType): Type = tp.derivedPolyType( @@ -228,12 +219,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: * @@ -290,7 +284,7 @@ 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)(_) + val paramErasure = erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(_) val formals = tp.paramTypes.mapConserve(paramErasure) eraseResult(tp.resultType) match { case rt: MethodType => From 926d48e91fafc54d5f82f892c01a7f95bca3ff61 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 17 Apr 2015 19:51:26 +0200 Subject: [PATCH 06/18] TypeComparer: Add support for ErasedValueType --- src/dotty/tools/dotc/core/TypeComparer.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index a59a64a9127c..18f9f08bbc22 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 _ => From a02ce561ca7078414141dbb326ea235af2e80e4b Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:55:38 +0100 Subject: [PATCH 07/18] Cache the instantiations of ErasedValueType This reduces the number of objects created and speeds up subtyping tests Also make ErasedValueType extend ValueType --- src/dotty/tools/dotc/core/TypeErasure.scala | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 6a8d22bf9d5c..56b50c74a7b6 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -3,6 +3,7 @@ package dotc package core import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._, Flags.JavaDefined +import Uniques.unique import dotc.transform.ExplicitOuter._ import typer.Mode import util.DotClass @@ -51,8 +52,30 @@ 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) + } + + 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) = From f168970f38df1d1ccc2b262f1a77f72cd4ec9f39 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:55:50 +0100 Subject: [PATCH 08/18] RefinedPrinter: Pretty-print ErasedValueType --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 64c8189645db..11d4512552f0 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) => From 391c80c4dfb2489e4098af33265b22332ef3d5f1 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:56:07 +0100 Subject: [PATCH 09/18] Add synthetic casts to and from ErasedValueType For a value class V, let U be the underlying type after erasure. We add to the companion object 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 (not yet present in this commit). This is different from the implementation of value classes in Scala 2 (see SIP-15) which uses `asInstanceOf` which does not typecheck. --- src/dotty/tools/dotc/core/StdNames.scala | 2 ++ src/dotty/tools/dotc/transform/Erasure.scala | 15 ++++++-- .../dotc/transform/ExtensionMethods.scala | 34 ++++++++++++++++--- .../tools/dotc/transform/ValueClasses.scala | 14 ++++++++ 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 829ff8b8fe25..74a121b47124 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 = "" 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 = "" 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/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 65e5081593ff..01f86915d5d8 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -192,6 +192,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 +205,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) + } } } diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index e6260bde2a0c..724f3fc642f5 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -14,13 +14,24 @@ 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 => @@ -36,15 +47,28 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { case ref: ClassDenotation if ref is ModuleClass => ref.linkedClass match { - // In Scala 2, extension methods are added before pickling so we should not generate them again - case origClass: ClassSymbol if isDerivedValueClass(origClass) && !(origClass is Scala2x) => + case origClass: ClassSymbol if isDerivedValueClass(origClass) => 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)) diff --git a/src/dotty/tools/dotc/transform/ValueClasses.scala b/src/dotty/tools/dotc/transform/ValueClasses.scala index 9cd0e1ef77f3..8969b932123f 100644 --- a/src/dotty/tools/dotc/transform/ValueClasses.scala +++ b/src/dotty/tools/dotc/transform/ValueClasses.scala @@ -35,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 From 5dec4ce8a64d44ee602c09d468414b13eecba389 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:56:19 +0100 Subject: [PATCH 10/18] Erasure: properly erase value classes There are three ways to erase a value class: - In most case, it should be semi-erased to an ErasedValueType, which will be fully erased to its underlying type in ElimErasedValueType. This corresponds to semiEraseVCs = true in TypeErasure. - In a few cases, it should be erased like a normal class, so far this seems to be necessary for: * The return type of a constructor * The underlying type of a ThisType * TypeTree nodes inside New nodes * TypeApply nodes * Arrays In these cases, we set semiEraseVCs = false - When calling `sigName` it should be erased to its underlying type. This commit implements all these cases. Note that this breaks most tests because ElimErasedValueType has not been implemented yet, it is part of the next commit. --- .../tools/dotc/core/SymDenotations.scala | 10 ++++- src/dotty/tools/dotc/core/Symbols.scala | 8 ---- src/dotty/tools/dotc/core/TypeErasure.scala | 38 +++++++++++++++---- src/dotty/tools/dotc/transform/Erasure.scala | 37 ++++++++++++++---- .../tools/dotc/transform/TypeTestsCasts.scala | 2 +- 5 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index bcd46810e0eb..14be606a1d73 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 9f18e723c9d8..2b91efbcd83c 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/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 56b50c74a7b6..1ea63465ab69 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -5,11 +5,13 @@ 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 @@ -30,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 => @@ -283,10 +289,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean * - 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 @@ -295,7 +303,9 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean 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) => @@ -307,7 +317,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case OrType(tp1, tp2) => ctx.typeComparer.orType(this(tp1), this(tp2), erased = true) case tp: MethodType => - val paramErasure = erasureFn(tp.isJava, semiEraseVCs, 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 => @@ -345,9 +356,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean 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 @@ -365,8 +378,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case tp => this(tp) } - private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = - unsupported("eraseDerivedValueClass") + private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = { + val cls = tref.symbol.asClass + val underlying = underlyingOfValueClass(cls) + ErasedValueType(cls, erasure(underlying)) + } + private def eraseNormalClassRef(tref: TypeRef)(implicit ctx: Context): Type = { val cls = tref.symbol.asClass @@ -378,7 +395,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean 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) @@ -400,10 +420,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean * 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/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 01f86915d5d8..293059af2fe1 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -254,18 +254,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) @@ -330,7 +351,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)) @@ -349,7 +370,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) diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 9d827d3e05c8..b8ba4427cf73 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -93,7 +93,7 @@ trait TypeTestsCasts { 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) From 9c94605d5464936cc156680c5db5344d5ff092ef Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 18 Mar 2015 23:56:27 +0100 Subject: [PATCH 11/18] New phase: ElimErasedValueType This phase erases ErasedValueType to their underlying type, in scalac this was done in PostErasure. --- src/dotty/tools/dotc/Compiler.scala | 3 +- .../dotc/transform/ElimErasedValueType.scala | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/dotty/tools/dotc/transform/ElimErasedValueType.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 102d99347c92..dc92187db274 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -56,7 +56,8 @@ class Compiler { new ElimByName, new ResolveSuper), List(new Erasure), - List(new Mixin, + List(new ElimErasedValueType, + 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/transform/ElimErasedValueType.scala b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala new file mode 100644 index 000000000000..8a18c9c177bc --- /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) +} From 8bd4139db4dd89e83b71a49b39c0747b9f5fc68a Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 26 Mar 2015 19:35:23 +0100 Subject: [PATCH 12/18] Erasure: properly handle null in value classes This fixes the issues reported in SI-5866 and SI-8097 --- src/dotty/tools/dotc/transform/Erasure.scala | 30 ++++++++++++++----- .../tools/dotc/transform/TypeTestsCasts.scala | 5 +++- test/dotc/tests.scala | 1 + .../pos/valueclasses/nullAsInstanceOfVC.scala | 27 +++++++++++++++++ 4 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 tests/pos/valueclasses/nullAsInstanceOfVC.scala diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 293059af2fe1..7d74d1616db5 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -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 diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index b8ba4427cf73..d7fa9feafc2c 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,7 +91,9 @@ 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, semiEraseVCs = false) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index b3f6f0b8a72f..4e2edd4df65a 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -76,6 +76,7 @@ class tests extends CompilerTest { @Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", twice) @Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", twice) @Test def pos_tailcall = compileDir(posDir, "tailcall", twice) + @Test def pos_valueclasses = compileDir(posDir, "valueclasses", twice) @Test def pos_nullarify = compileFile(posDir, "nullarify", args = "-Ycheck:nullarify" :: Nil) @Test def pos_subtyping = compileFile(posDir, "subtyping", twice) @Test def pos_t2613 = compileFile(posSpecialDir, "t2613")(allowDeepSubtypes) diff --git a/tests/pos/valueclasses/nullAsInstanceOfVC.scala b/tests/pos/valueclasses/nullAsInstanceOfVC.scala new file mode 100644 index 000000000000..0c12328834db --- /dev/null +++ b/tests/pos/valueclasses/nullAsInstanceOfVC.scala @@ -0,0 +1,27 @@ +// These issues were originally reported in SI-5866 and SI-8097 +// FIXME: Make this a run test once we have run tests. + +object VCNull { + case class Foo(d: Double) extends AnyVal { + override def toString = s"Foo($d)" + } + case class Bar(s: String) extends AnyVal { + override def toString = s"Bar($s)" + } + + def testDirect(): Unit = { + val fooDirect: Foo = null.asInstanceOf[Foo] + val barDirect: Bar = null.asInstanceOf[Bar] + } + + def testIndirect(): Unit = { + val fooIndirect: Foo = { val n: Any = null; n.asInstanceOf[Foo] } + val barIndirect: Bar = { val n: Any = null; n.asInstanceOf[Bar] } + } + + def nullOf[T]: T = null.asInstanceOf[T] + def testGeneric(): Unit = { + val fooGeneric: Foo = nullOf[Foo] + val barGeneric: Bar = nullOf[Bar] + } +} From 3fca64e2dfd53e376b3a45605100ef6f768b07a4 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 3 Apr 2015 23:20:24 +0200 Subject: [PATCH 13/18] Make ExtensionMethods#extensionMethods an object method This method will be needed to implement VCInline. --- src/dotty/tools/dotc/core/Phases.scala | 2 + .../dotc/transform/ExtensionMethods.scala | 121 +++++++++--------- .../dotc/transform/FullParameterization.scala | 21 +-- 3 files changed, 76 insertions(+), 68 deletions(-) diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 96066db5ee58..406a3457a0eb 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/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 724f3fc642f5..b2f402bc5c05 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -36,6 +36,7 @@ import SymUtils._ 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" @@ -89,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 @@ -209,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 f46942fb325d..d402c2e7fb8d 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 + } +} From 411d5be477cc862b14d8938c591524d8bf37d4cd Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 4 Apr 2015 01:15:16 +0200 Subject: [PATCH 14/18] New phase: VCInline which inlines value classes calls This corresponds roughly to step 2 of SIP-15 and to the peephole optimizations of step 3. The extractors in TreeExtractors are copied or inspired from src/compiler/scala/tools/nsc/ast/TreeInfo.scala in scalac. --- src/dotty/tools/dotc/Compiler.scala | 1 + .../dotc/transform/InterceptedMethods.scala | 1 + .../tools/dotc/transform/TreeExtractors.scala | 48 +++++++++++++++ src/dotty/tools/dotc/transform/VCInline.scala | 59 +++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 src/dotty/tools/dotc/transform/TreeExtractors.scala create mode 100644 src/dotty/tools/dotc/transform/VCInline.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index dc92187db274..2b5748229427 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -57,6 +57,7 @@ class Compiler { new ResolveSuper), List(new Erasure), List(new ElimErasedValueType, + new VCInline, new Mixin, new LazyVals, new Memoize, diff --git a/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/src/dotty/tools/dotc/transform/InterceptedMethods.scala index 72591094998b..ff354a54c6d1 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 000000000000..7a5c5df9db98 --- /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/VCInline.scala b/src/dotty/tools/dotc/transform/VCInline.scala new file mode 100644 index 000000000000..e7b16f59e476 --- /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 + } +} From 06e1905aed315d5199936797c9e9493326b74595 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 4 Apr 2015 01:15:30 +0200 Subject: [PATCH 15/18] Enable untried pos tests related to value classes Each test needs to have its own package because pos_all will try to compile the whole valueclasses directory at once. --- test/dotc/tests.scala | 2 +- tests/pos/valueclasses/nullAsInstanceOfVC.scala | 2 ++ .../pos => pos/valueclasses}/t5853.scala | 2 ++ .../pos => pos/valueclasses}/t6029.scala | 2 ++ .../pos => pos/valueclasses}/t6034.scala | 2 ++ .../pos => pos/valueclasses}/t6215.scala | 2 ++ .../pos => pos/valueclasses}/t6260.scala | 2 ++ .../pos => pos/valueclasses}/t6260b.scala | 2 ++ .../pos => pos/valueclasses}/t6358.scala | 2 ++ .../pos => pos/valueclasses}/t6358_2.scala | 2 ++ .../t6601/PrivateValueClass_1.scala | 2 ++ .../t6601/UsePrivateValueClass_2.scala | 2 ++ .../pos => pos/valueclasses}/t6651.scala | 2 ++ .../pos => pos/valueclasses}/t7818.scala | 2 ++ .../value-class-override-no-spec.flags | 0 .../value-class-override-no-spec.scala | 2 ++ .../value-class-override-spec.scala | 2 ++ .../pos => pos/valueclasses}/xlint1.flags | 0 .../pos => pos/valueclasses}/xlint1.scala | 2 ++ tests/untried/pos/delambdafy_t6260_method.check | 13 ------------- tests/untried/pos/delambdafy_t6260_method.flags | 1 - tests/untried/pos/delambdafy_t6260_method.scala | 17 ----------------- tests/untried/pos/t6260.flags | 1 - 23 files changed, 33 insertions(+), 33 deletions(-) rename tests/{untried/pos => pos/valueclasses}/t5853.scala (98%) rename tests/{untried/pos => pos/valueclasses}/t6029.scala (87%) rename tests/{untried/pos => pos/valueclasses}/t6034.scala (77%) rename tests/{untried/pos => pos/valueclasses}/t6215.scala (85%) rename tests/{untried/pos => pos/valueclasses}/t6260.scala (96%) rename tests/{untried/pos => pos/valueclasses}/t6260b.scala (88%) rename tests/{untried/pos => pos/valueclasses}/t6358.scala (88%) rename tests/{untried/pos => pos/valueclasses}/t6358_2.scala (87%) rename tests/{untried/pos => pos/valueclasses}/t6601/PrivateValueClass_1.scala (74%) rename tests/{untried/pos => pos/valueclasses}/t6601/UsePrivateValueClass_2.scala (97%) rename tests/{untried/pos => pos/valueclasses}/t6651.scala (98%) rename tests/{untried/pos => pos/valueclasses}/t7818.scala (96%) rename tests/{untried/pos => pos/valueclasses}/value-class-override-no-spec.flags (100%) rename tests/{untried/pos => pos/valueclasses}/value-class-override-no-spec.scala (86%) rename tests/{untried/pos => pos/valueclasses}/value-class-override-spec.scala (87%) rename tests/{untried/pos => pos/valueclasses}/xlint1.flags (100%) rename tests/{untried/pos => pos/valueclasses}/xlint1.scala (92%) delete mode 100644 tests/untried/pos/delambdafy_t6260_method.check delete mode 100644 tests/untried/pos/delambdafy_t6260_method.flags delete mode 100644 tests/untried/pos/delambdafy_t6260_method.scala delete mode 100644 tests/untried/pos/t6260.flags diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 4e2edd4df65a..7761589ad24a 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -76,7 +76,7 @@ class tests extends CompilerTest { @Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", twice) @Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", twice) @Test def pos_tailcall = compileDir(posDir, "tailcall", twice) - @Test def pos_valueclasses = compileDir(posDir, "valueclasses", twice) + @Test def pos_valueclasses = compileFiles(posDir + "valueclasses/", twice) @Test def pos_nullarify = compileFile(posDir, "nullarify", args = "-Ycheck:nullarify" :: Nil) @Test def pos_subtyping = compileFile(posDir, "subtyping", twice) @Test def pos_t2613 = compileFile(posSpecialDir, "t2613")(allowDeepSubtypes) diff --git a/tests/pos/valueclasses/nullAsInstanceOfVC.scala b/tests/pos/valueclasses/nullAsInstanceOfVC.scala index 0c12328834db..43af839ec6b0 100644 --- a/tests/pos/valueclasses/nullAsInstanceOfVC.scala +++ b/tests/pos/valueclasses/nullAsInstanceOfVC.scala @@ -1,3 +1,5 @@ +package nullAsInstanceOfVC + // These issues were originally reported in SI-5866 and SI-8097 // FIXME: Make this a run test once we have run tests. diff --git a/tests/untried/pos/t5853.scala b/tests/pos/valueclasses/t5853.scala similarity index 98% rename from tests/untried/pos/t5853.scala rename to tests/pos/valueclasses/t5853.scala index 2ebb6667dc0f..82ac9dd1dd24 100644 --- a/tests/untried/pos/t5853.scala +++ b/tests/pos/valueclasses/t5853.scala @@ -1,3 +1,5 @@ +package t5853 + diff --git a/tests/untried/pos/t6029.scala b/tests/pos/valueclasses/t6029.scala similarity index 87% rename from tests/untried/pos/t6029.scala rename to tests/pos/valueclasses/t6029.scala index 8f1bbb4ebff5..13f8f8830576 100644 --- a/tests/untried/pos/t6029.scala +++ b/tests/pos/valueclasses/t6029.scala @@ -1,3 +1,5 @@ +package t6029 + final case class V[A](x: A) extends AnyVal { def flatMap[B](f: A => V[B]) = if (true) this else f(x) } diff --git a/tests/untried/pos/t6034.scala b/tests/pos/valueclasses/t6034.scala similarity index 77% rename from tests/untried/pos/t6034.scala rename to tests/pos/valueclasses/t6034.scala index 3558d7ff0b5e..8e2fb625cc71 100644 --- a/tests/untried/pos/t6034.scala +++ b/tests/pos/valueclasses/t6034.scala @@ -1 +1,3 @@ +package t6034 + final class OptPlus[+A](val x: A) extends AnyVal { } diff --git a/tests/untried/pos/t6215.scala b/tests/pos/valueclasses/t6215.scala similarity index 85% rename from tests/untried/pos/t6215.scala rename to tests/pos/valueclasses/t6215.scala index 2f66892b690e..579503e6ca04 100644 --- a/tests/untried/pos/t6215.scala +++ b/tests/pos/valueclasses/t6215.scala @@ -1 +1,3 @@ +package t6215 + class Foo(val v: String) extends AnyVal { private def len = v.length ; def f = len } diff --git a/tests/untried/pos/t6260.scala b/tests/pos/valueclasses/t6260.scala similarity index 96% rename from tests/untried/pos/t6260.scala rename to tests/pos/valueclasses/t6260.scala index 8edfe4ac343c..675c3c16afb5 100644 --- a/tests/untried/pos/t6260.scala +++ b/tests/pos/valueclasses/t6260.scala @@ -1,3 +1,5 @@ +package t6260 + class Box[X](val x: X) extends AnyVal { def map[Y](f: X => Y): Box[Y] = ((bx: Box[X]) => new Box(f(bx.x)))(this) diff --git a/tests/untried/pos/t6260b.scala b/tests/pos/valueclasses/t6260b.scala similarity index 88% rename from tests/untried/pos/t6260b.scala rename to tests/pos/valueclasses/t6260b.scala index 73e2e58f736c..fb9a2961b49a 100644 --- a/tests/untried/pos/t6260b.scala +++ b/tests/pos/valueclasses/t6260b.scala @@ -1,3 +1,5 @@ +package t6260b + class X(val value: Object) extends AnyVal { def or(alt: => X): X = this } class Y { def f = new X("") or new X("") } diff --git a/tests/untried/pos/t6358.scala b/tests/pos/valueclasses/t6358.scala similarity index 88% rename from tests/untried/pos/t6358.scala rename to tests/pos/valueclasses/t6358.scala index 25539c885efb..291ae2e9e74e 100644 --- a/tests/untried/pos/t6358.scala +++ b/tests/pos/valueclasses/t6358.scala @@ -1,3 +1,5 @@ +package t6358 + class L(val t: Int) extends AnyVal { def lazyString = { lazy val x = t.toString diff --git a/tests/untried/pos/t6358_2.scala b/tests/pos/valueclasses/t6358_2.scala similarity index 87% rename from tests/untried/pos/t6358_2.scala rename to tests/pos/valueclasses/t6358_2.scala index 7c2beb60d04f..effac505af0f 100644 --- a/tests/untried/pos/t6358_2.scala +++ b/tests/pos/valueclasses/t6358_2.scala @@ -1,3 +1,5 @@ +package t6358_2 + class Y[T](val i: Option[T]) extends AnyVal { def q: List[T] = { lazy val e: List[T] = i.toList diff --git a/tests/untried/pos/t6601/PrivateValueClass_1.scala b/tests/pos/valueclasses/t6601/PrivateValueClass_1.scala similarity index 74% rename from tests/untried/pos/t6601/PrivateValueClass_1.scala rename to tests/pos/valueclasses/t6601/PrivateValueClass_1.scala index dc0137420e51..fc6f3e422069 100644 --- a/tests/untried/pos/t6601/PrivateValueClass_1.scala +++ b/tests/pos/valueclasses/t6601/PrivateValueClass_1.scala @@ -1 +1,3 @@ +package t6601 + class V private (val a: Any) extends AnyVal diff --git a/tests/untried/pos/t6601/UsePrivateValueClass_2.scala b/tests/pos/valueclasses/t6601/UsePrivateValueClass_2.scala similarity index 97% rename from tests/untried/pos/t6601/UsePrivateValueClass_2.scala rename to tests/pos/valueclasses/t6601/UsePrivateValueClass_2.scala index ec9793751efd..acd0dbef98c9 100644 --- a/tests/untried/pos/t6601/UsePrivateValueClass_2.scala +++ b/tests/pos/valueclasses/t6601/UsePrivateValueClass_2.scala @@ -1,3 +1,5 @@ +package t6601 + object Test { // After the first attempt to make seprately compiled value // classes respect the privacy of constructors, we got: diff --git a/tests/untried/pos/t6651.scala b/tests/pos/valueclasses/t6651.scala similarity index 98% rename from tests/untried/pos/t6651.scala rename to tests/pos/valueclasses/t6651.scala index 55a3b74e4cad..6201b6de3d5a 100644 --- a/tests/untried/pos/t6651.scala +++ b/tests/pos/valueclasses/t6651.scala @@ -1,3 +1,5 @@ +package t6651 + class YouAreYourself[A <: AnyRef](val you: A) extends AnyVal { def yourself: you.type = you } diff --git a/tests/untried/pos/t7818.scala b/tests/pos/valueclasses/t7818.scala similarity index 96% rename from tests/untried/pos/t7818.scala rename to tests/pos/valueclasses/t7818.scala index 77b99e7d5d0d..31f542366024 100644 --- a/tests/untried/pos/t7818.scala +++ b/tests/pos/valueclasses/t7818.scala @@ -1,3 +1,5 @@ +package t7818 + class Observable1[+T](val asJava: JObservable[_ <: T]) extends AnyVal { private def foo[X](a: JObservable[X]): JObservable[X] = ??? // was generating a type error as the type of the RHS included an existential diff --git a/tests/untried/pos/value-class-override-no-spec.flags b/tests/pos/valueclasses/value-class-override-no-spec.flags similarity index 100% rename from tests/untried/pos/value-class-override-no-spec.flags rename to tests/pos/valueclasses/value-class-override-no-spec.flags diff --git a/tests/untried/pos/value-class-override-no-spec.scala b/tests/pos/valueclasses/value-class-override-no-spec.scala similarity index 86% rename from tests/untried/pos/value-class-override-no-spec.scala rename to tests/pos/valueclasses/value-class-override-no-spec.scala index 79de5d93054a..058e3e911364 100644 --- a/tests/untried/pos/value-class-override-no-spec.scala +++ b/tests/pos/valueclasses/value-class-override-no-spec.scala @@ -1,3 +1,5 @@ +package value_class_override_no_spec + // There are two versions of this tests: one with and one without specialization. // The bug was only exposed *without* specialization. trait T extends Any { diff --git a/tests/untried/pos/value-class-override-spec.scala b/tests/pos/valueclasses/value-class-override-spec.scala similarity index 87% rename from tests/untried/pos/value-class-override-spec.scala rename to tests/pos/valueclasses/value-class-override-spec.scala index 79de5d93054a..c315be8d0494 100644 --- a/tests/untried/pos/value-class-override-spec.scala +++ b/tests/pos/valueclasses/value-class-override-spec.scala @@ -1,3 +1,5 @@ +package value_class_override_spec + // There are two versions of this tests: one with and one without specialization. // The bug was only exposed *without* specialization. trait T extends Any { diff --git a/tests/untried/pos/xlint1.flags b/tests/pos/valueclasses/xlint1.flags similarity index 100% rename from tests/untried/pos/xlint1.flags rename to tests/pos/valueclasses/xlint1.flags diff --git a/tests/untried/pos/xlint1.scala b/tests/pos/valueclasses/xlint1.scala similarity index 92% rename from tests/untried/pos/xlint1.scala rename to tests/pos/valueclasses/xlint1.scala index 27936d8b14c7..c2f39f9b3247 100644 --- a/tests/untried/pos/xlint1.scala +++ b/tests/pos/valueclasses/xlint1.scala @@ -1,3 +1,5 @@ +package xlint1 + package object foo { implicit class Bar[T](val x: T) extends AnyVal { def bippy = 1 diff --git a/tests/untried/pos/delambdafy_t6260_method.check b/tests/untried/pos/delambdafy_t6260_method.check deleted file mode 100644 index f5cd6947d128..000000000000 --- a/tests/untried/pos/delambdafy_t6260_method.check +++ /dev/null @@ -1,13 +0,0 @@ -delambdafy_t6260_method.scala:3: error: bridge generated for member method apply: (bx: Object)Object in class map$extension1 -which overrides method apply: (v1: Object)Object in trait Function1 -clashes with definition of the member itself; -both have erased type (bx: Object)Object - ((bx: Box[X]) => new Box(f(bx.x)))(this) - ^ -delambdafy_t6260_method.scala:8: error: bridge generated for member method apply: (bx: Object)Object in class map21 -which overrides method apply: (v1: Object)Object in trait Function1 -clashes with definition of the member itself; -both have erased type (bx: Object)Object - ((bx: Box[X]) => new Box(f(bx.x)))(self) - ^ -two errors found diff --git a/tests/untried/pos/delambdafy_t6260_method.flags b/tests/untried/pos/delambdafy_t6260_method.flags deleted file mode 100644 index 48b438ddf86a..000000000000 --- a/tests/untried/pos/delambdafy_t6260_method.flags +++ /dev/null @@ -1 +0,0 @@ --Ydelambdafy:method diff --git a/tests/untried/pos/delambdafy_t6260_method.scala b/tests/untried/pos/delambdafy_t6260_method.scala deleted file mode 100644 index 8edfe4ac343c..000000000000 --- a/tests/untried/pos/delambdafy_t6260_method.scala +++ /dev/null @@ -1,17 +0,0 @@ -class Box[X](val x: X) extends AnyVal { - def map[Y](f: X => Y): Box[Y] = - ((bx: Box[X]) => new Box(f(bx.x)))(this) -} - -object Test { - def map2[X, Y](self: Box[X], f: X => Y): Box[Y] = - ((bx: Box[X]) => new Box(f(bx.x)))(self) - - def main(args: Array[String]): Unit = { - val f = (x: Int) => x + 1 - val g = (x: String) => x + x - - map2(new Box(42), f) - new Box("abc") map g - } -} diff --git a/tests/untried/pos/t6260.flags b/tests/untried/pos/t6260.flags deleted file mode 100644 index 2349d8294d80..000000000000 --- a/tests/untried/pos/t6260.flags +++ /dev/null @@ -1 +0,0 @@ --Ydelambdafy:inline From e5b02a88e66af0d5e9c37a881ac0237bf1d38387 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Mon, 6 Apr 2015 18:32:38 +0200 Subject: [PATCH 16/18] Enable pending pos tests related to value classes Each test needs to have its own package because pos_all will try to compile the whole valueclasses directory at once. The remaining tests with "extends AnyVal" in tests/pending/pos are related to separate compilation, except for: - t6482.scala and t7022.scala which were fixed by https://github.com/scala/scala/pull/1468 in scalac and seem to trigger a similar bug in FullParameterization - strip-tvars-for-lubbasetypes.scala which was fixed by https://github.com/scala/scala/pull/1758 in scalac --- tests/{pending/pos => pos/valueclasses}/optmatch.scala | 4 +++- tests/{pending/pos => pos/valueclasses}/t5667.scala | 2 ++ tests/{pending/pos => pos/valueclasses}/t5953.scala | 2 ++ tests/{pending/pos => pos/valueclasses}/t6260a.scala | 2 ++ tests/{pending/pos => pos/valueclasses}/t8011.scala | 2 ++ 5 files changed, 11 insertions(+), 1 deletion(-) rename tests/{pending/pos => pos/valueclasses}/optmatch.scala (92%) rename tests/{pending/pos => pos/valueclasses}/t5667.scala (88%) rename tests/{pending/pos => pos/valueclasses}/t5953.scala (97%) rename tests/{pending/pos => pos/valueclasses}/t6260a.scala (98%) rename tests/{pending/pos => pos/valueclasses}/t8011.scala (90%) diff --git a/tests/pending/pos/optmatch.scala b/tests/pos/valueclasses/optmatch.scala similarity index 92% rename from tests/pending/pos/optmatch.scala rename to tests/pos/valueclasses/optmatch.scala index 354be65da777..a7995a455f17 100644 --- a/tests/pending/pos/optmatch.scala +++ b/tests/pos/valueclasses/optmatch.scala @@ -1,3 +1,5 @@ +package optmatch + // final case class NonZeroLong(value: Long) extends AnyVal { // def get: Long = value // def isEmpty: Boolean = get == 0l @@ -5,7 +7,7 @@ class NonZeroLong(val value: Long) extends AnyVal { def get: Long = value - def isEmpty: Boolean = get == 0l + def isDefined: Boolean = get != 0l } object NonZeroLong { def unapply(value: Long): NonZeroLong = new NonZeroLong(value) diff --git a/tests/pending/pos/t5667.scala b/tests/pos/valueclasses/t5667.scala similarity index 88% rename from tests/pending/pos/t5667.scala rename to tests/pos/valueclasses/t5667.scala index 353eec93d6ae..80efb181bd99 100644 --- a/tests/pending/pos/t5667.scala +++ b/tests/pos/valueclasses/t5667.scala @@ -1,3 +1,5 @@ +package t5667 + object Main { implicit class C(val s: String) extends AnyVal implicit class C2(val s: String) extends AnyRef diff --git a/tests/pending/pos/t5953.scala b/tests/pos/valueclasses/t5953.scala similarity index 97% rename from tests/pending/pos/t5953.scala rename to tests/pos/valueclasses/t5953.scala index 7ba035ec3bca..669fac7dfb17 100644 --- a/tests/pending/pos/t5953.scala +++ b/tests/pos/valueclasses/t5953.scala @@ -1,3 +1,5 @@ +package t5953 + import scala.collection.{ mutable, immutable, generic, GenTraversableOnce } package object foo { diff --git a/tests/pending/pos/t6260a.scala b/tests/pos/valueclasses/t6260a.scala similarity index 98% rename from tests/pending/pos/t6260a.scala rename to tests/pos/valueclasses/t6260a.scala index 21b2fd43ce56..e29f10452dca 100644 --- a/tests/pending/pos/t6260a.scala +++ b/tests/pos/valueclasses/t6260a.scala @@ -1,3 +1,5 @@ +package t6260a + final class Option[+A](val value: A) extends AnyVal // Was: sandbox/test.scala:21: error: bridge generated for member method f: ()Option[A] in class Bar diff --git a/tests/pending/pos/t8011.scala b/tests/pos/valueclasses/t8011.scala similarity index 90% rename from tests/pending/pos/t8011.scala rename to tests/pos/valueclasses/t8011.scala index 451590d77e71..88b4b53aa2b7 100644 --- a/tests/pending/pos/t8011.scala +++ b/tests/pos/valueclasses/t8011.scala @@ -1,3 +1,5 @@ +package t8011 + class ThingOps1(val x: String) extends AnyVal { def fn[A]: Any = { new X[A] { def foo(a: A) = a } From 449055db733a78a44b15addc3ddcbb51bfdc3aa4 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 17 Apr 2015 21:55:25 +0200 Subject: [PATCH 17/18] Fix companionClass not working after Erasure for value classes For a module class V$, the synthesized companion class method looks like: val companion$class: V If V is a value class, after erasure it will look like: val companion$class: ErasedValueType(V, ...) This will break SymDenotation#companionClass which relies on the type of companion$class. The solution is to not semi-erase the type of companion$class. --- src/dotty/tools/dotc/core/TypeErasure.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 1ea63465ab69..e695e6721a11 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -142,11 +142,15 @@ object TypeErasure { * - 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, semiEraseVCs = 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( From d012f93635184dc8aa6325b715a133861c74ab08 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 22 Apr 2015 01:40:03 +0200 Subject: [PATCH 18/18] Erasure: Box closures of value classes when needed 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. --- src/dotty/tools/dotc/transform/Erasure.scala | 52 ++++++++++++++++++++ src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 7d74d1616db5..996c480ce778 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -481,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/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index a2b280c6e922..b58f4872870c 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 =