Skip to content

Commit 3900796

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 0bbec83 commit 3900796

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
@@ -2261,7 +2261,7 @@ object Types {
22612261

22622262
if ctx.runId != mySignatureRunId then
22632263
mySignature = computeSignature
2264-
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
2264+
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
22652265
mySignature
22662266
end signature
22672267

@@ -3763,17 +3763,17 @@ object Types {
37633763
case SourceLanguage.Java =>
37643764
if ctx.runId != myJavaSignatureRunId then
37653765
myJavaSignature = computeSignature
3766-
if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
3766+
if !myJavaSignature.isUnderDefined && !isProvisional then myJavaSignatureRunId = ctx.runId
37673767
myJavaSignature
37683768
case SourceLanguage.Scala2 =>
37693769
if ctx.runId != myScala2SignatureRunId then
37703770
myScala2Signature = computeSignature
3771-
if !myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId
3771+
if !myScala2Signature.isUnderDefined && !isProvisional then myScala2SignatureRunId = ctx.runId
37723772
myScala2Signature
37733773
case SourceLanguage.Scala3 =>
37743774
if ctx.runId != mySignatureRunId then
37753775
mySignature = computeSignature
3776-
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
3776+
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
37773777
mySignature
37783778
end signature
37793779

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)