@@ -12,6 +12,7 @@ import config.Printers.typr
1212import typer .ProtoTypes .{newTypeVar , representedParamRef }
1313import UnificationDirection .*
1414import NameKinds .AvoidNameKind
15+ import util .SimpleIdentitySet
1516
1617/** Methods for adding constraints and solving them.
1718 *
@@ -74,8 +75,41 @@ trait ConstraintHandling {
7475 protected def necessaryConstraintsOnly (using Context ): Boolean =
7576 ctx.mode.is(Mode .GadtConstraintInference ) || myNecessaryConstraintsOnly
7677
78+ /** If `trustBounds = false` we perform comparisons in a pessimistic way as follows:
79+ * Given an abstract type `A >: L <: H`, a subtype comparison of any type
80+ * with `A` will compare against both `L` and `H`. E.g.
81+ *
82+ * T <:< A if T <:< L and T <:< H
83+ * A <:< T if L <:< T and H <:< T
84+ *
85+ * This restricted form makes sure we don't "forget" types when forming
86+ * unions and intersections with abstract types that have bad bounds. E.g.
87+ * the following example from neg/i8900.scala that @smarter came up with:
88+ * We have a type variable X with constraints
89+ *
90+ * X >: 1, X >: x.M
91+ *
92+ * where `x` is a locally nested variable and `x.M` has bad bounds
93+ *
94+ * x.M >: Int | String <: Int & String
95+ *
96+ * If we trust bounds, then the lower bound of `X` is `x.M` since `x.M >: 1`.
97+ * Then even if we correct levels on instantiation to eliminate the local `x`,
98+ * it is alreay too late, we'd get `Int & String` as instance, which does not
99+ * satisfy the original constraint `X >: 1`.
100+ *
101+ * But if `trustBounds` is false, we do not conclude the `x.M >: 1` since
102+ * we compare both bounds and the upper bound `Int & String` is not a supertype
103+ * of `1`. So the lower bound is `1 | x.M` and when we level-avoid that we
104+ * get `1 | Int & String`, which simplifies to `Int`.
105+ */
77106 protected var trustBounds = true
78107
108+ inline def withUntrustedBounds (op : => Type ): Type =
109+ val saved = trustBounds
110+ trustBounds = false
111+ try op finally trustBounds = saved
112+
79113 def checkReset () =
80114 assert(addConstraintInvocations == 0 )
81115 assert(frozenConstraint == false )
@@ -262,16 +296,14 @@ trait ConstraintHandling {
262296 // If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
263297 // that `param >: bound`.
264298 val narrowedBounds =
265- val savedHomogenizeArgs = homogenizeArgs
266- val savedTrustBounds = trustBounds
299+ val saved = homogenizeArgs
267300 homogenizeArgs = Config .alignArgsInAnd
268301 try
269- trustBounds = false
270- if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound)
271- else oldBounds.derivedTypeBounds(lo | bound, hi)
302+ withUntrustedBounds(
303+ if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound)
304+ else oldBounds.derivedTypeBounds(lo | bound, hi) )
272305 finally
273- homogenizeArgs = savedHomogenizeArgs
274- trustBounds = savedTrustBounds
306+ homogenizeArgs = saved
275307 // println(i"narrow bounds for $param from $oldBounds to $narrowedBounds")
276308 val c1 = constraint.updateEntry(param, narrowedBounds)
277309 (c1 eq constraint)
@@ -431,6 +463,49 @@ trait ConstraintHandling {
431463 }
432464 }
433465
466+ private def fixLevels (tp : Type , fromBelow : Boolean , maxLevel : Int , param : TypeParamRef )(using Context ) =
467+
468+ def needsFix (tp : NamedType ) =
469+ (tp.prefix eq NoPrefix ) && tp.symbol.nestingLevel > maxLevel
470+
471+ class NeedsLeveling extends TypeAccumulator [Boolean ]:
472+ if ! fromBelow then variance = - 1
473+ var nestedVarsLo, nestedVarsHi : SimpleIdentitySet [TypeVar ] = SimpleIdentitySet .empty
474+ def apply (need : Boolean , tp : Type ) =
475+ need || tp.match
476+ case tp : NamedType =>
477+ needsFix(tp)
478+ || ! stopBecauseStaticOrLocal(tp) && apply(need, tp.prefix)
479+ case tp : TypeVar =>
480+ val inst = tp.instanceOpt
481+ if inst.exists then apply(need, inst)
482+ else if tp.nestingLevel > maxLevel then
483+ if variance > 0 then nestedVarsLo += tp
484+ else if variance < 0 then nestedVarsHi += tp
485+ else tp.nestingLevel = maxLevel
486+ true
487+ else false
488+ case _ =>
489+ foldOver(need, tp)
490+
491+ class LevelAvoidMap extends TypeOps .AvoidMap :
492+ if ! fromBelow then variance = - 1
493+ def toAvoid (tp : NamedType ) = needsFix(tp)
494+ // override def apply(tp: Type): Type = tp match
495+ // case tp: LazyRef => tp
496+ // case _ => super.apply(tp)
497+
498+ if ctx.isAfterTyper then tp
499+ else
500+ val needsLeveling = NeedsLeveling ()
501+ if needsLeveling(false , tp) then
502+ typr.println(i " instance $tp for $param needs leveling to $maxLevel, nested = ${needsLeveling.nestedVarsLo.toList} | ${needsLeveling.nestedVarsHi.toList}" )
503+ needsLeveling.nestedVarsLo.foreach(_.instantiate(fromBelow = true ))
504+ needsLeveling.nestedVarsHi.foreach(_.instantiate(fromBelow = false ))
505+ LevelAvoidMap ()(tp)
506+ else tp
507+ end fixLevels
508+
434509 /** Solve constraint set for given type parameter `param`.
435510 * If `fromBelow` is true the parameter is approximated by its lower bound,
436511 * otherwise it is approximated by its upper bound, unless the upper bound
@@ -442,13 +517,17 @@ trait ConstraintHandling {
442517 * @return the instantiating type
443518 * @pre `param` is in the constraint's domain.
444519 */
445- final def approximation (param : TypeParamRef , fromBelow : Boolean )(using Context ): Type =
520+ final def approximation (param : TypeParamRef , fromBelow : Boolean , maxLevel : Int )(using Context ): Type =
446521 constraint.entry(param) match
447522 case entry : TypeBounds =>
448523 val useLowerBound = fromBelow || param.occursIn(entry.hi)
449- val inst = if useLowerBound then fullLowerBound(param) else fullUpperBound(param)
450- typr.println(s " approx ${param.show}, from below = $fromBelow, inst = ${inst.show}" )
451- inst
524+ val rawInst = withUntrustedBounds(
525+ if useLowerBound then fullLowerBound(param) else fullUpperBound(param))
526+ val levelInst = fixLevels(rawInst, fromBelow, maxLevel, param)
527+ if levelInst ne rawInst then
528+ typr.println(i " level avoid for $maxLevel: $rawInst --> $levelInst" )
529+ typr.println(i " approx $param, from below = $fromBelow, inst = $levelInst" )
530+ levelInst
452531 case inst =>
453532 assert(inst.exists, i " param = $param\n constraint = $constraint" )
454533 inst
@@ -561,8 +640,8 @@ trait ConstraintHandling {
561640 * a lower bound instantiation can be a singleton type only if the upper bound
562641 * is also a singleton type.
563642 */
564- def instanceType (param : TypeParamRef , fromBelow : Boolean )(using Context ): Type = {
565- val approx = approximation(param, fromBelow).simplified
643+ def instanceType (param : TypeParamRef , fromBelow : Boolean , maxLevel : Int )(using Context ): Type = {
644+ val approx = approximation(param, fromBelow, maxLevel ).simplified
566645 if fromBelow then
567646 val widened = widenInferred(approx, param)
568647 // Widening can add extra constraints, in particular the widened type might
@@ -572,7 +651,7 @@ trait ConstraintHandling {
572651 // (we do not check for non-toplevel occurences: those should never occur
573652 // since `addOneBound` disallows recursive lower bounds).
574653 if constraint.occursAtToplevel(param, widened) then
575- instanceType(param, fromBelow)
654+ instanceType(param, fromBelow, maxLevel )
576655 else
577656 widened
578657 else
0 commit comments