Skip to content

Commit 2863a29

Browse files
committed
Avoid the TypeVar.inst trap
`tvar.inst` gives the _permanent_ instance of a type variable `tvar`. Even if `tvar.isInstantiated` is true its `inst` can still be NoType. This is a trap that caused a regression in the code of glb. This commit fixes the regression and introduces different names that will hopefully avoid the trap in the future. Fixes #20154
1 parent 9d990fb commit 2863a29

File tree

6 files changed

+50
-26
lines changed

6 files changed

+50
-26
lines changed

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
315315

316316
override def tyconTypeParams(tp: AppliedType)(using Context): List[ParamInfo] =
317317
def tparams(tycon: Type): List[ParamInfo] = tycon match
318-
case tycon: TypeVar if !tycon.inst.exists => tparams(tycon.origin)
318+
case tycon: TypeVar if !tycon.isPermanentlyInstantiated => tparams(tycon.origin)
319319
case tycon: TypeParamRef if !hasBounds(tycon) =>
320320
val entryParams = entry(tycon).typeParams
321321
if entryParams.nonEmpty then entryParams
@@ -715,7 +715,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
715715
var newDepEntry = newEntry
716716
replacedTypeVar match
717717
case tvar: TypeVar =>
718-
if tvar.inst.exists // `isInstantiated` would use ctx.typerState.constraint rather than the current constraint
718+
if tvar.isPermanentlyInstantiated // `isInstantiated` would use ctx.typerState.constraint rather than the current constraint
719719
then
720720
// If the type variable has been instantiated, we need to forget about
721721
// the instantiation for old dependencies.
@@ -781,7 +781,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
781781
@tailrec def allRemovable(last: Int): Boolean =
782782
if (last < 0) true
783783
else typeVar(entries, last) match {
784-
case tv: TypeVar => tv.inst.exists && allRemovable(last - 1)
784+
case tv: TypeVar => tv.isPermanentlyInstantiated && allRemovable(last - 1)
785785
case _ => false
786786
}
787787
allRemovable(paramCount(entries) - 1)
@@ -887,7 +887,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
887887
val limit = paramCount(entries)
888888
while i < limit do
889889
typeVar(entries, i) match
890-
case tv: TypeVar if !tv.inst.exists => op(tv)
890+
case tv: TypeVar if !tv.isPermanentlyInstantiated => op(tv)
891891
case _ =>
892892
i += 1
893893
}
@@ -896,12 +896,12 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
896896

