Skip to content

Commit bf55005

Browse files
committed
Integrate prune logic in addOneBound
New cyclic bounds can also appear as a consequence of unification. The latter goes through addOneBound, but not through addConstraint. So the prune logic that avoids cycles should go to addOneBound. ExprType checking could logically stay with addConstraint but it was simpler to move it as well.
1 parent f876537 commit bf55005

File tree

1 file changed

+60
-73
lines changed

1 file changed

+60
-73
lines changed

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

Lines changed: 60 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -87,55 +87,87 @@ trait ConstraintHandling[AbstractContext] {
8787
nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param))
8888

8989
protected def addOneBound(param: TypeParamRef, rawBound: Type, isUpper: Boolean)(implicit actx: AbstractContext): Boolean =
90-
!constraint.contains(param) || {
91-
def avoidCycle(tp: Type): Type = tp match
92-
case tp: AndOrType =>
93-
tp.derivedAndOrType(avoidCycle(tp.tp1), avoidCycle(tp.tp2))
94-
case tp: TypeParamRef =>
95-
if tp eq param then
96-
//println(i"stripping $tp from $rawBound, upper = $isUpper in $constraint")
97-
if isUpper then defn.AnyType else defn.NothingType
98-
else constraint.entry(tp) match
99-
case TypeBounds(lo, hi) => if lo eq hi then avoidCycle(lo) else tp
100-
case inst => avoidCycle(inst)
101-
case tp: TypeVar =>
102-
val underlying1 = avoidCycle(tp.underlying)
103-
if underlying1 eq tp.underlying then tp else underlying1
104-
case _ =>
105-
tp
106-
val bound = avoidCycle(rawBound)
90+
91+
/** Adjust the bound `tp` in the following ways:
92+
*
93+
* 1. Any toplevel occurrences of the compared parameter `param` are
94+
* replaced by `Nothing` if bound is from below or `Any` otherwise.
95+
* 2. Toplevel occurrences of TypeVars over TypeRefs in the current
96+
* constraint are dereferenced.
97+
* 3. Toplevel occurrences of TypeRefs that are instantiated in the current
98+
* constraint are also dereferenced.
99+
* 4. Toplevel occurrences of ExprTypes lead to a `NoType` return, which
100+
* causes the addOneBound operation to fail.
101+
*
102+
* An occurrence is toplevel if it is the bound itself,
103+
* or the bound is a union or intersection, and the ocurrence is
104+
* toplevel in one of the operands of the `&` or `|`.
105+
*/
106+
def adjust(tp: Type): Type = tp match
107+
case tp: AndOrType =>
108+
val p1 = adjust(tp.tp1)
109+
val p2 = adjust(tp.tp2)
110+
if p1.exists && p2.exists then tp.derivedAndOrType(p1, p2) else NoType
111+
case tp: TypeParamRef =>
112+
if tp eq param then // (1)
113+
//println(i"stripping $tp from $rawBound, upper = $isUpper in $constraint")
114+
if isUpper then defn.AnyType else defn.NothingType
115+
else constraint.entry(tp) match // (3)
116+
case TypeBounds(lo, hi) => if lo eq hi then adjust(lo) else tp
117+
case inst => adjust(inst)
118+
case tp: TypeVar => // (2)
119+
val underlying1 = adjust(tp.underlying)
120+
if (underlying1 ne tp.underlying) || constraint.contains(tp.origin)
121+
then underlying1
122+
else tp
123+
case tp: ExprType => // (4)
124+
// ExprTypes are not value types, so type parameters should not
125+
// be instantiated to ExprTypes. A scenario where such an attempted
126+
// instantiation can happen is if we unify (=> T) => () with A => ()
127+
// where A is a TypeParamRef. See the comment on EtaExpansion.etaExpand
128+
// why types such as (=> T) => () can be constructed and i7969.scala
129+
// as a test where this happens.
130+
// Note that scalac by contrast allows such instantiations. But letting
131+
// type variables be ExprTypes has its own problems (e.g. you can't write
132+
// the resulting types down) and is largely unknown terrain.
133+
NoType
134+
case _ =>
135+
tp
136+
137+
if !constraint.contains(param) then true
138+
else
139+
val bound = adjust(rawBound)
107140

108141
val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param)
109142
val equalBounds = isUpper && (lo eq bound) || !isUpper && (bound eq hi)
110-
if (equalBounds &&
111-
!bound.existsPart(bp => bp.isInstanceOf[WildcardType] || (bp eq param))) {
143+
if !bound.exists then false
144+
else if equalBounds
145+
&& !bound.existsPart(bp => bp.isInstanceOf[WildcardType] || (bp eq param))
146+
then
112147
// The narrowed bounds are equal and do not contain wildcards,
113148
// so we can remove `param` from the constraint.
114149
// (Handling wildcards requires choosing a bound, but we don't know which
115150
// bound to choose here, this is handled in `ConstraintHandling#approximation`)
116151
constraint = constraint.replace(param, bound)
117152
true
118-
}
119-
else {
153+
else
120154
// Narrow one of the bounds of type parameter `param`
121155
// If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
122156
// that `param >: bound`.
123-
val narrowedBounds = {
157+
val narrowedBounds =
124158
val saved = homogenizeArgs
125159
homogenizeArgs = Config.alignArgsInAnd
126160
try
127161
if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound)
128162
else oldBounds.derivedTypeBounds(lo | bound, hi)
129163
finally homogenizeArgs = saved
130-
}
131164
val c1 = constraint.updateEntry(param, narrowedBounds)
132165
(c1 eq constraint) || {
133166
constraint = c1
134167
val TypeBounds(lo, hi) = constraint.entry(param)
135168
isSubType(lo, hi)
136169
}
137-
}
138-
}
170+
end addOneBound
139171

