Skip to content

Commit d15558d

Browse files
committed
Try alternative to track hard typevars in constraint
Try alternative to track hard typevars in constraint instead of typer state. This avoids the problem that a failing subtype comparison can also mark type variables as hard.
1 parent f177266 commit d15558d

File tree

6 files changed

+41
-23
lines changed

6 files changed

+41
-23
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ abstract class Constraint extends Showable {
126126
*/
127127
def subst(from: TypeLambda, to: TypeLambda)(using Context): This
128128

129+
/** Is `tv` marked as hard in the constraint? */
130+
def isHard(tv: TypeVar): Boolean
131+
132+
/** The same as this constraint, but with `tv` marked as hard. */
133+
def withHard(tv: TypeVar)(using Context): This
134+
129135
/** Gives for each instantiated type var that does not yet have its `inst` field
130136
* set, the instance value stored in the constraint. Storing instances in constraints
131137
* is done only in a temporary way for contexts that may be retracted

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Decorators._
66
import Contexts._
77
import Types._
88
import Symbols._
9-
import util.SimpleIdentityMap
9+
import util.{SimpleIdentitySet, SimpleIdentityMap}
1010
import collection.mutable
1111
import printing._
1212

@@ -68,7 +68,7 @@ final class ProperGadtConstraint private(
6868
import dotty.tools.dotc.config.Printers.{gadts, gadtsConstr}
6969

7070
def this() = this(
71-
myConstraint = new OrderingConstraint(SimpleIdentityMap.empty, SimpleIdentityMap.empty, SimpleIdentityMap.empty),
71+
myConstraint = new OrderingConstraint(SimpleIdentityMap.empty, SimpleIdentityMap.empty, SimpleIdentityMap.empty, SimpleIdentitySet.empty),
7272
mapping = SimpleIdentityMap.empty,
7373
reverseMapping = SimpleIdentityMap.empty,
7474
wasConstrained = false

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotc
33
package core
44

55
import Types._, Contexts._, Symbols._, Decorators._, TypeApplications._
6-
import util.SimpleIdentityMap
6+
import util.{SimpleIdentitySet, SimpleIdentityMap}
77
import collection.mutable
88
import printing.Printer
99
import printing.Texts._
@@ -24,12 +24,14 @@ object OrderingConstraint {
2424
/** The type of `OrderingConstraint#lowerMap`, `OrderingConstraint#upperMap` */
2525
type ParamOrdering = ArrayValuedMap[List[TypeParamRef]]
2626

27-
/** A new constraint with given maps */
28-
private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(using Context) : OrderingConstraint =
27+
/** A new constraint with given maps and given set of hard typevars */
28+
private def newConstraint(
29+
boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering,
30+
hardVars: TypeVars)(using Context) : OrderingConstraint =
2931
if boundsMap.isEmpty && lowerMap.isEmpty && upperMap.isEmpty then
3032
empty
3133
else
32-
val result = new OrderingConstraint(boundsMap, lowerMap, upperMap)
34+
val result = new OrderingConstraint(boundsMap, lowerMap, upperMap, hardVars)
3335
if ctx.run != null then ctx.run.nn.recordConstraintSize(result, result.boundsMap.size)
3436
result
3537

@@ -91,28 +93,28 @@ object OrderingConstraint {
9193
def entries(c: OrderingConstraint, poly: TypeLambda): Array[Type] | Null =
9294
c.boundsMap(poly)
9395
def updateEntries(c: OrderingConstraint, poly: TypeLambda, entries: Array[Type])(using Context): OrderingConstraint =
94-
newConstraint(c.boundsMap.updated(poly, entries), c.lowerMap, c.upperMap)
96+
newConstraint(c.boundsMap.updated(poly, entries), c.lowerMap, c.upperMap, c.hardVars)
9597
def initial = NoType
9698
}
9799

98100
val lowerLens: ConstraintLens[List[TypeParamRef]] = new ConstraintLens[List[TypeParamRef]] {
99101
def entries(c: OrderingConstraint, poly: TypeLambda): Array[List[TypeParamRef]] | Null =
100102
c.lowerMap(poly)
101103
def updateEntries(c: OrderingConstraint, poly: TypeLambda, entries: Array[List[TypeParamRef]])(using Context): OrderingConstraint =
102-
newConstraint(c.boundsMap, c.lowerMap.updated(poly, entries), c.upperMap)
104+
newConstraint(c.boundsMap, c.lowerMap.updated(poly, entries), c.upperMap, c.hardVars)
103105
def initial = Nil
104106
}
105107

106108
val upperLens: ConstraintLens[List[TypeParamRef]] = new ConstraintLens[List[TypeParamRef]] {
107109
def entries(c: OrderingConstraint, poly: TypeLambda): Array[List[TypeParamRef]] | Null =
108110
c.upperMap(poly)
109111
def updateEntries(c: OrderingConstraint, poly: TypeLambda, entries: Array[List[TypeParamRef]])(using Context): OrderingConstraint =
110-
newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries))
112+
newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries), c.hardVars)
111113
def initial = Nil
112114
}
113115

