Skip to content

Commit

Permalink
Test new provisional state
Browse files Browse the repository at this point in the history
  • Loading branch information
noti0na1 committed Aug 6, 2024
1 parent 4980430 commit e38f059
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 71 deletions.
189 changes: 147 additions & 42 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,51 +106,132 @@ object Types extends TypeUtils {
// nextId
// }

/** A cache indicating whether the type was still provisional, last time we checked */
@sharable private var mightBeProvisional = true
type ProvisionalState = util.HashMap[Type, Type]

/** Is this type still provisional? This is the case if the type contains, or depends on,
* uninstantiated type variables or type symbols that have the Provisional flag set.
* This is an antimonotonic property - once a type is not provisional, it stays so forever.
*
* FIXME: The semantics of this flag are broken by the existence of `TypeVar#resetInst`,
* a non-provisional type could go back to being provisional after
* a call to `resetInst`. This means all caches that rely on `isProvisional`
* can likely end up returning stale results.
*/
def isProvisional(using Context): Boolean = mightBeProvisional && testProvisional

private def testProvisional(using Context): Boolean =
def currentProvisionalState(using Context): ProvisionalState =
val state: ProvisionalState = util.HashMap()
// Compared to `testProvisional`, we don't use short-circuiting or,
// because we want to collect all provisional types.
class ProAcc extends TypeAccumulator[Boolean]:
override def apply(x: Boolean, t: Type) = x || test(t, this)
override def apply(x: Boolean, t: Type) = x | test(t, this)
def test(t: Type, theAcc: TypeAccumulator[Boolean] | Null): Boolean =
if t.mightBeProvisional then
t.mightBeProvisional = t match
case t: TypeRef =>
t.currentSymbol.isProvisional || !t.currentSymbol.isStatic && {
if t.currentSymbol.isProvisional then
// When t is a TypeRef and its symbol is provisional,
// t will be considered provisional and its state is always updating.
state(t) = t
true
else if !t.currentSymbol.isStatic then
(t: Type).mightBeProvisional = false // break cycles
test(t.prefix, theAcc)
|| t.denot.infoOrCompleter.match
case info: LazyType => true
case info: AliasingBounds => test(info.alias, theAcc)
case TypeBounds(lo, hi) => test(lo, theAcc) || test(hi, theAcc)
case _ => false
}
if test(t.prefix, theAcc) then
// If the prefix is provisional, some provisional type from it
// must have been added to state, so we don't need to add t.
true
else t.denot.infoOrCompleter.match
case info: LazyType =>
state(t) = info
true
case info: AliasingBounds =>
test(info.alias, theAcc)
case TypeBounds(lo, hi) =>
test(lo, theAcc) | test(hi, theAcc)
case _ =>
// If a TypeRef has been fully completed, it is no longer provisional,
// so we don't need to traverse its info.
false
else false
case t: TermRef =>
!t.currentSymbol.isStatic && test(t.prefix, theAcc)
case t: AppliedType =>
t.fold(false, (x, tp) => x || test(tp, theAcc))
t.fold(false, (x, tp) => x | test(tp, theAcc))
case t: TypeVar =>
!t.isPermanentlyInstantiated || test(t.permanentInst, theAcc)
if t.isPermanentlyInstantiated then
test(t.permanentInst, theAcc)
else
val inst = t.instanceOpt
if inst.exists then
// We want to store the temporary instance to the state
// in order to reuse the cache when possible.
state(t) = inst
test(inst, theAcc)
else state(t) = t
true
case t: LazyRef =>
!t.completed || test(t.ref, theAcc)
if !t.completed then
state(t) = t
true
else
test(t.ref, theAcc)
case _ =>
(if theAcc != null then theAcc else ProAcc()).foldOver(false, t)
end if
t.mightBeProvisional
end test
test(this, null)
end testProvisional
state
end currentProvisionalState

def isCacheUpToDate(
currentState: ProvisionalState,
lastState: ProvisionalState | Null)
(using Context): Boolean =
lastState != null
&& currentState.size == lastState.size
&& currentState.iterator.forall: (tp, info) =>
lastState.contains(tp) && {
tp match
case tp: TypeRef => (info ne tp) && (info eq lastState(tp))
case _=> info eq lastState(tp)
}

/** A cache indicating whether the type was still provisional, last time we checked */
@sharable private var mightBeProvisional = true

/** Is this type still provisional? This is the case if the type contains, or depends on,
* uninstantiated type variables or type symbols that have the Provisional flag set.
* This is an antimonotonic property - once a type is not provisional, it stays so forever.
*
* FIXME: The semantics of this flag are broken by the existence of `TypeVar#resetInst`,
* a non-provisional type could go back to being provisional after
* a call to `resetInst`. This means all caches that rely on `isProvisional`
* can likely end up returning stale results.
*/
// def isProvisional(using Context): Boolean = mightBeProvisional && testProvisional
def isProvisional(using Context): Boolean = mightBeProvisional && !currentProvisionalState.isEmpty

// private def testProvisional(using Context): Boolean =
// class ProAcc extends TypeAccumulator[Boolean]:
// override def apply(x: Boolean, t: Type) = x || test(t, this)
// def test(t: Type, theAcc: TypeAccumulator[Boolean] | Null): Boolean =
// if t.mightBeProvisional then
// t.mightBeProvisional = t match
// case t: TypeRef =>
// t.currentSymbol.isProvisional || !t.currentSymbol.isStatic && {
// (t: Type).mightBeProvisional = false // break cycles
// test(t.prefix, theAcc)
// || t.denot.infoOrCompleter.match
// case info: LazyType => true
// case info: AliasingBounds => test(info.alias, theAcc)
// case TypeBounds(lo, hi) => test(lo, theAcc) || test(hi, theAcc)
// case _ => false
// }
// case t: TermRef =>
// !t.currentSymbol.isStatic && test(t.prefix, theAcc)
// case t: AppliedType =>
// t.fold(false, (x, tp) => x || test(tp, theAcc))
// case t: TypeVar =>
// !t.isPermanentlyInstantiated || test(t.permanentInst, theAcc)
// case t: LazyRef =>
// !t.completed || test(t.ref, theAcc)
// case _ =>
// (if theAcc != null then theAcc else ProAcc()).foldOver(false, t)
// end if
// t.mightBeProvisional
// end test
// test(this, null)
// end testProvisional

/** Is this type different from NoType? */
final def exists: Boolean = this.ne(NoType)
Expand Down Expand Up @@ -2306,10 +2387,12 @@ object Types extends TypeUtils {

private var myName: Name | Null = null
private var lastDenotation: Denotation | Null = null
private var lastDenotationProvState: ProvisionalState | Null = null
private var lastSymbol: Symbol | Null = null
private var checkedPeriod: Period = Nowhere
private var myStableHash: Byte = 0
private var mySignature: Signature = uninitialized
private var mySignatureProvState: ProvisionalState | Null = null
private var mySignatureRunId: Int = NoRunId

// Invariants:
Expand Down Expand Up @@ -2344,9 +2427,11 @@ object Types extends TypeUtils {
else if ctx.erasedTypes then atPhase(erasurePhase)(computeSignature)
else symbol.asSeenFrom(prefix).signature

if ctx.runId != mySignatureRunId then
if ctx.runId != mySignatureRunId
|| !isCacheUpToDate(currentProvisionalState, mySignatureProvState) then
mySignature = computeSignature
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
mySignatureProvState = currentProvisionalState
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
mySignature
end signature

Expand All @@ -2357,7 +2442,9 @@ object Types extends TypeUtils {
* some symbols change their signature at erasure.
*/
private def currentSignature(using Context): Signature =
if ctx.runId == mySignatureRunId then mySignature
if ctx.runId == mySignatureRunId
&& isCacheUpToDate(currentProvisionalState, mySignatureProvState)
then mySignature
else
val lastd = lastDenotation
if lastd != null then sigFromDenot(lastd)
Expand Down Expand Up @@ -2435,7 +2522,10 @@ object Types extends TypeUtils {
val lastd = lastDenotation.asInstanceOf[Denotation]
// Even if checkedPeriod == now we still need to recheck lastDenotation.validFor
// as it may have been mutated by SymDenotation#installAfter
if checkedPeriod.code != NowhereCode && lastd.validFor.contains(ctx.period) then lastd
if checkedPeriod.code != NowhereCode
&& isCacheUpToDate(prefix.currentProvisionalState, lastDenotationProvState)
&& lastd.validFor.contains(ctx.period)
then lastd
else computeDenot

private def computeDenot(using Context): Denotation = {
Expand Down Expand Up @@ -2469,14 +2559,18 @@ object Types extends TypeUtils {
finish(symd.current)
}

def isLastDenotValid =
checkedPeriod.code != NowhereCode
&& isCacheUpToDate(prefix.currentProvisionalState, lastDenotationProvState)

lastDenotation match {
case lastd0: SingleDenotation =>
val lastd = lastd0.skipRemoved
if lastd.validFor.runId == ctx.runId && checkedPeriod.code != NowhereCode then
if lastd.validFor.runId == ctx.runId && isLastDenotValid then
finish(lastd.current)
else lastd match {
case lastd: SymDenotation =>
if stillValid(lastd) && checkedPeriod.code != NowhereCode then finish(lastd.current)
if stillValid(lastd) && isLastDenotValid then finish(lastd.current)
else finish(memberDenot(lastd.initial.name, allowPrivate = false))
case _ =>
fromDesignator
Expand Down Expand Up @@ -2567,7 +2661,8 @@ object Types extends TypeUtils {

lastDenotation = denot
lastSymbol = denot.symbol
checkedPeriod = if (prefix.isProvisional) Nowhere else ctx.period
lastDenotationProvState = prefix.currentProvisionalState
checkedPeriod = ctx.period
designator match {
case sym: Symbol if designator ne lastSymbol.nn =>
designator = lastSymbol.asInstanceOf[Designator{ type ThisName = self.ThisName }]
Expand Down Expand Up @@ -3850,14 +3945,18 @@ object Types extends TypeUtils {
sealed abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType {

// Invariants:
// (1) mySignatureRunId != NoRunId => mySignature != null
// (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
// (1) mySignatureRunId != NoRunId => mySignature != null
// (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
// (3) myScala2SignatureRunId != NoRunId => myScala2Signature != null

private var mySignature: Signature = uninitialized
private var mySignatureProvState: ProvisionalState | Null = null
private var mySignatureRunId: Int = NoRunId
private var myJavaSignature: Signature = uninitialized
private var myJavaSignatureProvState: ProvisionalState | Null = null
private var myJavaSignatureRunId: Int = NoRunId
private var myScala2Signature: Signature = uninitialized
private var myScala2SignatureProvState: ProvisionalState | Null = null
private var myScala2SignatureRunId: Int = NoRunId

/** If `isJava` is false, the Scala signature of this method. Otherwise, its Java signature.
Expand Down Expand Up @@ -3895,19 +3994,25 @@ object Types extends TypeUtils {

sourceLanguage match
case SourceLanguage.Java =>
if ctx.runId != myJavaSignatureRunId then
if ctx.runId != myJavaSignatureRunId
|| !isCacheUpToDate(currentProvisionalState, myJavaSignatureProvState) then
myJavaSignature = computeSignature
if !myJavaSignature.isUnderDefined && !isProvisional then myJavaSignatureRunId = ctx.runId
myJavaSignatureProvState = currentProvisionalState
if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
myJavaSignature
case SourceLanguage.Scala2 =>
if ctx.runId != myScala2SignatureRunId then
if ctx.runId != myScala2SignatureRunId
|| !isCacheUpToDate(currentProvisionalState, myScala2SignatureProvState) then
myScala2Signature = computeSignature
if !myScala2Signature.isUnderDefined && !isProvisional then myScala2SignatureRunId = ctx.runId
myScala2SignatureProvState = currentProvisionalState
if !myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId
myScala2Signature
case SourceLanguage.Scala3 =>
if ctx.runId != mySignatureRunId then
if ctx.runId != mySignatureRunId
|| !isCacheUpToDate(currentProvisionalState, mySignatureProvState) then
mySignature = computeSignature
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
mySignatureProvState = currentProvisionalState
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
mySignature
end signature

Expand Down
Loading

0 comments on commit e38f059

Please sign in to comment.