Skip to content

Commit 3729960

Browse files
committed
Fix signature caching involving type variables
Signatures should not be cached if they may change. The existing `isUnderDefined` check is not enough to guarantee this because the signature might have been computed based on a type variable instantiated in a TyperState which was subsequently retracted. To guard against this, we now rely on `Type#isProvisional`. This is a stricter check than needed since the provisional part of the type does not always have an impact on its signature, but in practice this is fine: when compiling Dotty, signature cache misses increased by less than 2%.
1 parent c21133f commit 3729960

File tree

2 files changed

+41
-4
lines changed

2 files changed

+41
-4
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/Types.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -2272,7 +2272,7 @@ object Types {
22722272

22732273
if ctx.runId != mySignatureRunId then
22742274
mySignature = computeSignature
2275-
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
2275+
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
22762276
mySignature
22772277
end signature
22782278

@@ -3784,17 +3784,17 @@ object Types {
37843784
case SourceLanguage.Java =>
37853785
if ctx.runId != myJavaSignatureRunId then
37863786
myJavaSignature = computeSignature
3787-
if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
3787+
if !myJavaSignature.isUnderDefined && !isProvisional then myJavaSignatureRunId = ctx.runId
37883788
myJavaSignature
37893789
case SourceLanguage.Scala2 =>
37903790
if ctx.runId != myScala2SignatureRunId then
37913791
myScala2Signature = computeSignature
3792-
if !myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId
3792+
if !myScala2Signature.isUnderDefined && !isProvisional then myScala2SignatureRunId = ctx.runId
37933793
myScala2Signature
37943794
case SourceLanguage.Scala3 =>
37953795
if ctx.runId != mySignatureRunId then
37963796
mySignature = computeSignature
3797-
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
3797+
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
37983798
mySignature
37993799
end signature
38003800

Diff for: compiler/test/dotty/tools/SignatureTest.scala

+37
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package dotty.tools
22

33
import vulpix.TestConfiguration
44

5+
import org.junit.Assert._
56
import org.junit.Test
67

78
import dotc.ast.untpd
89
import dotc.core.Decorators._
910
import dotc.core.Contexts._
1011
import dotc.core.Flags._
1112
import dotc.core.Phases._
13+
import dotc.core.Names._
1214
import dotc.core.Types._
1315
import dotc.core.Symbols._
1416
import dotc.core.StdNames._
@@ -76,3 +78,38 @@ class SignatureTest:
7678
assert(isFullyDefined(tvar, force = ForceDegree.all), s"Could not instantiate $tvar")
7779
checkSignatures(expectedIsUnderDefined = false)
7880

81+
/** Check that signature caching behaves correctly with respect to retracted
82+
* instantiations of type variables.
83+
*/
84+
@Test def cachingWithRetraction: Unit =
85+
inCompilerContext(TestConfiguration.basicClasspath, separateRun = false,
86+
"""trait Foo
87+
|trait Bar
88+
|class A[T]:
89+
| def and(x: T & Foo): Unit = {}
90+
|""".stripMargin):
91+
val cls = requiredClass("A")
92+
val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda], untpd.EmptyTree, alwaysAddTypeVars = true)._2.head.tpe
93+
val prefix = cls.typeRef.appliedTo(tvar)
94+
val ref = prefix.select(cls.requiredMethod("and")).asInstanceOf[TermRef]
95+
96+
/** Check that the signature of the first parameter of `ref` is equal to `expectedParamSig`. */
97+
def checkParamSig(ref: TermRef, expectedParamSig: TypeName)(using Context): Unit =
98+
assertEquals(i"Check failed for param signature of $ref",
99+
expectedParamSig, ref.signature.paramsSig.head)
100+
// Both NamedType and MethodOrPoly cache signatures, so check both caches.
101+
assertEquals(i"Check failed for param signature of ${ref.info} (but not for $ref itself)",
102+
expectedParamSig, ref.info.signature.paramsSig.head)
103+
104+
105+
// Initially, the param signature is Uninstantiated since it depends on an uninstantiated type variable
106+
checkParamSig(ref, tpnme.Uninstantiated)
107+
108+
// In this context, the signature is the erasure of `Bar & Foo`.
109+
inContext(ctx.fresh.setNewTyperState()):
110+
tvar =:= requiredClass("Bar").typeRef
111+
assert(isFullyDefined(tvar, force = ForceDegree.all), s"Could not instantiate $tvar")
112+
checkParamSig(ref, "Bar".toTypeName)
113+
114+
// If our caching logic is working correctly, we should get the original signature here.
115+
checkParamSig(ref, tpnme.Uninstantiated)

0 commit comments

Comments
 (0)