@@ -36,7 +36,7 @@ import scala.annotation.tailrec
36
36
object TypeErasure {
37
37
38
38
private def erasureDependsOnArgs (sym : Symbol )(using Context ) =
39
- sym == defn.ArrayClass || sym == defn.PairClass
39
+ sym == defn.ArrayClass || sym == defn.PairClass || isDerivedValueClass(sym)
40
40
41
41
def normalizeClass (cls : ClassSymbol )(using Context ): ClassSymbol = {
42
42
if (cls.owner == defn.ScalaPackageClass ) {
@@ -59,7 +59,7 @@ object TypeErasure {
59
59
case tp : TypeRef =>
60
60
val sym = tp.symbol
61
61
sym.isClass &&
62
- ! erasureDependsOnArgs(sym) &&
62
+ ( ! erasureDependsOnArgs(sym) || isDerivedValueClass(sym) ) &&
63
63
! defn.specialErasure.contains(sym) &&
64
64
! defn.isSyntheticFunctionClass(sym)
65
65
case _ : TermRef =>
@@ -157,7 +157,7 @@ object TypeErasure {
157
157
158
158
def sigName (tp : Type , isJava : Boolean )(using Context ): TypeName = {
159
159
val normTp = tp.translateFromRepeated(toArray = isJava)
160
- val erase = erasureFn(isJava, semiEraseVCs = false , isConstructor = false , wildcardOK = true )
160
+ val erase = erasureFn(isJava, semiEraseVCs = true , isConstructor = false , wildcardOK = true )
161
161
erase.sigName(normTp)(using preErasureCtx)
162
162
}
163
163
@@ -444,14 +444,15 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
444
444
case tp : TypeRef =>
445
445
val sym = tp.symbol
446
446
if (! sym.isClass) this (tp.translucentSuperType)
447
- else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef (tp)
447
+ else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClass (tp)
448
448
else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym)
449
449
else eraseNormalClassRef(tp)
450
450
case tp : AppliedType =>
451
451
val tycon = tp.tycon
452
452
if (tycon.isRef(defn.ArrayClass )) eraseArray(tp)
453
453
else if (tycon.isRef(defn.PairClass )) erasePair(tp)
454
454
else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = isJava))
455
+ else if (semiEraseVCs && isDerivedValueClass(tycon.classSymbol)) eraseDerivedValueClass(tp)
455
456
else apply(tp.translucentSuperType)
456
457
case _ : TermRef | _ : ThisType =>
457
458
this (tp.widen)
@@ -551,12 +552,34 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
551
552
case rt => MethodType (Nil , Nil , rt)
552
553
case tp1 => this (tp1)
553
554
554
- private def eraseDerivedValueClassRef (tref : TypeRef )(using Context ): Type = {
555
- val cls = tref.symbol.asClass
556
- val underlying = underlyingOfValueClass(cls)
557
- if underlying.exists && ! isCyclic(cls) then
558
- val erasedValue = valueErasure(underlying)
559
- if erasedValue.exists then ErasedValueType (tref, erasedValue)
555
+ private def eraseDerivedValueClass (tp : Type )(using Context ): Type = {
556
+ val cls = tp.classSymbol.asClass
557
+ val unbox = valueClassUnbox(cls)
558
+ if unbox.exists then
559
+ val genericUnderlying = unbox.info.resultType
560
+ val underlying = tp.select(unbox).widen.resultType
561
+
562
+ val erasedUnderlying = erasure(underlying)
563
+
564
+ // Ideally, we would just use `erasedUnderlying` as the erasure of `tp`, but to
565
+ // be binary-compatible with Scala 2 we need two special cases for polymorphic
566
+ // value classes:
567
+ // - Given `class Foo[A](x: A) extends AnyVal`, `Foo[X]` should erase like
568
+ // `X`, except if its a primitive in which case it erases to the boxed
569
+ // version of this primitive.
570
+ // - Given `class Bar[A](x: Array[A]) extends AnyVal`, `Bar[X]` will be
571
+ // erased like `Array[A]` as seen from its definition site, no matter
572
+ // the `X` (same if `A` is bounded).
573
+ //
574
+ // The binary compatibility is checked by sbt-dotty/sbt-test/scala2-compat/i8001
575
+ val erasedValueClass =
576
+ if erasedUnderlying.isPrimitiveValueType && ! genericUnderlying.isPrimitiveValueType then
577
+ defn.boxedType(erasedUnderlying)
578
+ else if genericUnderlying.derivesFrom(defn.ArrayClass ) then
579
+ erasure(genericUnderlying)
580
+ else erasedUnderlying
581
+
582
+ if erasedValueClass.exists then ErasedValueType (cls.typeRef, erasedValueClass)
560
583
else
561
584
assert(ctx.reporter.errorsReported, i " no erasure for $underlying" )
562
585
NoType
@@ -569,22 +592,23 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
569
592
}
570
593
571
594
/** The erasure of a function result type. */
572
- private def eraseResult (tp : Type )(using Context ): Type = tp match {
573
- case tp : TypeRef =>
574
- val sym = tp.symbol
575
- if (sym eq defn.UnitClass ) sym.typeRef
576
- // For a value class V, "new V(x)" should have type V for type adaptation to work
577
- // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the return type of a
578
- // constructor method should not be semi-erased.
579
- else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
580
- else this (tp)
581
- case tp : AppliedType =>
582
- val sym = tp.tycon.typeSymbol
583
- if (sym.isClass && ! erasureDependsOnArgs(sym)) eraseResult(tp.tycon)
584
- else this (tp)
585
- case _ =>
586
- this (tp)
587
- }
595
+ def eraseResult (tp : Type )(using Context ): Type =
596
+ // For a value class V, "new V(x)" should have type V for type adaptation to work
597
+ // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the result type of a
598
+ // constructor method should not be semi-erased.
599
+ if semiEraseVCs && isConstructor && ! tp.isInstanceOf [MethodOrPoly ] then
600
+ erasureFn(isJava, semiEraseVCs = false , isConstructor, wildcardOK).eraseResult(tp)
601
+ else tp match
602
+ case tp : TypeRef =>
603
+ val sym = tp.symbol
604
+ if (sym eq defn.UnitClass ) sym.typeRef
605
+ else this (tp)
606
+ case tp : AppliedType =>
607
+ val sym = tp.tycon.typeSymbol
608
+ if (sym.isClass && ! erasureDependsOnArgs(sym)) eraseResult(tp.tycon)
609
+ else this (tp)
610
+ case _ =>
611
+ this (tp)
588
612
589
613
/** The name of the type as it is used in `Signature`s.
590
614
* Need to ensure correspondence with erasure!
@@ -602,7 +626,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
602
626
return sigName(info)
603
627
}
604
628
if (isDerivedValueClass(sym)) {
605
- val erasedVCRef = eraseDerivedValueClassRef (tp)
629
+ val erasedVCRef = eraseDerivedValueClass (tp)
606
630
if (erasedVCRef.exists) return sigName(erasedVCRef)
607
631
}
608
632
if (defn.isSyntheticFunctionClass(sym))
0 commit comments