Skip to content

Commit e38f059

Browse files
committed
Test new provisional state
1 parent 4980430 commit e38f059

File tree

2 files changed

+167
-71
lines changed

2 files changed

+167
-71
lines changed

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

Lines changed: 147 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)