Skip to content

Commit 7533217

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 7533217

File tree

4 files changed

+55
-19
lines changed

4 files changed

+55
-19
lines changed

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,26 @@ 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 or in uncommitable TyperStates
88+
// (as asserted in ProtoTypes.constrained) and is special-cased in `levelOK` below.
89+
Int.MaxValue
90+
91+
/** Is `level` <= `maxLevel` or legal in the current context? */
92+
def levelOK(level: Int, maxLevel: Int)(using Context): Boolean =
93+
level <= maxLevel ||
94+
ctx.isAfterTyper || !ctx.typerState.isCommittable || // Leaks in these cases shouldn't break soundness
95+
level == Int.MaxValue // See `nestingLevel` above.
8796

8897
/** If `param` is nested deeper than `maxLevel`, try to instantiate it to a
8998
* fresh type variable of level `maxLevel` and return the new variable.
9099
* If this isn't possible, throw a TypeError.
91100
*/
92101
def atLevel(maxLevel: Int, param: TypeParamRef)(using Context): TypeParamRef =
93-
if nestingLevel(param) <= maxLevel then return param
102+
if levelOK(nestingLevel(param), maxLevel) then
103+
return param
94104
LevelAvoidMap(0, maxLevel)(param) match
95105
case freshVar: TypeVar => freshVar.origin
96106
case _ => throw new TypeError(
@@ -129,18 +139,12 @@ trait ConstraintHandling {
129139

130140
/** An approximating map that prevents types nested deeper than maxLevel as
131141
* 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.
134142
*/
135143
class LevelAvoidMap(topLevelVariance: Int, maxLevel: Int)(using Context) extends TypeOps.AvoidMap:
136144
variance = topLevelVariance
137145

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-
142146
def toAvoid(tp: NamedType): Boolean =
143-
tp.prefix == NoPrefix && !tp.symbol.isStatic && !levelOK(tp.symbol.nestingLevel)
147+
tp.prefix == NoPrefix && !tp.symbol.isStatic && !levelOK(tp.symbol.nestingLevel, maxLevel)
144148

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

187191
override def apply(tp: Type): Type = tp match
188-
case tp: TypeVar if !tp.isInstantiated && !levelOK(tp.nestingLevel) =>
192+
case tp: TypeVar if !tp.isInstantiated && !levelOK(tp.nestingLevel, maxLevel) =>
189193
legalVar(tp)
190194
// TypeParamRef can occur in tl bounds
191195
case tp: TypeParamRef =>
@@ -431,7 +435,6 @@ trait ConstraintHandling {
431435
final def approximation(param: TypeParamRef, fromBelow: Boolean)(using Context): Type =
432436
constraint.entry(param) match
433437
case entry: TypeBounds =>
434-
val maxLevel = nestingLevel(param)
435438
val useLowerBound = fromBelow || param.occursIn(entry.hi)
436439
val inst = if useLowerBound then fullLowerBound(param) else fullUpperBound(param)
437440
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)