@@ -106,51 +106,132 @@ object Types extends TypeUtils {
106106// nextId
107107// }
108108
109- /** A cache indicating whether the type was still provisional, last time we checked */
110- @ sharable private var mightBeProvisional = true
109+ type ProvisionalState = util.HashMap [Type , Type ]
111110
112- /** Is this type still provisional? This is the case if the type contains, or depends on,
113- * uninstantiated type variables or type symbols that have the Provisional flag set.
114- * This is an antimonotonic property - once a type is not provisional, it stays so forever.
115- *
116- * FIXME: The semantics of this flag are broken by the existence of `TypeVar#resetInst`,
117- * a non-provisional type could go back to being provisional after
118- * a call to `resetInst`. This means all caches that rely on `isProvisional`
119- * can likely end up returning stale results.
120- */
121- def isProvisional (using Context ): Boolean = mightBeProvisional && testProvisional
122-
123- private def testProvisional (using Context ): Boolean =
111+ def currentProvisionalState (using Context ): ProvisionalState =
112+ val state : ProvisionalState = util.HashMap ()
113+ // Compared to `testProvisional`, we don't use short-circuiting or,
114+ // because we want to collect all provisional types.
124115 class ProAcc extends TypeAccumulator [Boolean ]:
125- override def apply (x : Boolean , t : Type ) = x || test(t, this )
116+ override def apply (x : Boolean , t : Type ) = x | test(t, this )
126117 def test (t : Type , theAcc : TypeAccumulator [Boolean ] | Null ): Boolean =
127118 if t.mightBeProvisional then
128119 t.mightBeProvisional = t match
129120 case t : TypeRef =>
130- t.currentSymbol.isProvisional || ! t.currentSymbol.isStatic && {
121+ if t.currentSymbol.isProvisional then
122+ // When t is a TypeRef and its symbol is provisional,
123+ // t will be considered provisional and its state is always updating.
124+ state(t) = t
125+ true
126+ else if ! t.currentSymbol.isStatic then
131127 (t : Type ).mightBeProvisional = false // break cycles
132- test(t.prefix, theAcc)
133- || t.denot.infoOrCompleter.match
134- case info : LazyType => true
135- case info : AliasingBounds => test(info.alias, theAcc)
136- case TypeBounds (lo, hi) => test(lo, theAcc) || test(hi, theAcc)
137- case _ => false
138- }
128+ if test(t.prefix, theAcc) then
129+ // If the prefix is provisional, some provisional type from it
130+ // must have been added to state, so we don't need to add t.
131+ true
132+ else t.denot.infoOrCompleter.match
133+ case info : LazyType =>
134+ state(t) = info
135+ true
136+ case info : AliasingBounds =>
137+ test(info.alias, theAcc)
138+ case TypeBounds (lo, hi) =>
139+ test(lo, theAcc) | test(hi, theAcc)
140+ case _ =>
141+ // If a TypeRef has been fully completed, it is no longer provisional,
142+ // so we don't need to traverse its info.
143+ false
144+ else false
139145 case t : TermRef =>
140146 ! t.currentSymbol.isStatic && test(t.prefix, theAcc)
141147 case t : AppliedType =>
142- t.fold(false , (x, tp) => x || test(tp, theAcc))
148+ t.fold(false , (x, tp) => x | test(tp, theAcc))
143149 case t : TypeVar =>
144- ! t.isPermanentlyInstantiated || test(t.permanentInst, theAcc)
150+ if t.isPermanentlyInstantiated then
151+ test(t.permanentInst, theAcc)
152+ else
153+ val inst = t.instanceOpt
154+ if inst.exists then
155+ // We want to store the temporary instance to the state
156+ // in order to reuse the cache when possible.
157+ state(t) = inst
158+ test(inst, theAcc)
159+ else state(t) = t
160+ true
145161 case t : LazyRef =>
146- ! t.completed || test(t.ref, theAcc)
162+ if ! t.completed then
163+ state(t) = t
164+ true
165+ else
166+ test(t.ref, theAcc)
147167 case _ =>
148168 (if theAcc != null then theAcc else ProAcc ()).foldOver(false , t)
149169 end if
150170 t.mightBeProvisional
151171 end test
152172 test(this , null )
153- end testProvisional
173+ state
174+ end currentProvisionalState
175+
176+ def isCacheUpToDate (
177+ currentState : ProvisionalState ,
178+ lastState : ProvisionalState | Null )
179+ (using Context ): Boolean =
180+ lastState != null
181+ && currentState.size == lastState.size
182+ && currentState.iterator.forall: (tp, info) =>
183+ lastState.contains(tp) && {
184+ tp match
185+ case tp : TypeRef => (info ne tp) && (info eq lastState(tp))
186+ case _=> info eq lastState(tp)
187+ }
188+
189+ /** A cache indicating whether the type was still provisional, last time we checked */
190+ @ sharable private var mightBeProvisional = true
191+
192+ /** Is this type still provisional? This is the case if the type contains, or depends on,
193+ * uninstantiated type variables or type symbols that have the Provisional flag set.
194+ * This is an antimonotonic property - once a type is not provisional, it stays so forever.
195+ *
196+ * FIXME: The semantics of this flag are broken by the existence of `TypeVar#resetInst`,
197+ * a non-provisional type could go back to being provisional after
198+ * a call to `resetInst`. This means all caches that rely on `isProvisional`
199+ * can likely end up returning stale results.
200+ */
201+ // def isProvisional(using Context): Boolean = mightBeProvisional && testProvisional
202+ def isProvisional (using Context ): Boolean = mightBeProvisional && ! currentProvisionalState.isEmpty
203+
204+ // private def testProvisional(using Context): Boolean =
205+ // class ProAcc extends TypeAccumulator[Boolean]:
206+ // override def apply(x: Boolean, t: Type) = x || test(t, this)
207+ // def test(t: Type, theAcc: TypeAccumulator[Boolean] | Null): Boolean =
208+ // if t.mightBeProvisional then
209+ // t.mightBeProvisional = t match
210+ // case t: TypeRef =>
211+ // t.currentSymbol.isProvisional || !t.currentSymbol.isStatic && {
212+ // (t: Type).mightBeProvisional = false // break cycles
213+ // test(t.prefix, theAcc)
214+ // || t.denot.infoOrCompleter.match
215+ // case info: LazyType => true
216+ // case info: AliasingBounds => test(info.alias, theAcc)
217+ // case TypeBounds(lo, hi) => test(lo, theAcc) || test(hi, theAcc)
218+ // case _ => false
219+ // }
220+ // case t: TermRef =>
221+ // !t.currentSymbol.isStatic && test(t.prefix, theAcc)
222+ // case t: AppliedType =>
223+ // t.fold(false, (x, tp) => x || test(tp, theAcc))
224+ // case t: TypeVar =>
225+ // !t.isPermanentlyInstantiated || test(t.permanentInst, theAcc)
226+ // case t: LazyRef =>
227+ // !t.completed || test(t.ref, theAcc)
228+ // case _ =>
229+ // (if theAcc != null then theAcc else ProAcc()).foldOver(false, t)
230+ // end if
231+ // t.mightBeProvisional
232+ // end test
233+ // test(this, null)
234+ // end testProvisional
154235
155236 /** Is this type different from NoType? */
156237 final def exists : Boolean = this .ne(NoType )
@@ -2306,10 +2387,12 @@ object Types extends TypeUtils {
23062387
23072388 private var myName : Name | Null = null
23082389 private var lastDenotation : Denotation | Null = null
2390+ private var lastDenotationProvState : ProvisionalState | Null = null
23092391 private var lastSymbol : Symbol | Null = null
23102392 private var checkedPeriod : Period = Nowhere
23112393 private var myStableHash : Byte = 0
23122394 private var mySignature : Signature = uninitialized
2395+ private var mySignatureProvState : ProvisionalState | Null = null
23132396 private var mySignatureRunId : Int = NoRunId
23142397
23152398 // Invariants:
@@ -2344,9 +2427,11 @@ object Types extends TypeUtils {
23442427 else if ctx.erasedTypes then atPhase(erasurePhase)(computeSignature)
23452428 else symbol.asSeenFrom(prefix).signature
23462429
2347- if ctx.runId != mySignatureRunId then
2430+ if ctx.runId != mySignatureRunId
2431+ || ! isCacheUpToDate(currentProvisionalState, mySignatureProvState) then
23482432 mySignature = computeSignature
2349- if ! mySignature.isUnderDefined && ! isProvisional then mySignatureRunId = ctx.runId
2433+ mySignatureProvState = currentProvisionalState
2434+ if ! mySignature.isUnderDefined then mySignatureRunId = ctx.runId
23502435 mySignature
23512436 end signature
23522437
@@ -2357,7 +2442,9 @@ object Types extends TypeUtils {
23572442 * some symbols change their signature at erasure.
23582443 */
23592444 private def currentSignature (using Context ): Signature =
2360- if ctx.runId == mySignatureRunId then mySignature
2445+ if ctx.runId == mySignatureRunId
2446+ && isCacheUpToDate(currentProvisionalState, mySignatureProvState)
2447+ then mySignature
23612448 else
23622449 val lastd = lastDenotation
23632450 if lastd != null then sigFromDenot(lastd)
@@ -2435,7 +2522,10 @@ object Types extends TypeUtils {
24352522 val lastd = lastDenotation.asInstanceOf [Denotation ]
24362523 // Even if checkedPeriod == now we still need to recheck lastDenotation.validFor
24372524 // as it may have been mutated by SymDenotation#installAfter
2438- if checkedPeriod.code != NowhereCode && lastd.validFor.contains(ctx.period) then lastd
2525+ if checkedPeriod.code != NowhereCode
2526+ && isCacheUpToDate(prefix.currentProvisionalState, lastDenotationProvState)
2527+ && lastd.validFor.contains(ctx.period)
2528+ then lastd
24392529 else computeDenot
24402530
24412531 private def computeDenot (using Context ): Denotation = {
@@ -2469,14 +2559,18 @@ object Types extends TypeUtils {
24692559 finish(symd.current)
24702560 }
24712561
2562+ def isLastDenotValid =
2563+ checkedPeriod.code != NowhereCode
2564+ && isCacheUpToDate(prefix.currentProvisionalState, lastDenotationProvState)
2565+
24722566 lastDenotation match {
24732567 case lastd0 : SingleDenotation =>
24742568 val lastd = lastd0.skipRemoved
2475- if lastd.validFor.runId == ctx.runId && checkedPeriod.code != NowhereCode then
2569+ if lastd.validFor.runId == ctx.runId && isLastDenotValid then
24762570 finish(lastd.current)
24772571 else lastd match {
24782572 case lastd : SymDenotation =>
2479- if stillValid(lastd) && checkedPeriod.code != NowhereCode then finish(lastd.current)
2573+ if stillValid(lastd) && isLastDenotValid then finish(lastd.current)
24802574 else finish(memberDenot(lastd.initial.name, allowPrivate = false ))
24812575 case _ =>
24822576 fromDesignator
@@ -2567,7 +2661,8 @@ object Types extends TypeUtils {
25672661
25682662 lastDenotation = denot
25692663 lastSymbol = denot.symbol
2570- checkedPeriod = if (prefix.isProvisional) Nowhere else ctx.period
2664+ lastDenotationProvState = prefix.currentProvisionalState
2665+ checkedPeriod = ctx.period
25712666 designator match {
25722667 case sym : Symbol if designator ne lastSymbol.nn =>
25732668 designator = lastSymbol.asInstanceOf [Designator { type ThisName = self.ThisName }]
@@ -3850,14 +3945,18 @@ object Types extends TypeUtils {
38503945 sealed abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType {
38513946
38523947 // Invariants:
3853- // (1) mySignatureRunId != NoRunId => mySignature != null
3854- // (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
3948+ // (1) mySignatureRunId != NoRunId => mySignature != null
3949+ // (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
3950+ // (3) myScala2SignatureRunId != NoRunId => myScala2Signature != null
38553951
38563952 private var mySignature : Signature = uninitialized
3953+ private var mySignatureProvState : ProvisionalState | Null = null
38573954 private var mySignatureRunId : Int = NoRunId
38583955 private var myJavaSignature : Signature = uninitialized
3956+ private var myJavaSignatureProvState : ProvisionalState | Null = null
38593957 private var myJavaSignatureRunId : Int = NoRunId
38603958 private var myScala2Signature : Signature = uninitialized
3959+ private var myScala2SignatureProvState : ProvisionalState | Null = null
38613960 private var myScala2SignatureRunId : Int = NoRunId
38623961
38633962 /** If `isJava` is false, the Scala signature of this method. Otherwise, its Java signature.
@@ -3895,19 +3994,25 @@ object Types extends TypeUtils {
38953994
38963995 sourceLanguage match
38973996 case SourceLanguage .Java =>
3898- if ctx.runId != myJavaSignatureRunId then
3997+ if ctx.runId != myJavaSignatureRunId
3998+ || ! isCacheUpToDate(currentProvisionalState, myJavaSignatureProvState) then
38993999 myJavaSignature = computeSignature
3900- if ! myJavaSignature.isUnderDefined && ! isProvisional then myJavaSignatureRunId = ctx.runId
4000+ myJavaSignatureProvState = currentProvisionalState
4001+ if ! myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
39014002 myJavaSignature
39024003 case SourceLanguage .Scala2 =>
3903- if ctx.runId != myScala2SignatureRunId then
4004+ if ctx.runId != myScala2SignatureRunId
4005+ || ! isCacheUpToDate(currentProvisionalState, myScala2SignatureProvState) then
39044006 myScala2Signature = computeSignature
3905- if ! myScala2Signature.isUnderDefined && ! isProvisional then myScala2SignatureRunId = ctx.runId
4007+ myScala2SignatureProvState = currentProvisionalState
4008+ if ! myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId
39064009 myScala2Signature
39074010 case SourceLanguage .Scala3 =>
3908- if ctx.runId != mySignatureRunId then
4011+ if ctx.runId != mySignatureRunId
4012+ || ! isCacheUpToDate(currentProvisionalState, mySignatureProvState) then
39094013 mySignature = computeSignature
3910- if ! mySignature.isUnderDefined && ! isProvisional then mySignatureRunId = ctx.runId
4014+ mySignatureProvState = currentProvisionalState
4015+ if ! mySignature.isUnderDefined then mySignatureRunId = ctx.runId
39114016 mySignature
39124017 end signature
39134018
0 commit comments