Skip to content

Commit 2975434

Browse files
authored
Merge pull request #8819 from dotty-staging/fix-#8799-v2
Fix #8799: Make sure signatures are computed before erasure
2 parents 5358651 + 1818b99 commit 2975434

File tree

8 files changed

+102
-54
lines changed

8 files changed

+102
-54
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ class Compiler {
6161
new CheckStatic, // Check restrictions that apply to @static members
6262
new BetaReduce, // Reduce closure applications
6363
new init.Checker) :: // Check initialization of objects
64-
List(new CompleteJavaEnums, // Fill in constructors for Java enums
65-
new ElimRepeated, // Rewrite vararg parameters and arguments
64+
List(new ElimRepeated, // Rewrite vararg parameters and arguments
6665
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
6766
new ProtectedAccessors, // Add accessors for protected members
6867
new ExtensionMethods, // Expand methods of value classes with extension methods
@@ -101,6 +100,7 @@ class Compiler {
101100
new ArrayApply, // Optimize `scala.Array.apply([....])` and `scala.Array.apply(..., [....])` into `[...]`
102101
new ElimPolyFunction, // Rewrite PolyFunction subclasses to FunctionN subclasses
103102
new TailRec, // Rewrite tail recursion to loops
103+
new CompleteJavaEnums, // Fill in constructors for Java enums
104104
new Mixin, // Expand trait fields and trait initializers
105105
new LazyVals, // Expand lazy vals
106106
new Memoize, // Add private fields to getters and setters

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ object Signature {
141141
export MatchDegree._
142142

143143
/** The signature of everything that's not a method, i.e. that has
144-
* a type different from PolyType, MethodType, or ExprType.
144+
* a type different from PolyType or MethodType.
145145
*/
146146
val NotAMethod: Signature = Signature(List(), EmptyTypeName)
147147

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

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1896,30 +1896,39 @@ object Types {
18961896
case sym: Symbol => sym.originDenotation.name
18971897
}
18981898

1899-
/** The signature of the last known denotation, or if there is none, the
1900-
* signature of the symbol
1899+
/** The signature computed from the last known denotation with `sigFromDenot`,
1900+
* or if there is none, the signature of the symbol. Signatures are always
1901+
* computed before erasure, since some symbols change their signature at erasure.
19011902
*/
1902-
protected def computeSignature(implicit ctx: Context): Signature = {
1903+
protected def computeSignature(implicit ctx: Context): Signature =
19031904
val lastd = lastDenotation
1904-
if (lastd != null) lastd.signature
1905+
if lastd != null then sigFromDenot(lastd)
1906+
else if ctx.erasedTypes then computeSignature(using ctx.withPhase(ctx.erasurePhase))
19051907
else symbol.asSeenFrom(prefix).signature
1906-
}
19071908

1908-
/** The signature of the current denotation if it is known without forcing.
1909+
/** The signature computed from the current denotation with `sigFromDenot` if it is
1910+
* known without forcing.
19091911
* Otherwise the signature of the current symbol if it is known without forcing.
1910-
* Otherwise NotAMethod.
1912+
* Otherwise NotAMethod. Signatures are always computed before erasure, since
1913+
* some symbols change their signature at erasure.
19111914
*/
19121915
private def currentSignature(implicit ctx: Context): Signature =
1913-
if (ctx.runId == mySignatureRunId) mySignature
1914-
else {
1916+
if ctx.runId == mySignatureRunId then mySignature
1917+
else
19151918
val lastd = lastDenotation
1916-
if (lastd != null) lastd.signature
1917-
else {
1919+
if lastd != null then sigFromDenot(lastd)
1920+
else if ctx.erasedTypes then currentSignature(using ctx.withPhase(ctx.erasurePhase))
1921+
else
19181922
val sym = currentSymbol
1919-
if (sym.exists) sym.asSeenFrom(prefix).signature
1923+
if sym.exists then sym.asSeenFrom(prefix).signature
19201924
else Signature.NotAMethod
1921-
}
1922-
}
1925+
1926+
/** The signature of a pre-erasure version of denotation `lastd`. */
1927+
private def sigFromDenot(lastd: Denotation)(using Context) =
1928+
if lastd.validFor.firstPhaseId <= ctx.erasurePhase.id then lastd.signature
1929+
else lastd match
1930+
case lastd: SingleDenotation => lastd.initial.signature
1931+
case _ => Signature.OverloadedSignature
19231932

19241933
final def symbol(implicit ctx: Context): Symbol =
19251934
// We can rely on checkedPeriod (unlike in the definition of `denot` below)
@@ -3066,7 +3075,7 @@ object Types {
30663075
}
30673076
}
30683077

3069-
trait MethodicType extends SignatureCachingType {
3078+
trait MethodicType extends TermType {
30703079
protected def resultSignature(implicit ctx: Context): Signature = try resultType match {
30713080
case rtp: MethodicType => rtp.signature
30723081
case tp =>
@@ -3086,7 +3095,7 @@ object Types {
30863095
override def resultType(implicit ctx: Context): Type = resType
30873096
override def underlying(implicit ctx: Context): Type = resType
30883097

3089-
def computeSignature(implicit ctx: Context): Signature = resultSignature
3098+
override def signature(implicit ctx: Context): Signature = Signature.NotAMethod
30903099

30913100
def derivedExprType(resType: Type)(implicit ctx: Context): ExprType =
30923101
if (resType eq this.resType) this else ExprType(resType)
@@ -3192,7 +3201,7 @@ object Types {
31923201
final override def equals(that: Any): Boolean = equals(that, null)
31933202
}
31943203

3195-
abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType {
3204+
abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType with SignatureCachingType {
31963205
final override def hashCode: Int = System.identityHashCode(this)
31973206

31983207
final override def equals(that: Any): Boolean = equals(that, null)

compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -77,38 +77,14 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
7777
case p => p
7878
}
7979

80-
/** 1. If this is a constructor of a enum class that extends, add $name and $ordinal parameters to it.
81-
*
82-
* 2. If this is a $new method that creates simple cases, pass $name and $ordinal parameters
83-
* to the enum superclass. The $new method looks like this:
84-
*
85-
* def $new(..., ordinal: Int, name: String) = {
86-
* class $anon extends E(...) { ... }
87-
* new $anon
88-
* }
89-
*
90-
* After the transform it is expanded to
91-
*
92-
* def $new(..., ordinal: Int, name: String) = {
93-
* class $anon extends E(..., name, ordinal) { ... }
94-
* new $anon
95-
* }
96-
*/
80+
/** If this is a constructor of a enum class that extends, add $name and $ordinal parameters to it. */
9781
override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = {
9882
val sym = tree.symbol
9983
if (sym.isConstructor && sym.owner.derivesFromJavaEnum)
10084
val tree1 = cpy.DefDef(tree)(
10185
vparamss = tree.vparamss.init :+ (tree.vparamss.last ++ addedParams(sym, Param)))
10286
sym.setParamssFromDefs(tree1.tparams, tree1.vparamss)
10387
tree1
104-
else if (sym.name == nme.DOLLAR_NEW && sym.owner.linkedClass.derivesFromJavaEnum) {
105-
val Block((tdef @ TypeDef(tpnme.ANON_CLASS, templ: Template)) :: Nil, call) = tree.rhs
106-
val args = tree.vparamss.last.takeRight(2).map(param => ref(param.symbol)).reverse
107-
val templ1 = cpy.Template(templ)(
108-
parents = addEnumConstrArgs(sym.owner.linkedClass, templ.parents, args))
109-
cpy.DefDef(tree)(
110-
rhs = cpy.Block(tree.rhs)(cpy.TypeDef(tdef)(tdef.name, templ1) :: Nil, call))
111-
}
11288
else tree
11389
}
11490

@@ -159,7 +135,8 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
159135
parents = addEnumConstrArgs(defn.JavaEnumClass, templ.parents, addedSyms.map(ref)),
160136
body = params ++ addedDefs ++ addedForwarders ++ rest)
161137
}
162-
else if (cls.isAnonymousClass && cls.owner.isAllOf(EnumCase) && cls.owner.owner.linkedClass.derivesFromJavaEnum) {
138+
else if (cls.isAnonymousClass && ((cls.owner.name eq nme.DOLLAR_NEW) || cls.owner.isAllOf(EnumCase)) &&
139+
cls.owner.owner.linkedClass.derivesFromJavaEnum) {
163140
def rhsOf(name: TermName) =
164141
templ.body.collect {
165142
case mdef: DefDef if mdef.name == name => mdef.rhs

compiler/src/dotty/tools/dotc/transform/Mixin.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase =>
112112
override def relaxedTypingInGroup: Boolean = true
113113
// Because it changes number of parameters in trait initializers
114114

115-
override def runsAfter: Set[String] = Set(Erasure.name)
115+
override def runsAfter: Set[String] = Set(
116+
Erasure.name,
117+
CompleteJavaEnums.name // This phase changes constructor parameters which Mixin translates into super-calls
118+
)
116119

117120
override def changesMembers: Boolean = true // the phase adds implementions of mixin accessors
118121

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ class TreeChecker extends Phase with SymTransformer {
8787

8888
checkCompanion(symd)
8989

90+
// Signatures are used to disambiguate overloads and need to stay stable
91+
// until erasure, see the comment above `Compiler#phases`.
92+
if (ctx.phaseId <= ctx.erasurePhase.id) {
93+
val cur = symd.info
94+
val initial = symd.initial.info
95+
assert(cur.signature == initial.signature,
96+
i"""Signature of $symd changed at phase ${ctx.phase}
97+
|Initial info: ${initial}
98+
|Initial sig : ${initial.signature}
99+
|Current info: ${cur}
100+
|Current sig : ${cur.signature}""")
101+
}
102+
90103
symd
91104
}
92105

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dotty.tools
2+
3+
import vulpix.TestConfiguration
4+
5+
import org.junit.Test
6+
7+
import dotc.ast.Trees._
8+
import dotc.core.Decorators._
9+
import dotc.core.Contexts._
10+
import dotc.core.Types._
11+
12+
import java.io.File
13+
import java.nio.file._
14+
15+
class SignatureTest:
16+
@Test def signatureCaching: Unit =
17+
inCompilerContext(TestConfiguration.basicClasspath, separateRun = true, "case class Foo(value: Unit)") {
18+
val (ref, refSig) = ctx.atPhase(ctx.erasurePhase.next) {
19+
val cls = ctx.requiredClass("Foo")
20+
val ref = cls.requiredMethod("value").termRef
21+
(ref, ref.signature)
22+
}
23+
ctx.atPhase(ctx.typerPhase) {
24+
// NamedType#signature is always computed before erasure, which ensures
25+
// that it stays stable and therefore can be cached as long as
26+
// signatures are guaranteed to be stable before erasure, see the
27+
// comment above `Compiler#phases`.
28+
assert(refSig == ref.signature,
29+
s"""The signature of a type should never change but the signature of $ref was:
30+
|${ref.signature} at typer, whereas it was:
31+
|${refSig} after erasure""".stripMargin)
32+
assert(ref.signature == ref.denot.signature,
33+
s"""Before erasure, the signature of a TypeRef should be the signature of its denotation,
34+
|but the cached signature of $ref was:
35+
|${ref.signature}, whereas its denotation signature at typer was:
36+
|${ref.denot.signature}""".stripMargin)
37+
}
38+
}

compiler/test/dotty/tools/compilerSupport.scala

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package dotty.tools
22

33
import javax.tools._
4-
import java.io.File
4+
import java.io._
55
import java.nio.file._
66
import java.net.URI
77
import scala.jdk.CollectionConverters._
@@ -10,14 +10,22 @@ import core._
1010
import core.Contexts._
1111
import dotc.core.Comments.{ContextDoc, ContextDocstrings}
1212

13-
/** Initialize a compiler context with the given classpath and
14-
* pass it to `op`.
13+
/** Initialize a compiler context with the given `classpath`, compile all
14+
* `scalaSources`, then run `op`.
15+
*
16+
* If `separateRun` is true , then `op` will be run in a new run different from the
17+
* one used to compile the sources, this makes it easier to test for potential
18+
* issues involving retrieving symbols defined in a previous run.
1519
*/
16-
def inCompilerContext[T](classpath: String)(op: Context ?=> T): T =
20+
def inCompilerContext[T](classpath: String, separateRun: Boolean = true, scalaSources: String*)(op: Context ?=> T): T =
1721
val compiler = Compiler()
18-
val run = compiler.newRun(initCtx(classpath))
19-
run.compileUnits(Nil) // Initialize phases
20-
op(using run.runContext)
22+
val rootCtx = initCtx(classpath)
23+
val firstRun = compiler.newRun(rootCtx)
24+
firstRun.compileFromStrings(scalaSources.toList)
25+
val opRun = if separateRun
26+
then compiler.newRun(rootCtx)
27+
else firstRun
28+
op(using opRun.runContext)
2129

2230
private def initCtx(classpath: String): Context =
2331
val ctx0 = (new ContextBase).initialCtx.fresh

0 commit comments

Comments
 (0)