Skip to content

Commit c7054c2

Browse files
authored
Merge pull request #11603 from dotty-staging/scala2-intersection-erasure-3
Correctly erase Scala 2 intersection types
2 parents e6be077 + 943636f commit c7054c2

File tree

24 files changed

+911
-89
lines changed

24 files changed

+911
-89
lines changed

compiler/src/dotty/tools/dotc/core/Denotations.scala

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,9 @@ object Denotations {
582582
*/
583583
def prefix: Type = NoPrefix
584584

585-
/** Either the Scala or Java signature of the info, depending on where the
586-
* symbol is defined.
585+
/** For SymDenotations, the language-specific signature of the info, depending on
586+
* where the symbol is defined. For non-SymDenotations, the Scala 3
587+
* signature.
587588
*
588589
* Invariants:
589590
* - Before erasure, the signature of a denotation is always equal to the
@@ -595,17 +596,17 @@ object Denotations {
595596
* SingleDenotations will have distinct signatures (cf #9050).
596597
*/
597598
final def signature(using Context): Signature =
598-
signature(isJava = !isType && symbol.is(JavaDefined))
599+
signature(sourceLanguage = if isType || !this.isInstanceOf[SymDenotation] then SourceLanguage.Scala3 else SourceLanguage(symbol))
599600

600-
/** Overload of `signature` which lets the caller pick between the Java and
601-
* Scala signature of the info. Useful to match denotations defined in
601+
/** Overload of `signature` which lets the caller pick the language used
602+
* to compute the signature of the info. Useful to match denotations defined in
602603
* different classes (see `matchesLoosely`).
603604
*/
604-
def signature(isJava: Boolean)(using Context): Signature =
605+
def signature(sourceLanguage: SourceLanguage)(using Context): Signature =
605606
if (isType) Signature.NotAMethod // don't force info if this is a type denotation
606607
else info match {
607608
case info: MethodOrPoly =>
608-
try info.signature(isJava)
609+
try info.signature(sourceLanguage)
609610
catch { // !!! DEBUG
610611
case scala.util.control.NonFatal(ex) =>
611612
report.echo(s"cannot take signature of $info")
@@ -1013,36 +1014,36 @@ object Denotations {
10131014

10141015
/** `matches` without a target name check.
10151016
*
1016-
* We consider a Scala method and a Java method to match if they have
1017-
* matching Scala signatures. This allows us to override some Java
1018-
* definitions even if they have a different erasure (see i8615b,
1019-
* i9109b), Erasure takes care of adding any necessary bridge to make
1020-
* this work at runtime.
1017+
* For definitions coming from different languages, we pick a common
1018+
* language to compute their signatures. This allows us for example to
1019+
* override some Java definitions from Scala even if they have a different
1020+
* erasure (see i8615b, i9109b), Erasure takes care of adding any necessary
1021+
* bridge to make this work at runtime.
10211022
*/
10221023
def matchesLoosely(other: SingleDenotation)(using Context): Boolean =
10231024
if isType then true
10241025
else
1025-
val isJava = symbol.is(JavaDefined)
1026-
val otherIsJava = other.symbol.is(JavaDefined)
1027-
val useJavaSig = isJava && otherIsJava
1028-
val sig = signature(isJava = useJavaSig)
1029-
val otherSig = other.signature(isJava = useJavaSig)
1026+
val thisLanguage = SourceLanguage(symbol)
1027+
val otherLanguage = SourceLanguage(other.symbol)
1028+
val commonLanguage = SourceLanguage.commonLanguage(thisLanguage, otherLanguage)
1029+
val sig = signature(commonLanguage)
1030+
val otherSig = other.signature(commonLanguage)
10301031
sig.matchDegree(otherSig) match
10311032
case FullMatch =>
10321033
true
10331034
case MethodNotAMethodMatch =>
10341035
!ctx.erasedTypes && {
10351036
// A Scala zero-parameter method and a Scala non-method always match.
1036-
if !isJava && !otherIsJava then
1037+
if !thisLanguage.isJava && !otherLanguage.isJava then
10371038
true
10381039
// Java allows defining both a field and a zero-parameter method with the same name,
10391040
// so they must not match.
1040-
else if isJava && otherIsJava then
1041+
else if thisLanguage.isJava && otherLanguage.isJava then
10411042
false
10421043
// A Java field never matches a Scala method.
1043-
else if isJava then
1044+
else if thisLanguage.isJava then
10441045
symbol.is(Method)
1045-
else // otherIsJava
1046+
else // otherLanguage.isJava
10461047
other.symbol.is(Method)
10471048
}
10481049
case ParamMatch =>

compiler/src/dotty/tools/dotc/core/Signature.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ case class Signature(paramsSig: List[ParamSig], resSig: TypeName) {
109109
*
110110
* Like Signature#apply, the result is only cacheable if `isUnderDefined == false`.
111111
*/
112-
def prependTermParams(params: List[Type], isJava: Boolean)(using Context): Signature =
113-
Signature(params.map(p => sigName(p, isJava)) ::: paramsSig, resSig)
112+
def prependTermParams(params: List[Type], sourceLanguage: SourceLanguage)(using Context): Signature =
113+
Signature(params.map(p => sigName(p, sourceLanguage)) ::: paramsSig, resSig)
114114

115115
/** Construct a signature by prepending the length of a type parameter section
116116
* to the parameter part of this signature.
@@ -164,9 +164,9 @@ object Signature {
164164
* otherwise the signature will change once the contained type variables have
165165
* been instantiated.
166166
*/
167-
def apply(resultType: Type, isJava: Boolean)(using Context): Signature = {
167+
def apply(resultType: Type, sourceLanguage: SourceLanguage)(using Context): Signature = {
168168
assert(!resultType.isInstanceOf[ExprType])
169-
apply(Nil, sigName(resultType, isJava))
169+
apply(Nil, sigName(resultType, sourceLanguage))
170170
}
171171

172172
val lexicographicOrdering: Ordering[Signature] = new Ordering[Signature] {

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,45 @@ import transform.ExplicitOuter._
1010
import transform.ValueClasses._
1111
import transform.TypeUtils._
1212
import transform.ContextFunctionResults._
13+
import unpickleScala2.Scala2Erasure
1314
import Decorators._
1415
import Definitions.MaxImplementedFunctionArity
1516
import scala.annotation.tailrec
1617

18+
/** The language in which the definition being erased was written. */
19+
enum SourceLanguage:
20+
case Java, Scala2, Scala3
21+
def isJava: Boolean = this eq Java
22+
def isScala2: Boolean = this eq Scala2
23+
def isScala3: Boolean = this eq Scala3
24+
object SourceLanguage:
25+
/** The language in which `sym` was defined. */
26+
def apply(sym: Symbol)(using Context): SourceLanguage =
27+
if sym.is(JavaDefined) then
28+
SourceLanguage.Java
29+
// Scala 2 methods don't have Inline set, except for the ones injected with `patchStdlibClass`
30+
// which are really Scala 3 methods.
31+
else if sym.isClass && sym.is(Scala2x) || (sym.maybeOwner.is(Scala2x) && !sym.is(Inline)) then
32+
SourceLanguage.Scala2
33+
else
34+
SourceLanguage.Scala3
35+
36+
/** Number of bits needed to represent this enum. */
37+
def bits: Int =
38+
val len = values.length
39+
val log2 = 31 - Integer.numberOfLeadingZeros(len)
40+
if len == 1 << log2 then
41+
log2
42+
else
43+
log2 + 1
44+
45+
/** A common language to use when matching definitions written in different
46+
* languages.
47+
*/
48+
def commonLanguage(x: SourceLanguage, y: SourceLanguage): SourceLanguage =
49+
if x.ordinal > y.ordinal then x else y
50+
end SourceLanguage
51+
1752
/** Erased types are:
1853
*
1954
* ErasedValueType
@@ -107,28 +142,29 @@ object TypeErasure {
107142
}
108143
}
109144

110-
private def erasureIdx(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
111-
(if (isJava) 1 else 0) +
112-
(if (semiEraseVCs) 2 else 0) +
113-
(if (isConstructor) 4 else 0) +
114-
(if (wildcardOK) 8 else 0)
145+
private def erasureIdx(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
146+
extension (b: Boolean) def toInt = if b then 1 else 0
147+
wildcardOK.toInt
148+
+ (isConstructor.toInt << 1)
149+
+ (semiEraseVCs.toInt << 2)
150+
+ (sourceLanguage.ordinal << 3)
115151

116-
private val erasures = new Array[TypeErasure](16)
152+
private val erasures = new Array[TypeErasure](1 << (SourceLanguage.bits + 3))
117153

118-
for {
119-
isJava <- List(false, true)
154+
for
155+
sourceLanguage <- SourceLanguage.values
120156
semiEraseVCs <- List(false, true)
121157
isConstructor <- List(false, true)
122158
wildcardOK <- List(false, true)
123-
}
124-
erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) =
125-
new TypeErasure(isJava, semiEraseVCs, isConstructor, wildcardOK)
159+
do
160+
erasures(erasureIdx(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)) =
161+
new TypeErasure(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)
126162

127163
/** Produces an erasure function. See the documentation of the class [[TypeErasure]]
128164
* for a description of each parameter.
129165
*/
130-
private def erasureFn(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure =
131-
erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK))
166+
private def erasureFn(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure =
167+
erasures(erasureIdx(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK))
132168

133169
/** The current context with a phase no later than erasure */
134170
def preErasureCtx(using Context) =
@@ -139,25 +175,29 @@ object TypeErasure {
139175
* @param tp The type to erase.
140176
*/
141177
def erasure(tp: Type)(using Context): Type =
142-
erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
178+
erasureFn(sourceLanguage = SourceLanguage.Scala3, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
143179

144180
/** The value class erasure of a Scala type, where value classes are semi-erased to
145181
* ErasedValueType (they will be fully erased in [[ElimErasedValueType]]).
146182
*
147183
* @param tp The type to erase.
148184
*/
149185
def valueErasure(tp: Type)(using Context): Type =
150-
erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
186+
erasureFn(sourceLanguage = SourceLanguage.Scala3, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
187+
188+
/** The erasure that Scala 2 would use for this type. */
189+
def scala2Erasure(tp: Type)(using Context): Type =
190+
erasureFn(sourceLanguage = SourceLanguage.Scala2, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
151191

152192
/** Like value class erasure, but value classes erase to their underlying type erasure */
153193
def fullErasure(tp: Type)(using Context): Type =
154194
valueErasure(tp) match
155195
case ErasedValueType(_, underlying) => erasure(underlying)
156196
case etp => etp
157197

158-
def sigName(tp: Type, isJava: Boolean)(using Context): TypeName = {
159-
val normTp = tp.translateFromRepeated(toArray = isJava)
160-
val erase = erasureFn(isJava, semiEraseVCs = true, isConstructor = false, wildcardOK = true)
198+
def sigName(tp: Type, sourceLanguage: SourceLanguage)(using Context): TypeName = {
199+
val normTp = tp.translateFromRepeated(toArray = sourceLanguage.isJava)
200+
val erase = erasureFn(sourceLanguage, semiEraseVCs = !sourceLanguage.isJava, isConstructor = false, wildcardOK = true)
161201
erase.sigName(normTp)(using preErasureCtx)
162202
}
163203

@@ -181,15 +221,13 @@ object TypeErasure {
181221
* - For $asInstanceOf : [T]T
182222
* - For $isInstanceOf : [T]Boolean
183223
* - For all abstract types : = ?
184-
* - For Java-defined symbols: : the erasure of their type with isJava = true,
185-
* semiEraseVCs = false. Semi-erasure never happens in Java.
186-
* - For all other symbols : the semi-erasure of their types, with
187-
* isJava, isConstructor set according to symbol.
224+
*
225+
* `sourceLanguage`, `isConstructor` and `semiEraseVCs` are set based on the symbol.
188226
*/
189227
def transformInfo(sym: Symbol, tp: Type)(using Context): Type = {
190-
val isJava = sym is JavaDefined
191-
val semiEraseVCs = !isJava
192-
val erase = erasureFn(isJava, semiEraseVCs, sym.isConstructor, wildcardOK = false)
228+
val sourceLanguage = SourceLanguage(sym)
229+
val semiEraseVCs = !sourceLanguage.isJava // Java sees our value classes as regular classes.
230+
val erase = erasureFn(sourceLanguage, semiEraseVCs, sym.isConstructor, wildcardOK = false)
193231

194232
def eraseParamBounds(tp: PolyType): Type =
195233
tp.derivedLambdaType(
@@ -391,18 +429,20 @@ object TypeErasure {
391429
case _ => false
392430
}
393431
}
432+
394433
import TypeErasure._
395434

396435
/**
397-
* @param isJava Arguments should be treated the way Java does it
398-
* @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType
399-
* (they will be fully erased in [[ElimErasedValueType]]).
400-
* If false, they are erased like normal classes.
401-
* @param isConstructor Argument forms part of the type of a constructor
402-
* @param wildcardOK Wildcards are acceptable (true when using the erasure
403-
* for computing a signature name).
436+
* @param sourceLanguage Adapt our erasure rules to mimic what the given language
437+
* would do.
438+
* @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType
439+
* (they will be fully erased in [[ElimErasedValueType]]).
440+
* If false, they are erased like normal classes.
441+
* @param isConstructor Argument forms part of the type of a constructor
442+
* @param wildcardOK Wildcards are acceptable (true when using the erasure
443+
* for computing a signature name).
404444
*/
405-
class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) {
445+
class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) {
406446

407447
/** The erasure |T| of a type T. This is:
408448
*
@@ -450,7 +490,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
450490
val tycon = tp.tycon
451491
if (tycon.isRef(defn.ArrayClass)) eraseArray(tp)
452492
else if (tycon.isRef(defn.PairClass)) erasePair(tp)
453-
else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = isJava))
493+
else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = sourceLanguage.isJava))
454494
else if (semiEraseVCs && isDerivedValueClass(tycon.classSymbol)) eraseDerivedValueClass(tp)
455495
else apply(tp.translucentSuperType)
456496
case _: TermRef | _: ThisType =>
@@ -467,13 +507,16 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
467507
this(defn.FunctionType(paramss.head.length, isContextual = res.isImplicitMethod, isErased = res.isErasedMethod))
468508
case tp: TypeProxy =>
469509
this(tp.underlying)
470-
case AndType(tp1, tp2) =>
471-
erasedGlb(this(tp1), this(tp2), isJava)
510+
case tp @ AndType(tp1, tp2) =>
511+
if sourceLanguage.isScala2 then
512+
this(Scala2Erasure.intersectionDominator(Scala2Erasure.flattenedParents(tp)))
513+
else
514+
erasedGlb(this(tp1), this(tp2), isJava = sourceLanguage.isJava)
472515
case OrType(tp1, tp2) =>
473516
TypeComparer.orType(this(tp1), this(tp2), isErased = true)
474517
case tp: MethodType =>
475518
def paramErasure(tpToErase: Type) =
476-
erasureFn(isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
519+
erasureFn(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
477520
val (names, formals0) = if (tp.isErasedMethod) (Nil, Nil) else (tp.paramNames, tp.paramInfos)
478521
val formals = formals0.mapConserve(paramErasure)
479522
eraseResult(tp.resultType) match {
@@ -516,8 +559,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
516559
private def eraseArray(tp: Type)(using Context) = {
517560
val defn.ArrayOf(elemtp) = tp
518561
if (classify(elemtp).derivesFrom(defn.NullClass)) JavaArrayType(defn.ObjectType)
519-
else if (isUnboundedGeneric(elemtp) && !isJava) defn.ObjectType
520-
else JavaArrayType(erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(elemtp))
562+
else if (isUnboundedGeneric(elemtp) && !sourceLanguage.isJava) defn.ObjectType
563+
else JavaArrayType(erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, wildcardOK)(elemtp))
521564
}
522565

523566
private def erasePair(tp: Type)(using Context): Type = {
@@ -544,7 +587,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
544587
// See doc comment for ElimByName for speculation how we could improve this.
545588
else
546589
MethodType(Nil, Nil,
547-
eraseResult(sym.info.finalResultType.translateFromRepeated(toArray = isJava)))
590+
eraseResult(sym.info.finalResultType.translateFromRepeated(toArray = sourceLanguage.isJava)))
548591
case tp1: PolyType =>
549592
eraseResult(tp1.resultType) match
550593
case rt: MethodType => rt
@@ -596,7 +639,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
596639
// correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the result type of a
597640
// constructor method should not be semi-erased.
598641
if semiEraseVCs && isConstructor && !tp.isInstanceOf[MethodOrPoly] then
599-
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK).eraseResult(tp)
642+
erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, wildcardOK).eraseResult(tp)
600643
else tp match
601644
case tp: TypeRef =>
602645
val sym = tp.symbol
@@ -624,7 +667,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
624667
if (!info.exists) assert(false, i"undefined: $tp with symbol $sym")
625668
return sigName(info)
626669
}
627-
if (isDerivedValueClass(sym)) {
670+
if (semiEraseVCs && isDerivedValueClass(sym)) {
628671
val erasedVCRef = eraseDerivedValueClass(tp)
629672
if (erasedVCRef.exists) return sigName(erasedVCRef)
630673
}

0 commit comments

Comments
 (0)