897897
/** The uninstantiated typevars of this constraint */
898898
def uninstVars: collection.Seq[TypeVar] = {
899-
if (myUninstVars == null || myUninstVars.uncheckedNN.exists(_.inst.exists)) {
899+
if (myUninstVars == null || myUninstVars.uncheckedNN.exists(_.isPermanentlyInstantiated)) {
900900
myUninstVars = new mutable.ArrayBuffer[TypeVar]
901901
boundsMap.foreachBinding { (poly, entries) =>
902902
for (i <- 0 until paramCount(entries))
903903
typeVar(entries, i) match {
904-
case tv: TypeVar if !tv.inst.exists && isBounds(entries(i)) => myUninstVars.uncheckedNN += tv
904+
case tv: TypeVar if !tv.isPermanentlyInstantiated && isBounds(entries(i)) => myUninstVars.uncheckedNN += tv
905905
case _ =>
906906
}
907907
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@ object SymDenotations {
16091609
case tp: RefinedType => hasSkolems(tp.parent) || hasSkolems(tp.refinedInfo)
16101610
case tp: RecType => hasSkolems(tp.parent)
16111611
case tp: TypeBounds => hasSkolems(tp.lo) || hasSkolems(tp.hi)
1612-
case tp: TypeVar => hasSkolems(tp.inst)
1612+
case tp: TypeVar => hasSkolems(tp.permanentInst)
16131613
case tp: ExprType => hasSkolems(tp.resType)
16141614
case tp: AppliedType => hasSkolems(tp.tycon) || tp.args.exists(hasSkolems)
16151615
case tp: LambdaType => tp.paramInfos.exists(hasSkolems) || hasSkolems(tp.resType)

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -1600,7 +1600,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
16001600
val tycon1 = liftToThis(tp.tycon)
16011601
if (tycon1 ne tp.tycon) tp.derivedAppliedType(tycon1, tp.args) else tp
16021602
case tp: TypeVar if tp.isInstantiated =>
1603-
liftToThis(tp.inst)
1603+
liftToThis(tp.instanceOpt)
16041604
case tp: AnnotatedType =>
16051605
val parent1 = liftToThis(tp.parent)
16061606
if (parent1 ne tp.parent) tp.derivedAnnotatedType(parent1, tp.annot) else tp
@@ -2521,14 +2521,14 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
25212521

25222522
def isSuperOf(sub: Type): Boolean = sub match
25232523
case AndType(sub1, sub2) => isSuperOf(sub1) || isSuperOf(sub2)
2524-
case sub: TypeVar if sub.isInstantiated => isSuperOf(sub.inst)
2524+
case sub: TypeVar if sub.isInstantiated => isSuperOf(sub.instanceOpt)
25252525
case _ => isSubTypeWhenFrozen(sub, tp)
25262526

25272527
tp match
25282528
case tp @ AndType(tp1, tp2) =>
25292529
recombine(dropIfSuper(tp1, sub), dropIfSuper(tp2, sub), tp)
25302530
case tp: TypeVar if tp.isInstantiated =>
2531-
dropIfSuper(tp.inst, sub)
2531+
dropIfSuper(tp.instanceOpt, sub)
25322532
case _ =>
25332533
if isSuperOf(sub) then NoType else tp
25342534
end dropIfSuper
@@ -2538,14 +2538,14 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
25382538

25392539
def isSubOf(sup: Type): Boolean = sup match
25402540
case OrType(sup1, sup2) => isSubOf(sup1) || isSubOf(sup2)
2541-
case sup: TypeVar if sup.isInstantiated => isSubOf(sup.inst)
2541+
case sup: TypeVar if sup.isInstantiated => isSubOf(sup.instanceOpt)
25422542
case _ => isSubType(tp, sup, whenFrozen = !canConstrain)
25432543

25442544
tp match
25452545
case tp @ OrType(tp1, tp2) =>
25462546
recombine(dropIfSub(tp1, sup, canConstrain), dropIfSub(tp2, sup, canConstrain), tp)
25472547
case tp: TypeVar if tp.isInstantiated =>
2548-
dropIfSub(tp.inst, sup, canConstrain)
2548+
dropIfSub(tp.instanceOpt, sup, canConstrain)
25492549
case _ =>
25502550
if isSubOf(sup) then NoType else tp
25512551
end dropIfSub

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ class TyperState() {
231231
val tvars = tl.paramRefs.map(other.typeVarOfParam(_)).collect { case tv: TypeVar => tv }
232232
if this.isCommittable then
233233
tvars.foreach(tvar =>
234-
if !tvar.inst.exists && !isOwnedAnywhere(this, tvar) then includeVar(tvar))
234+
if !tvar.isPermanentlyInstantiated && !isOwnedAnywhere(this, tvar) then includeVar(tvar))
235235
typeComparer.addToConstraint(tl, tvars)
236236
}) &&
237237
// Integrate the additional constraints on type variables from `other`
@@ -287,10 +287,10 @@ class TyperState() {
287287
for tvar <- ownedVars do
288288
val tvarState = tvar.owningState.nn.get
289289
assert(tvarState eqn this, s"Inconsistent state in $this: it owns $tvar whose owningState is ${tvarState}")
290-
assert(!tvar.inst.exists, s"Inconsistent state in $this: it owns $tvar which is already instantiated")
290+
assert(!tvar.isPermanentlyInstantiated, s"Inconsistent state in $this: it owns $tvar which is already instantiated")
291291
val inst = constraint.instType(tvar)
292292
if inst.exists then
293-
tvar.setInst(inst)
293+
tvar.setPermanentInst(inst)
294294
val tl = tvar.origin.binder
295295
if constraint.isRemovable(tl) then toCollect += tl
296296
for tl <- toCollect do

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

+20-11
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ object Types extends TypeUtils {
139139
case t: AppliedType =>
140140
t.fold(false, (x, tp) => x || test(tp, theAcc))
141141
case t: TypeVar =>
142-
!t.inst.exists || test(t.inst, theAcc)
142+
!t.isPermanentlyInstantiated || test(t.permanentInst, theAcc)
143143
case t: LazyRef =>
144144
!t.completed || test(t.ref, theAcc)
145145
case _ =>
@@ -4934,20 +4934,24 @@ object Types extends TypeUtils {
49344934
def setOrigin(p: TypeParamRef) = currentOrigin = p
49354935

49364936
/** The permanent instance type of the variable, or NoType is none is given yet */
4937-
private var myInst: Type = NoType
4937+
private var inst: Type = NoType
49384938

4939-
private[core] def inst: Type = myInst
4940-
private[core] def setInst(tp: Type): Unit =
4941-
myInst = tp
4939+
/** The permanent instance type that's stored in the type variable, so it cannot be retracted
4940+
* anymore, or NoType if the variable can still be further constrained or a provisional
4941+
* instance type in the constraint can be retracted.
4942+
*/
4943+
private[core] def permanentInst = inst
4944+
private[core] def setPermanentInst(tp: Type): Unit =
4945+
inst = tp
49424946
if tp.exists && owningState != null then
49434947
val owningState1 = owningState.uncheckedNN.get
49444948
if owningState1 != null then
49454949
owningState1.ownedVars -= this
49464950
owningState = null // no longer needed; null out to avoid a memory leak
49474951

49484952
private[core] def resetInst(ts: TyperState): Unit =
4949-
assert(myInst.exists)
4950-
myInst = NoType
4953+
assert(inst.exists)
4954+
inst = NoType
49514955
owningState = new WeakReference(ts)
49524956

49534957
/** The state owning the variable. This is at first `creatorState`, but it can
@@ -4985,18 +4989,23 @@ object Types extends TypeUtils {
49854989
/** Is the variable already instantiated? */
49864990
def isInstantiated(using Context): Boolean = instanceOpt.exists
49874991

4992+
/** Is the variable already instantiated so that the instance cannot be
4993+
* retracted anymore?
4994+
*/
4995+
def isPermanentlyInstantiated: Boolean = inst.exists
4996+
49884997
/** Instantiate variable with given type */
49894998
def instantiateWith(tp: Type)(using Context): Type = {
49904999
assert(tp ne this, i"self instantiation of $origin, constraint = ${ctx.typerState.constraint}")
4991-
assert(!myInst.exists, i"$origin is already instantiated to $myInst but we attempted to instantiate it to $tp")
5000+
assert(!inst.exists, i"$origin is already instantiated to $inst but we attempted to instantiate it to $tp")
49925001
typr.println(i"instantiating $this with $tp")
49935002

49945003
if Config.checkConstraintsSatisfiable then
49955004
assert(currentEntry.bounds.contains(tp),
49965005
i"$origin is constrained to be $currentEntry but attempted to instantiate it to $tp")
49975006

49985007
if ((ctx.typerState eq owningState.nn.get.uncheckedNN) && !TypeComparer.subtypeCheckInProgress)
4999-
setInst(tp)
5008+
setPermanentInst(tp)
50005009
ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, tp)
50015010
tp
50025011
}
@@ -5013,8 +5022,8 @@ object Types extends TypeUtils {
50135022
*/
50145023
def instantiate(fromBelow: Boolean)(using Context): Type =
50155024
val tp = typeToInstantiateWith(fromBelow)
5016-
if myInst.exists then // The line above might have triggered instantiation of the current type variable
5017-
myInst
5025+
if inst.exists then // The line above might have triggered instantiation of the current type variable
5026+
inst
50185027
else
50195028
instantiateWith(tp)
50205029

tests/pos/i20154.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
sealed abstract class Kyo[+T, -S]
2+
opaque type <[+T, -S] >: T = T | Kyo[T, S]
3+
4+
abstract class Effect[+E]:
5+
type Command[_]
6+
7+
case class Recurse[Command[_], Result[_], E <: Effect[E], T, S, S2](
8+
h: ResultHandler[Command, Result, E, S],
9+
v: T < (E & S & S2)
10+
)
11+
12+
abstract class ResultHandler[Command[_], Result[_], E <: Effect[E], S]:
13+
opaque type Handle[T, S2] >: (Result[T] < (S & S2)) = Result[T] < (S & S2) | Recurse[Command, Result, E, T, S, S2]
14+
15+
def handle[T, S2](h: ResultHandler[Command, Result, E, S], v: T < (E & S & S2)): Handle[T, S2] = Recurse(h, v)

0 commit comments

Comments
 (0)