Skip to content

Commit f7e141d

Browse files
committed
Relax avoidance checks more for match type reduction
TypeParamRefs in match types do not have a corresponding TypeVar so they get assigned level Int.MaxValue by default, this means they can refer to variables at any level, but to avoid a crash in i14921 we also need the reverse direction (they can appear in the bounds of variables of any level), both direction should be safe because these constraints only exist during match type reduction (see `MatchType#reduced`). Fixes #14921.
1 parent 0761c50 commit f7e141d

File tree

4 files changed

+57
-19
lines changed

4 files changed

+57
-19
lines changed

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,28 @@ trait ConstraintHandling {
8181
assert(homogenizeArgs == false)
8282
assert(comparedTypeLambdas == Set.empty)
8383

84-
def nestingLevel(param: TypeParamRef) = constraint.typeVarOfParam(param) match
84+
def nestingLevel(param: TypeParamRef)(using Context) = constraint.typeVarOfParam(param) match
8585
case tv: TypeVar => tv.nestingLevel
86-
case _ => Int.MaxValue
86+
case _ =>
87+
// This should only happen when reducing match types (in
88+
// TrackingTypeComparer#matchCases) or in uncommitable TyperStates (as
89+
// asserted in ProtoTypes.constrained) and is special-cased in `levelOK`
90+
// below.
91+
Int.MaxValue
92+
93+
/** Is `level` <= `maxLevel` or legal in the current context? */
94+
def levelOK(level: Int, maxLevel: Int)(using Context): Boolean =
95+
level <= maxLevel ||
96+
ctx.isAfterTyper || !ctx.typerState.isCommittable || // Leaks in these cases shouldn't break soundness
97+
level == Int.MaxValue // See `nestingLevel` above.
8798

8899
/** If `param` is nested deeper than `maxLevel`, try to instantiate it to a
89100
* fresh type variable of level `maxLevel` and return the new variable.
90101
* If this isn't possible, throw a TypeError.
91102
*/
92103
def atLevel(maxLevel: Int, param: TypeParamRef)(using Context): TypeParamRef =
93-
if nestingLevel(param) <= maxLevel then return param
104+
if levelOK(nestingLevel(param), maxLevel) then
105+
return param
94106
LevelAvoidMap(0, maxLevel)(param) match
95107
case freshVar: TypeVar => freshVar.origin
96108
case _ => throw new TypeError(
@@ -129,18 +141,12 @@ trait ConstraintHandling {
129141

130142
/** An approximating map that prevents types nested deeper than maxLevel as
131143
* well as WildcardTypes from leaking into the constraint.
132-
* Note that level-checking is turned off after typer and in uncommitable
133-
* TyperState since these leaks should be safe.
134144
*/
135145
class LevelAvoidMap(topLevelVariance: Int, maxLevel: Int)(using Context) extends TypeOps.AvoidMap:
136146
variance = topLevelVariance
137147

138-
/** Are we allowed to refer to types of the given `level`? */
139-
private def levelOK(level: Int): Boolean =
140-
level <= maxLevel || ctx.isAfterTyper || !ctx.typerState.isCommittable
141-
142148
def toAvoid(tp: NamedType): Boolean =
143-
tp.prefix == NoPrefix && !tp.symbol.isStatic && !levelOK(tp.symbol.nestingLevel)
149+
tp.prefix == NoPrefix && !tp.symbol.isStatic && !levelOK(tp.symbol.nestingLevel, maxLevel)
144150

145151
/** Return a (possibly fresh) type variable of a level no greater than `maxLevel` which is:
146152
* - lower-bounded by `tp` if variance >= 0
@@ -185,7 +191,7 @@ trait ConstraintHandling {
185191
end legalVar
186192

187193
override def apply(tp: Type): Type = tp match
188-
case tp: TypeVar if !tp.isInstantiated && !levelOK(tp.nestingLevel) =>
194+
case tp: TypeVar if !tp.isInstantiated && !levelOK(tp.nestingLevel, maxLevel) =>
189195
legalVar(tp)
190196
// TypeParamRef can occur in tl bounds
191197
case tp: TypeParamRef =>
@@ -431,7 +437,6 @@ trait ConstraintHandling {
431437
final def approximation(param: TypeParamRef, fromBelow: Boolean)(using Context): Type =
432438
constraint.entry(param) match
433439
case entry: TypeBounds =>
434-
val maxLevel = nestingLevel(param)
435440
val useLowerBound = fromBelow || param.occursIn(entry.hi)
436441
val inst = if useLowerBound then fullLowerBound(param) else fullUpperBound(param)
437442
typr.println(s"approx ${param.show}, from below = $fromBelow, inst = ${inst.show}")

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -637,13 +637,15 @@ trait Inferencing { this: Typer =>
637637
else if v.intValue != 0 then
638638
typr.println(i"interpolate $tvar in $state in $tree: $tp, fromBelow = ${v.intValue == 1}, $constraint")
639639
toInstantiate += ((tvar, v.intValue == 1))
640-
else if tvar.nestingLevel > ctx.nestingLevel then
641-
// Invariant: a type variable of level N can only appear
642-
// in the type of a tree whose enclosing scope is level <= N.
643-
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
644-
comparing(_.atLevel(ctx.nestingLevel, tvar.origin))
645-
else
646-
typr.println(i"no interpolation for nonvariant $tvar in $state")
640+
else comparing(cmp =>
641+
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
642+
// Invariant: The type of a tree whose enclosing scope is level
643+
// N only contains type variables of level <= N.
644+
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
645+
cmp.atLevel(ctx.nestingLevel, tvar.origin)
646+
else
647+
typr.println(i"no interpolation for nonvariant $tvar in $state")
648+
)
647649

648650
/** Instantiate all type variables in `buf` in the indicated directions.
649651
* If a type variable A is instantiated from below, and there is another

tests/pos/i14921/A_1.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import scala.compiletime.ops.int.*
2+
3+
final class Label (val getLabel: String)
4+
5+
trait ShapelessPolyfill {
6+
7+
type Represented[R] = R match {
8+
case IndexedSeq[a] => a
9+
}
10+
11+
type TupleSized[R, A, N <: Int] <: Tuple = N match {
12+
case 0 => EmptyTuple
13+
case S[n] => A *: TupleSized[R, A, n]
14+
}
15+
16+
extension [R, A, N <: Int] (s: TupleSized[R, A, N]) {
17+
def unsized: IndexedSeq[A] = s.productIterator.toIndexedSeq.asInstanceOf[IndexedSeq[A]]
18+
}
19+
20+
type Nat = Int
21+
22+
type Sized[Repr, L <: Nat] = TupleSized[Repr, Represented[Repr], L]
23+
24+
object Sized {
25+
def apply[A](a1: A): Sized[IndexedSeq[A], 1] = Tuple1(a1)
26+
}
27+
}
28+
object poly extends ShapelessPolyfill

tests/pos/i14921/B_2.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import poly.*
2+
3+
def failing: Tuple1[Label] = Sized(new Label("foo"))

0 commit comments

Comments
 (0)