@@ -12,6 +12,7 @@ import config.Printers.typr
12
12
import typer .ProtoTypes .{newTypeVar , representedParamRef }
13
13
import UnificationDirection .*
14
14
import NameKinds .AvoidNameKind
15
+ import util .SimpleIdentitySet
15
16
16
17
/** Methods for adding constraints and solving them.
17
18
*
@@ -74,7 +75,43 @@ trait ConstraintHandling {
74
75
protected def necessaryConstraintsOnly (using Context ): Boolean =
75
76
ctx.mode.is(Mode .GadtConstraintInference ) || myNecessaryConstraintsOnly
76
77
77
- protected var trustBounds = true
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
+ */
106
+ private var myTrustBounds = true
107
+
108
+ inline def withUntrustedBounds (op : => Type ): Type =
109
+ val saved = myTrustBounds
110
+ myTrustBounds = false
111
+ try op finally myTrustBounds = saved
112
+
113
+ def trustBounds : Boolean =
114
+ ! Config .checkLevelsOnInstantiation || myTrustBounds
78
115
79
116
def checkReset () =
80
117
assert(addConstraintInvocations == 0 )
@@ -97,7 +134,7 @@ trait ConstraintHandling {
97
134
level <= maxLevel
98
135
|| ctx.isAfterTyper || ! ctx.typerState.isCommittable // Leaks in these cases shouldn't break soundness
99
136
|| level == Int .MaxValue // See `nestingLevel` above.
100
- || ! Config .checkLevels
137
+ || ! Config .checkLevelsOnConstraints
101
138
102
139
/** If `param` is nested deeper than `maxLevel`, try to instantiate it to a
103
140
* fresh type variable of level `maxLevel` and return the new variable.
@@ -262,16 +299,14 @@ trait ConstraintHandling {
262
299
// If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
263
300
// that `param >: bound`.
264
301
val narrowedBounds =
265
- val savedHomogenizeArgs = homogenizeArgs
266
- val savedTrustBounds = trustBounds
302
+ val saved = homogenizeArgs
267
303
homogenizeArgs = Config .alignArgsInAnd
268
304
try
269
- trustBounds = false
270
- if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound)
271
- else oldBounds.derivedTypeBounds(lo | bound, hi)
305
+ withUntrustedBounds(
306
+ if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound)
307
+ else oldBounds.derivedTypeBounds(lo | bound, hi) )
272
308
finally
273
- homogenizeArgs = savedHomogenizeArgs
274
- trustBounds = savedTrustBounds
309
+ homogenizeArgs = saved
275
310
// println(i"narrow bounds for $param from $oldBounds to $narrowedBounds")
276
311
val c1 = constraint.updateEntry(param, narrowedBounds)
277
312
(c1 eq constraint)
@@ -431,24 +466,98 @@ trait ConstraintHandling {
431
466
}
432
467
}
433
468
469
+ /** Fix instance type `tp` by avoidance so that it does not contain references
470
+ * to types at level > `maxLevel`.
471
+ * @param tp the type to be fixed
472
+ * @param fromBelow whether type was obtained from lower bound
473
+ * @param maxLevel the maximum level of references allowed
474
+ * @param param the parameter that was instantiated
475
+ */
476
+ private def fixLevels (tp : Type , fromBelow : Boolean , maxLevel : Int , param : TypeParamRef )(using Context ) =
477
+
478
+ def needsFix (tp : NamedType ) =
479
+ (tp.prefix eq NoPrefix ) && tp.symbol.nestingLevel > maxLevel
480
+
481
+ /** An accumulator that determines whether levels need to be fixed
482
+ * and computes on the side sets of nested type variables that need
483
+ * to be instantiated.
484
+ */
485
+ class NeedsLeveling extends TypeAccumulator [Boolean ]:
486
+ if ! fromBelow then variance = - 1
487
+
488
+ /** Nested type variables that should be instiated to theor lower (respoctively
489
+ * upper) bounds.
490
+ */
491
+ var nestedVarsLo, nestedVarsHi : SimpleIdentitySet [TypeVar ] = SimpleIdentitySet .empty
492
+
493
+ def apply (need : Boolean , tp : Type ) =
494
+ need || tp.match
495
+ case tp : NamedType =>
496
+ needsFix(tp)
497
+ || ! stopBecauseStaticOrLocal(tp) && apply(need, tp.prefix)
498
+ case tp : TypeVar =>
499
+ val inst = tp.instanceOpt
500
+ if inst.exists then apply(need, inst)
501
+ else if tp.nestingLevel > maxLevel then
502
+ if variance > 0 then nestedVarsLo += tp
503
+ else if variance < 0 then nestedVarsHi += tp
504
+ else
505
+ // For invariant type variables, we use a different strategy.
506
+ // Rather than instantiating to a bound and then propagating in an
507
+ // AvoidMap, change the nesting level of an invariant type
508
+ // variable to `maxLevel`. This means that the type variable will be
509
+ // instantiated later to a less nested type. If there are other references
510
+ // to the same type variable that do not come from the type undergoing
511
+ // `fixLevels`, this could lead to coarser types. But it has the potential
512
+ // to give a better approximation for the current type, since it avoids forming
513
+ // a Range in invariant position, which can lead to very coarse types further out.
514
+ constr.println(i " widening nesting level of type variable $tp from ${tp.nestingLevel} to $maxLevel" )
515
+ ctx.typerState.setNestingLevel(tp, maxLevel)
516
+ true
517
+ else false
518
+ case _ =>
519
+ foldOver(need, tp)
520
+ end NeedsLeveling
521
+
522
+ class LevelAvoidMap extends TypeOps .AvoidMap :
523
+ if ! fromBelow then variance = - 1
524
+ def toAvoid (tp : NamedType ) = needsFix(tp)
525
+
526
+ if ! Config .checkLevelsOnInstantiation || ctx.isAfterTyper then tp
527
+ else
528
+ val needsLeveling = NeedsLeveling ()
529
+ if needsLeveling(false , tp) then
530
+ typr.println(i " instance $tp for $param needs leveling to $maxLevel, nested = ${needsLeveling.nestedVarsLo.toList} | ${needsLeveling.nestedVarsHi.toList}" )
531
+ needsLeveling.nestedVarsLo.foreach(_.instantiate(fromBelow = true ))
532
+ needsLeveling.nestedVarsHi.foreach(_.instantiate(fromBelow = false ))
533
+ LevelAvoidMap ()(tp)
534
+ else tp
535
+ end fixLevels
536
+
434
537
/** Solve constraint set for given type parameter `param`.
435
538
* If `fromBelow` is true the parameter is approximated by its lower bound,
436
539
* otherwise it is approximated by its upper bound, unless the upper bound
437
540
* contains a reference to the parameter itself (such occurrences can arise
438
541
* for F-bounded types, `addOneBound` ensures that they never occur in the
439
542
* lower bound).
543
+ * The solved type is not allowed to contain references to types nested deeper
544
+ * than `maxLevel`.
440
545
* Wildcard types in bounds are approximated by their upper or lower bounds.
441
546
* The constraint is left unchanged.
442
547
* @return the instantiating type
443
548
* @pre `param` is in the constraint's domain.
444
549
*/
445
- final def approximation (param : TypeParamRef , fromBelow : Boolean )(using Context ): Type =
550
+ final def approximation (param : TypeParamRef , fromBelow : Boolean , maxLevel : Int )(using Context ): Type =
446
551
constraint.entry(param) match
447
552
case entry : TypeBounds =>
448
553
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
554
+ val rawInst = withUntrustedBounds(
555
+ if useLowerBound then fullLowerBound(param) else fullUpperBound(param))
556
+ val levelInst = fixLevels(rawInst, fromBelow, maxLevel, param)
557
+ if levelInst ne rawInst then
558
+ typr.println(i " level avoid for $maxLevel: $rawInst --> $levelInst" )
559
+ typr.println(i " approx $param, from below = $fromBelow, inst = $levelInst" )
560
+ levelInst
452
561
case inst =>
453
562
assert(inst.exists, i " param = $param\n constraint = $constraint" )
454
563
inst
@@ -560,9 +669,11 @@ trait ConstraintHandling {
560
669
* lower bounds; otherwise it is the glb of its upper bounds. However,
561
670
* a lower bound instantiation can be a singleton type only if the upper bound
562
671
* is also a singleton type.
672
+ * The instance type is not allowed to contain references to types nested deeper
673
+ * than `maxLevel`.
563
674
*/
564
- def instanceType (param : TypeParamRef , fromBelow : Boolean )(using Context ): Type = {
565
- val approx = approximation(param, fromBelow).simplified
675
+ def instanceType (param : TypeParamRef , fromBelow : Boolean , maxLevel : Int )(using Context ): Type = {
676
+ val approx = approximation(param, fromBelow, maxLevel ).simplified
566
677
if fromBelow then
567
678
val widened = widenInferred(approx, param)
568
679
// Widening can add extra constraints, in particular the widened type might
@@ -572,7 +683,7 @@ trait ConstraintHandling {
572
683
// (we do not check for non-toplevel occurences: those should never occur
573
684
// since `addOneBound` disallows recursive lower bounds).
574
685
if constraint.occursAtToplevel(param, widened) then
575
- instanceType(param, fromBelow)
686
+ instanceType(param, fromBelow, maxLevel )
576
687
else
577
688
widened
578
689
else
0 commit comments