Skip to content

Commit 49af5b8

Browse files
committed
Make sure Getters doesn't change signatures
Getters will replace `val x: Int`, by `def x: Int`, before this commit, this lead to the signature of `x` changing from `Signature.NotAMethod` to `Signature(Nil, scala.Int)` but we need signatures to be stable pre-erasure to disambiguate overloads. This commit fixes this by changing ExprType#signature to always return `NotAMethod`, this should not break anything since we do not allow overloads which only differ in their result type anyway. This commit also adds tests to make sure that the invariants related to signatures are not violated, currently there is one class of failure related to enums which is fixed in the next commit.
1 parent 0e6d670 commit 49af5b8

File tree

5 files changed

+70
-11
lines changed

5 files changed

+70
-11
lines changed

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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3075,7 +3075,7 @@ object Types {
30753075
}
30763076
}
30773077

3078-
trait MethodicType extends SignatureCachingType {
3078+
trait MethodicType extends TermType {
30793079
protected def resultSignature(implicit ctx: Context): Signature = try resultType match {
30803080
case rtp: MethodicType => rtp.signature
30813081
case tp =>
@@ -3095,7 +3095,7 @@ object Types {
30953095
override def resultType(implicit ctx: Context): Type = resType
30963096
override def underlying(implicit ctx: Context): Type = resType
30973097

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

31003100
def derivedExprType(resType: Type)(implicit ctx: Context): ExprType =
31013101
if (resType eq this.resType) this else ExprType(resType)
@@ -3201,7 +3201,7 @@ object Types {
32013201
final override def equals(that: Any): Boolean = equals(that, null)
32023202
}
32033203

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

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

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)