114116
@sharable
115-
val empty = new OrderingConstraint(SimpleIdentityMap.empty, SimpleIdentityMap.empty, SimpleIdentityMap.empty)
117+
val empty = new OrderingConstraint(SimpleIdentityMap.empty, SimpleIdentityMap.empty, SimpleIdentityMap.empty, SimpleIdentitySet.empty)
116118
}
117119

118120
import OrderingConstraint._
@@ -134,10 +136,13 @@ import OrderingConstraint._
134136
* @param upperMap a map from TypeLambdas to arrays. Each array entry corresponds
135137
* to a parameter P of the type lambda; it contains all constrained parameters
136138
* Q that are known to be greater than P, i.e. P <: Q.
139+
* @param hardVars a set of type variables that are marked as hard and therefore will not
140+
* undergo a `widenUnion` when instantiated to their lower bound.
137141
*/
138142
class OrderingConstraint(private val boundsMap: ParamBounds,
139143
private val lowerMap : ParamOrdering,
140-
private val upperMap : ParamOrdering) extends Constraint {
144+
private val upperMap : ParamOrdering,
145+
private val hardVars : TypeVars) extends Constraint {
141146

142147
import UnificationDirection.*
143148

@@ -277,7 +282,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
277282
val entries1 = new Array[Type](nparams * 2)
278283
poly.paramInfos.copyToArray(entries1, 0)
279284
tvars.copyToArray(entries1, nparams)
280-
newConstraint(boundsMap.updated(poly, entries1), lowerMap, upperMap).init(poly)
285+
newConstraint(boundsMap.updated(poly, entries1), lowerMap, upperMap, hardVars).init(poly)
281286
}
282287

