@@ -175,7 +175,7 @@ object Types {
175
175
// see: tests/explicit-nulls/pos/flow-stable.scala.disabled
176
176
tp.tp1.isStable && (realizability(tp.tp2) eq Realizable ) ||
177
177
tp.tp2.isStable && (realizability(tp.tp1) eq Realizable )
178
- case AppliedType ( tycon : TypeRef , args) if defn.isCompiletimeAppliedType(tycon.symbol) && args.forall(_.isStable) => true
178
+ case tp : AppliedType => tp.cachedIsStable
179
179
case _ => false
180
180
}
181
181
@@ -4162,10 +4162,32 @@ object Types {
4162
4162
private var myStableHash : Byte = 0
4163
4163
private var myGround : Byte = 0
4164
4164
4165
+ private var myIsStablePeriod : Period = Nowhere
4166
+ private var myIsStable : Boolean = false
4167
+
4165
4168
def isGround (acc : TypeAccumulator [Boolean ])(using Context ): Boolean =
4166
4169
if myGround == 0 then myGround = if acc.foldOver(true , this ) then 1 else - 1
4167
4170
myGround > 0
4168
4171
4172
+ private [Types ] def cachedIsStable (using Context ): Boolean =
4173
+ // We need to invalidate the cache when the period changes because the
4174
+ // case `TermRef` of `Type#isStable` reads denotations, which depend on
4175
+ // the period. See docs/_docs/internals/periods.md for more information.
4176
+ if myIsStablePeriod != ctx.period then
4177
+ val res : Boolean = computeIsStable
4178
+ // We don't cache if the type is provisional because `Type#isStable`
4179
+ // calls `Type#stripTypeVar` which might return different results later.
4180
+ if ! isProvisional then
4181
+ myIsStablePeriod = ctx.period
4182
+ myIsStable = res
4183
+ res
4184
+ else
4185
+ myIsStable
4186
+
4187
+ private def computeIsStable (using Context ): Boolean = tycon match
4188
+ case tycon : TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) && args.forall(_.isStable) => true
4189
+ case _ => false
4190
+
4169
4191
override def underlying (using Context ): Type = tycon
4170
4192
4171
4193
override def superType (using Context ): Type =
0 commit comments