Skip to content

Commit 295d598

Browse files
committed
Add a Scala 2 erasure mode
Previously we only distinguished between Java and Scala erasure, now we have Java, Scala 2 and Scala 3 erasure modes. This commit only adds the machinery needed to make that distinction, latter commits in this PR will introduce differences between the Scala 2 and 3 modes.
1 parent c08543a commit 295d598

File tree

5 files changed

+125
-83
lines changed

5 files changed

+125
-83
lines changed

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

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,7 @@ 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+
/** The symbol-specific signature of the info.
587586
*
588587
* Invariants:
589588
* - Before erasure, the signature of a denotation is always equal to the
@@ -595,17 +594,17 @@ object Denotations {
595594
* SingleDenotations will have distinct signatures (cf #9050).
596595
*/
597596
final def signature(using Context): Signature =
598-
signature(isJava = !isType && symbol.is(JavaDefined))
597+
signature(sourceLanguage = if isType then SourceLanguage.Scala3 else SourceLanguage(symbol))
599598

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
599+
/** Overload of `signature` which lets the caller pick the language used
600+
* to compute the signature of the info. Useful to match denotations defined in
602601
* different classes (see `matchesLoosely`).
603602
*/
604-
def signature(isJava: Boolean)(using Context): Signature =
603+
def signature(sourceLanguage: SourceLanguage)(using Context): Signature =
605604
if (isType) Signature.NotAMethod // don't force info if this is a type denotation
606605
else info match {
607606
case info: MethodOrPoly =>
608-
try info.signature(isJava)
607+
try info.signature(sourceLanguage)
609608
catch { // !!! DEBUG
610609
case scala.util.control.NonFatal(ex) =>
611610
report.echo(s"cannot take signature of $info")
@@ -1013,36 +1012,36 @@ object Denotations {
10131012

10141013
/** `matches` without a target name check.
10151014
*
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.
1015+
* For definitions coming from different languages, we pick a common
1016+
* language to compute their signatures. This allows us for example to
1017+
* override some Java definitions from Scala even if they have a different
1018+
* erasure (see i8615b, i9109b), Erasure takes care of adding any necessary
1019+
* bridge to make this work at runtime.
10211020
*/
10221021
def matchesLoosely(other: SingleDenotation)(using Context): Boolean =
10231022
if isType then true
10241023
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)
1024+
val thisLanguage = SourceLanguage(symbol)
1025+
val otherLanguage = SourceLanguage(other.symbol)
1026+
val commonLanguage = SourceLanguage.commonLanguage(thisLanguage, otherLanguage)
1027+
val sig = signature(commonLanguage)
1028+
val otherSig = other.signature(commonLanguage)
10301029
sig.matchDegree(otherSig) match
10311030
case FullMatch =>
10321031
true
10331032
case MethodNotAMethodMatch =>
10341033
!ctx.erasedTypes && {
10351034
// A Scala zero-parameter method and a Scala non-method always match.
1036-
if !isJava && !otherIsJava then
1035+
if !thisLanguage.isJava && !otherLanguage.isJava then
10371036
true
10381037
// Java allows defining both a field and a zero-parameter method with the same name,
10391038
// so they must not match.
1040-
else if isJava && otherIsJava then
1039+
else if thisLanguage.isJava && otherLanguage.isJava then
10411040
false
10421041
// A Java field never matches a Scala method.
1043-
else if isJava then
1042+
else if thisLanguage.isJava then
10441043
symbol.is(Method)
1045-
else // otherIsJava
1044+
else // otherLanguage.isJava
10461045
other.symbol.is(Method)
10471046
}
10481047
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: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,40 @@ import Decorators._
1414
import Definitions.MaxImplementedFunctionArity
1515
import scala.annotation.tailrec
1616

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

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)
144+
private def erasureIdx(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
145+
extension (b: Boolean) def toInt = if b then 1 else 0
146+
wildcardOK.toInt
147+
+ (isConstructor.toInt << 1)
148+
+ (semiEraseVCs.toInt << 2)
149+
+ (sourceLanguage.ordinal << 3)
115150

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

118-
for {
119-
isJava <- List(false, true)
153+
for
154+
sourceLanguage <- SourceLanguage.values
120155
semiEraseVCs <- List(false, true)
121156
isConstructor <- List(false, true)
122157
wildcardOK <- List(false, true)
123-
}
124-
erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) =
125-
new TypeErasure(isJava, semiEraseVCs, isConstructor, wildcardOK)
158+
do
159+
erasures(erasureIdx(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)) =
160+
new TypeErasure(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)
126161

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

133168
/** The current context with a phase no later than erasure */
134169
def preErasureCtx(using Context) =
@@ -139,25 +174,25 @@ object TypeErasure {
139174
* @param tp The type to erase.
140175
*/
141176
def erasure(tp: Type)(using Context): Type =
142-
erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
177+
erasureFn(sourceLanguage = SourceLanguage.Scala3, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
143178

144179
/** The value class erasure of a Scala type, where value classes are semi-erased to
145180
* ErasedValueType (they will be fully erased in [[ElimErasedValueType]]).
146181
*
147182
* @param tp The type to erase.
148183
*/
149184
def valueErasure(tp: Type)(using Context): Type =
150-
erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
185+
erasureFn(sourceLanguage = SourceLanguage.Scala3, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(using preErasureCtx)
151186

152187
/** Like value class erasure, but value classes erase to their underlying type erasure */
153188
def fullErasure(tp: Type)(using Context): Type =
154189
valueErasure(tp) match
155190
case ErasedValueType(_, underlying) => erasure(underlying)
156191
case etp => etp
157192

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)
193+
def sigName(tp: Type, sourceLanguage: SourceLanguage)(using Context): TypeName = {
194+
val normTp = tp.translateFromRepeated(toArray = sourceLanguage.isJava)
195+
val erase = erasureFn(sourceLanguage, semiEraseVCs = true, isConstructor = false, wildcardOK = true)
161196
erase.sigName(normTp)(using preErasureCtx)
162197
}
163198

@@ -181,15 +216,13 @@ object TypeErasure {
181216
* - For $asInstanceOf : [T]T
182217
* - For $isInstanceOf : [T]Boolean
183218
* - 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.
219+
*
220+
* `sourceLanguage`, `isConstructor` and `semiEraseVCs` are set based on the symbol.
188221
*/
189222
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)
223+
val sourceLanguage = SourceLanguage(sym)
224+
val semiEraseVCs = !sourceLanguage.isJava // Java sees our value classes as regular classes.
225+
val erase = erasureFn(sourceLanguage, semiEraseVCs, sym.isConstructor, wildcardOK = false)
193226

194227
def eraseParamBounds(tp: PolyType): Type =
195228
tp.derivedLambdaType(
@@ -391,18 +424,20 @@ object TypeErasure {
391424
case _ => false
392425
}
393426
}
427+
394428
import TypeErasure._
395429

396430
/**
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).
431+
* @param sourceLanguage Adapt our erasure rules to mimic what the given language
432+
* would do.
433+
* @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType
434+
* (they will be fully erased in [[ElimErasedValueType]]).
435+
* If false, they are erased like normal classes.
436+
* @param isConstructor Argument forms part of the type of a constructor
437+
* @param wildcardOK Wildcards are acceptable (true when using the erasure
438+
* for computing a signature name).
404439
*/
405-
class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) {
440+
class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) {
406441

407442
/** The erasure |T| of a type T. This is:
408443
*
@@ -450,7 +485,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
450485
val tycon = tp.tycon
451486
if (tycon.isRef(defn.ArrayClass)) eraseArray(tp)
452487
else if (tycon.isRef(defn.PairClass)) erasePair(tp)
453-
else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = isJava))
488+
else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = sourceLanguage.isJava))
454489
else if (semiEraseVCs && isDerivedValueClass(tycon.classSymbol)) eraseDerivedValueClass(tp)
455490
else apply(tp.translucentSuperType)
456491
case _: TermRef | _: ThisType =>
@@ -468,12 +503,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
468503
case tp: TypeProxy =>
469504
this(tp.underlying)
470505
case AndType(tp1, tp2) =>
471-
erasedGlb(this(tp1), this(tp2), isJava)
506+
erasedGlb(this(tp1), this(tp2), sourceLanguage.isJava)
472507
case OrType(tp1, tp2) =>
473508
TypeComparer.orType(this(tp1), this(tp2), isErased = true)
474509
case tp: MethodType =>
475510
def paramErasure(tpToErase: Type) =
476-
erasureFn(isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
511+
erasureFn(sourceLanguage, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
477512
val (names, formals0) = if (tp.isErasedMethod) (Nil, Nil) else (tp.paramNames, tp.paramInfos)
478513
val formals = formals0.mapConserve(paramErasure)
479514
eraseResult(tp.resultType) match {
@@ -516,8 +551,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
516551
private def eraseArray(tp: Type)(using Context) = {
517552
val defn.ArrayOf(elemtp) = tp
518553
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))
554+
else if (isUnboundedGeneric(elemtp) && !sourceLanguage.isJava) defn.ObjectType
555+
else JavaArrayType(erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, wildcardOK)(elemtp))
521556
}
522557

523558
private def erasePair(tp: Type)(using Context): Type = {
@@ -544,7 +579,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
544579
// See doc comment for ElimByName for speculation how we could improve this.
545580
else
546581
MethodType(Nil, Nil,
547-
eraseResult(sym.info.finalResultType.translateFromRepeated(toArray = isJava)))
582+
eraseResult(sym.info.finalResultType.translateFromRepeated(toArray = sourceLanguage.isJava)))
548583
case tp1: PolyType =>
549584
eraseResult(tp1.resultType) match
550585
case rt: MethodType => rt
@@ -596,7 +631,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
596631
// correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the result type of a
597632
// constructor method should not be semi-erased.
598633
if semiEraseVCs && isConstructor && !tp.isInstanceOf[MethodOrPoly] then
599-
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK).eraseResult(tp)
634+
erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, wildcardOK).eraseResult(tp)
600635
else tp match
601636
case tp: TypeRef =>
602637
val sym = tp.symbol

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

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3345,6 +3345,8 @@ object Types {
33453345
private var mySignatureRunId: Int = NoRunId
33463346
private var myJavaSignature: Signature = _
33473347
private var myJavaSignatureRunId: Int = NoRunId
3348+
private var myScala2Signature: Signature = _
3349+
private var myScala2SignatureRunId: Int = NoRunId
33483350

33493351
/** If `isJava` is false, the Scala signature of this method. Otherwise, its Java signature.
33503352
*
@@ -3360,39 +3362,45 @@ object Types {
33603362
*
33613363
* @see SingleDenotation#signature
33623364
*/
3363-
def signature(isJava: Boolean)(using Context): Signature =
3364-
def computeSignature(isJava: Boolean)(using Context): Signature =
3365+
def signature(sourceLanguage: SourceLanguage)(using Context): Signature =
3366+
def computeSignature(using Context): Signature =
33653367
val resultSignature = resultType match
3366-
case tp: MethodOrPoly => tp.signature(isJava)
3368+
case tp: MethodOrPoly => tp.signature(sourceLanguage)
33673369
case tp: ExprType => tp.signature
33683370
case tp =>
33693371
if tp.isRef(defn.UnitClass) then Signature(Nil, defn.UnitClass.fullName.asTypeName)
3370-
else Signature(tp, isJava)
3372+
else Signature(tp, sourceLanguage)
33713373
this match
33723374
case tp: MethodType =>
33733375
val params = if (isErasedMethod) Nil else tp.paramInfos
3374-
resultSignature.prependTermParams(params, isJava)
3376+
resultSignature.prependTermParams(params, sourceLanguage)
33753377
case tp: PolyType =>
33763378
resultSignature.prependTypeParams(tp.paramNames.length)
33773379

3378-
if isJava then
3379-
if ctx.runId != myJavaSignatureRunId then
3380-
myJavaSignature = computeSignature(isJava)
3381-
if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
3382-
myJavaSignature
3383-
else
3384-
if ctx.runId != mySignatureRunId then
3385-
mySignature = computeSignature(isJava)
3386-
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
3387-
mySignature
3380+
sourceLanguage match
3381+
case SourceLanguage.Java =>
3382+
if ctx.runId != myJavaSignatureRunId then
3383+
myJavaSignature = computeSignature
3384+
if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
3385+
myJavaSignature
3386+
case SourceLanguage.Scala2 =>
3387+
if ctx.runId != myScala2SignatureRunId then
3388+
myScala2Signature = computeSignature
3389+
if !myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId
3390+
myScala2Signature
3391+
case SourceLanguage.Scala3 =>
3392+
if ctx.runId != mySignatureRunId then
3393+
mySignature = computeSignature
3394+
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
3395+
mySignature
33883396
end signature
33893397

33903398
/** The Scala signature of this method. Note that two distinct Java method
33913399
* overloads may have the same Scala signature, the other overload of
33923400
* `signature` can be used to avoid ambiguity if necessary.
33933401
*/
33943402
final override def signature(using Context): Signature =
3395-
signature(isJava = false)
3403+
signature(sourceLanguage = SourceLanguage.Scala3)
33963404

33973405
final override def hashCode: Int = System.identityHashCode(this)
33983406

0 commit comments

Comments
 (0)