@@ -36,7 +36,7 @@ import scala.annotation.tailrec
3636object TypeErasure {
3737
3838 private def erasureDependsOnArgs (sym : Symbol )(using Context ) =
39- sym == defn.ArrayClass || sym == defn.PairClass
39+ sym == defn.ArrayClass || sym == defn.PairClass || isDerivedValueClass(sym)
4040
4141 def normalizeClass (cls : ClassSymbol )(using Context ): ClassSymbol = {
4242 if (cls.owner == defn.ScalaPackageClass ) {
@@ -59,7 +59,7 @@ object TypeErasure {
5959 case tp : TypeRef =>
6060 val sym = tp.symbol
6161 sym.isClass &&
62- ! erasureDependsOnArgs(sym) &&
62+ ( ! erasureDependsOnArgs(sym) || isDerivedValueClass(sym) ) &&
6363 ! defn.specialErasure.contains(sym) &&
6464 ! defn.isSyntheticFunctionClass(sym)
6565 case _ : TermRef =>
@@ -157,7 +157,7 @@ object TypeErasure {
157157
158158 def sigName (tp : Type , isJava : Boolean )(using Context ): TypeName = {
159159 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 )
161161 erase.sigName(normTp)(using preErasureCtx)
162162 }
163163
@@ -444,14 +444,15 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
444444 case tp : TypeRef =>
445445 val sym = tp.symbol
446446 if (! sym.isClass) this (tp.translucentSuperType)
447- else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef (tp)
447+ else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClass (tp)
448448 else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym)
449449 else eraseNormalClassRef(tp)
450450 case tp : AppliedType =>
451451 val tycon = tp.tycon
452452 if (tycon.isRef(defn.ArrayClass )) eraseArray(tp)
453453 else if (tycon.isRef(defn.PairClass )) erasePair(tp)
454454 else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = isJava))
455+ else if (semiEraseVCs && isDerivedValueClass(tycon.classSymbol)) eraseDerivedValueClass(tp)
455456 else apply(tp.translucentSuperType)
456457 case _ : TermRef | _ : ThisType =>
457458 this (tp.widen)
@@ -551,12 +552,34 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
551552 case rt => MethodType (Nil , Nil , rt)
552553 case tp1 => this (tp1)
553554
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)
560583 else
561584 assert(ctx.reporter.errorsReported, i " no erasure for $underlying" )
562585 NoType
@@ -569,22 +592,23 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
569592 }
570593
571594 /** 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)
588612
589613 /** The name of the type as it is used in `Signature`s.
590614 * Need to ensure correspondence with erasure!
@@ -602,7 +626,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
602626 return sigName(info)
603627 }
604628 if (isDerivedValueClass(sym)) {
605- val erasedVCRef = eraseDerivedValueClassRef (tp)
629+ val erasedVCRef = eraseDerivedValueClass (tp)
606630 if (erasedVCRef.exists) return sigName(erasedVCRef)
607631 }
608632 if (defn.isSyntheticFunctionClass(sym))
0 commit comments