283288
/** Split dependent parameters off the bounds for parameters in `poly`.
@@ -478,7 +483,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
478483
}
479484
po.remove(pt).mapValuesNow(removeFromBoundss)
480485
}
481-
newConstraint(boundsMap.remove(pt), removeFromOrdering(lowerMap), removeFromOrdering(upperMap))
486+
val hardVars1 = pt.paramRefs.foldLeft(hardVars)((hvs, param) => hvs - typeVarOfParam(param))
487+
newConstraint(boundsMap.remove(pt), removeFromOrdering(lowerMap), removeFromOrdering(upperMap), hardVars1)
482488
.checkNonCyclic()
483489
}
484490

@@ -505,7 +511,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
505511
def swapKey[T](m: ArrayValuedMap[T]) =
506512
val info = m(from)
507513
if info == null then m else m.remove(from).updated(to, info)
508-
var current = newConstraint(swapKey(boundsMap), swapKey(lowerMap), swapKey(upperMap))
514+
var current = newConstraint(swapKey(boundsMap), swapKey(lowerMap), swapKey(upperMap), hardVars)
509515
def subst[T <: Type](x: T): T = x.subst(from, to).asInstanceOf[T]
510516
current.foreachParam {(p, i) =>
511517
current = boundsLens.map(this, current, p, i, subst)
@@ -515,6 +521,11 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
515521
constr.println(i"renamed $this to $current")
516522
current.checkNonCyclic()
517523

524+
def isHard(tv: TypeVar) = hardVars.contains(tv)
525+
526+
def withHard(tv: TypeVar)(using Context) =
527+
newConstraint(boundsMap, lowerMap, upperMap, hardVars + tv)
528+
518529
def instType(tvar: TypeVar): Type = entry(tvar.origin) match
519530
case _: TypeBounds => NoType
520531
case tp: TypeParamRef => typeVarOfParam(tp).orElse(tp)

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -495,10 +495,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
495495
case AndType(tp21, tp22) => constrainRHSVars(tp21) && constrainRHSVars(tp22)
496496
case _ => true
497497

498-
/** Mark toplevel type vars in `tp2` as hard in the current typerState */
498+
/** Mark toplevel type vars in `tp2` as hard in the current constraint */
499499
def hardenTypeVars(tp2: Type): Unit = tp2.dealiasKeepRefiningAnnots match
500500
case tvar: TypeVar if constraint.contains(tvar.origin) =>
501501
state.hardenTypeVar(tvar)
502+
constraint = constraint.withHard(tvar)
502503
case tp2: TypeParamRef if constraint.contains(tp2) =>
503504
hardenTypeVars(constraint.typeVarOfParam(tp2))
504505
case tp2: AndOrType =>
@@ -524,14 +525,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
524525
// recursively, so we do it only once. See i14870.scala as a test case, which would
525526
// loop for a very long time without the recursion brake.
526527

527-
if res && !tp1.isSoft then
528+
if res && !tp1.isSoft && state.isCommittable then
528529
// We use a heuristic here where every toplevel type variable on the right hand side
529530
// is marked so that it converts all soft unions in its lower bound to hard unions
530-
// before it is instantiated. The reason is that the union might have come from
531-
// (decomposed and reconstituted) `tp1`. But of course there might be false positives
532-
// where we also treat unions that come from elsewhere as hard unions. Or the constraint
533-
// that created the union is ultimately thrown away, but the type variable will
534-
// stay marked. So it is a coarse measure to take. But it works in the obvious cases.
531+
// before it is instantiated. The reason is that the variable's instance type will
532+
// be a supertype of (decomposed and reconstituted) `tp1`.
535533
hardenTypeVars(tp2)
536534

537535
res

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ class TyperState() {
246246
constraint.contains(tl) || other.isRemovable(tl) || {
247247
val tvars = tl.paramRefs.map(other.typeVarOfParam(_)).collect { case tv: TypeVar => tv }
248248
if this.isCommittable then
249-
tvars.foreach(tvar => if !tvar.inst.exists && !isOwnedAnywhere(this, tvar) then includeVar(tvar))
249+
tvars.foreach(tvar =>
250+
if !tvar.inst.exists && !isOwnedAnywhere(this, tvar) then includeVar(tvar))
250251
typeComparer.addToConstraint(tl, tvars)
251252
}) &&
252253
// Integrate the additional constraints on type variables from `other`

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4721,7 +4721,9 @@ object Types {
47214721
instantiateWith(tp)
47224722

47234723
/** Widen unions when instantiating this variable in the current context? */
4724-
def widenUnions(using Context): Boolean = !ctx.typerState.isHard(this)
4724+
def widenUnions(using Context): Boolean =
4725+
if true then !ctx.typerState.constraint.isHard(this)
4726+
else !ctx.typerState.isHard(this)
47254727

47264728
/** For uninstantiated type variables: the entry in the constraint (either bounds or
47274729
* provisional instance value)

0 commit comments

Comments
 (0)