140172
private def location(implicit ctx: Context) = "" // i"in ${ctx.typerState.stateChainStr}" // use for debugging
141173

@@ -201,7 +233,6 @@ trait ConstraintHandling[AbstractContext] {
201233
up.forall(addOneBound(_, lo, isUpper = false))
202234
}
203235

204-
205236
protected def isSubType(tp1: Type, tp2: Type, whenFrozen: Boolean)(implicit actx: AbstractContext): Boolean =
206237
if (whenFrozen)
207238
isSubTypeWhenFrozen(tp1, tp2)
@@ -437,7 +468,7 @@ trait ConstraintHandling[AbstractContext] {
437468
* error in Test2 when the rest of the SI-2712 fix is applied but `pruneLambdaParams` is
438469
* missing.
439470
*/
440-
def pruneLambdaParams(tp: Type) =
471+
def avoidLambdaParams(tp: Type) =
441472
if (comparedTypeLambdas.nonEmpty) {
442473
val approx = new ApproximatingTypeMap {
443474
if (!fromBelow) variance = -1
@@ -461,49 +492,6 @@ trait ConstraintHandling[AbstractContext] {
461492
if (fromBelow) isSubType(bound, tp) else isSubType(tp, bound)
462493
}
463494

464-
/** Normalize the bound `bnd` in the following ways:
465-
*
466-
* 1. Any toplevel occurrences of the compared parameter `param` are
467-
* replaced by `Nothing` if bound is from below or `Any` otherwise.
468-
* 2. Toplevel occurrences of TypeVars over TypeRefs in the current
469-
* constraint are dereferenced.
470-
* 3. Toplevel occurrences of TypeRefs that are instantiated in the current
471-
* constraint are also referenced.
472-
* 4. Toplevel occurrences of ExprTypes lead to a `NoType` return, which
473-
* causes the addConstraint operation to fail.
474-
*
475-
* An occurrence is toplevel if it is the bound itself,
476-
* or the bound is a union or intersection, and the ocurrence is
477-
* toplevel in one of the operands of the `&` or `|`.
478-
*/
479-
def prune(bnd: Type): Type = bnd match
480-
case bnd: AndOrType =>
481-
val p1 = prune(bnd.tp1)
482-
val p2 = prune(bnd.tp2)
483-
if (p1.exists && p2.exists) bnd.derivedAndOrType(p1, p2)
484-
else NoType
485-
case bnd: TypeVar if constraint contains bnd.origin => // (2)
486-
prune(bnd.underlying)
487-
case bnd: TypeParamRef =>
488-
if bnd eq param then // (1)
489-
if fromBelow then defn.NothingType else defn.AnyType
490-
else constraint.entry(bnd) match
491-
case TypeBounds(lo, hi) => if lo eq hi then prune(lo) else bnd
492-
case inst => prune(inst) // (3)
493-
case bnd: ExprType => // (4)
494-
// ExprTypes are not value types, so type parameters should not
495-
// be instantiated to ExprTypes. A scenario where such an attempted
496-
// instantiation can happen is if we unify (=> T) => () with A => ()
497-
// where A is a TypeParamRef. See the comment on EtaExpansion.etaExpand
498-
// why types such as (=> T) => () can be constructed and i7969.scala
499-
// as a test where this happens.
500-
// Note that scalac by contrast allows such instantiations. But letting
501-
// type variables be ExprTypes has its own problems (e.g. you can't write
502-
// the resulting types down) and is largely unknown terrain.
503-
NoType
504-
case _ =>
505-
bnd
506-
507495
def kindCompatible(tp1: Type, tp2: Type): Boolean =
508496
val tparams1 = tp1.typeParams
509497
val tparams2 = tp2.typeParams
@@ -521,9 +509,8 @@ trait ConstraintHandling[AbstractContext] {
521509
case bound: TypeParamRef if constraint contains bound =>
522510
addParamBound(bound)
523511
case _ =>
524-
val pbound = prune(pruneLambdaParams(bound))
525-
pbound.exists
526-
&& kindCompatible(param, pbound)
512+
val pbound = avoidLambdaParams(bound)
513+
kindCompatible(param, pbound)
527514
&& (if fromBelow then addLowerBound(param, pbound) else addUpperBound(param, pbound))
528515
finally addConstraintInvocations -= 1
529516
}

0 commit comments

Comments
 (0)