Skip to content

Commit 0a3497b

Browse files
authored
Fix #19746: Do not follow param term refs in isConcrete. (#20015)
Term refs that reference term parameters can be substituted later by more precise ones, which can lead to different instantiations of type captures. They must therefore be considered as non concrete when following `baseType`s to captures in variant positions, like we do for type param refs and other substitutable references. We actually rewrite `isConcrete` in the process to be more based on an "allow list" of things we know to be concrete, rather than an "exclusion list" of things we know to be non-concrete. That should make it more straightforward to evaluate the validity of the algorithm.
2 parents 06066db + 0bf43b2 commit 0a3497b

File tree

5 files changed

+61
-30
lines changed

5 files changed

+61
-30
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+32-25
Original file line numberDiff line numberDiff line change
@@ -3403,37 +3403,44 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
34033403
*
34043404
* See notably neg/wildcard-match.scala for examples of this.
34053405
*
3406-
* See neg/i13780.scala and neg/i13780-1.scala for ClassCastException
3407-
* reproducers if we disable this check.
3406+
* See neg/i13780.scala, neg/i13780-1.scala and neg/i19746.scala for
3407+
* ClassCastException reproducers if we disable this check.
34083408
*/
34093409

3410-
def followEverythingConcrete(tp: Type): Type =
3411-
val widenedTp = tp.widenDealias
3412-
val tp1 = widenedTp.normalized
3413-
3414-
def followTp1: Type =
3415-
// If both widenDealias and normalized did something, start again
3416-
if (tp1 ne widenedTp) && (widenedTp ne tp) then followEverythingConcrete(tp1)
3417-
else tp1
3410+
def isConcrete(tp: Type): Boolean =
3411+
val tp1 = tp.normalized
34183412

34193413
tp1 match
34203414
case tp1: TypeRef =>
3421-
tp1.info match
3422-
case TypeAlias(tl: HKTypeLambda) => tl
3423-
case MatchAlias(tl: HKTypeLambda) => tl
3424-
case _ => followTp1
3425-
case tp1 @ AppliedType(tycon, args) =>
3426-
val concreteTycon = followEverythingConcrete(tycon)
3427-
if concreteTycon eq tycon then followTp1
3428-
else followEverythingConcrete(concreteTycon.applyIfParameterized(args))
3415+
if tp1.symbol.isClass then true
3416+
else
3417+
tp1.info match
3418+
case info: AliasingBounds => isConcrete(info.alias)
3419+
case _ => false
3420+
case tp1: AppliedType =>
3421+
isConcrete(tp1.tycon) && isConcrete(tp1.superType)
3422+
case tp1: HKTypeLambda =>
3423+
true
3424+
case tp1: TermRef =>
3425+
!tp1.symbol.is(Param) && isConcrete(tp1.underlying)
3426+
case tp1: TermParamRef =>
3427+
false
3428+
case tp1: SingletonType =>
3429+
isConcrete(tp1.underlying)
3430+
case tp1: ExprType =>
3431+
isConcrete(tp1.underlying)
3432+
case tp1: AnnotatedType =>
3433+
isConcrete(tp1.parent)
3434+
case tp1: RefinedType =>
3435+
isConcrete(tp1.underlying)
3436+
case tp1: RecType =>
3437+
isConcrete(tp1.underlying)
3438+
case tp1: AndOrType =>
3439+
isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
34293440
case _ =>
3430-
followTp1
3431-
end followEverythingConcrete
3432-
3433-
def isConcrete(tp: Type): Boolean =
3434-
followEverythingConcrete(tp) match
3435-
case tp1: AndOrType => isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
3436-
case tp1 => tp1.underlyingClassRef(refinementOK = true).exists
3441+
val tp2 = tp1.stripped.stripLazyRef
3442+
(tp2 ne tp) && isConcrete(tp2)
3443+
end isConcrete
34373444

34383445
// Actual matching logic
34393446

Diff for: tests/neg/i19746.check

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i19746.scala:9:30 -------------------------------------------------------------
2+
9 | def asX(w: W[Any]): w.X = self // error: Type Mismatch
3+
| ^^^^
4+
| Found: (self : Any)
5+
| Required: w.X
6+
|
7+
| longer explanation available when compiling with `-explain`

Diff for: tests/neg/i19746.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
trait V:
2+
type X = this.type match
3+
case W[x] => x
4+
5+
trait W[+Y] extends V
6+
7+
object Test:
8+
extension (self: Any) def as[T]: T =
9+
def asX(w: W[Any]): w.X = self // error: Type Mismatch
10+
asX(new W[T] {})
11+
12+
def main(args: Array[String]): Unit =
13+
val b = 0.as[Boolean] // java.lang.ClassCastException if the code is allowed to compile
14+
println(b)
15+
end Test

Diff for: tests/pos/TupleReverse.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ def test[T1, T2, T3, T4] =
1212
def test2[Tup <: Tuple] =
1313
summon[Reverse[Tup] =:= Reverse[Tup]]
1414

15-
def test3[T1, T2, T3, T4](tup1: (T1, T2, T3, T4)) =
16-
summon[Reverse[tup1.type] =:= (T4, T3, T2, T1)]
15+
def test3[T1, T2, T3, T4](tup1: (T1, T2, T3, T4)): Unit =
16+
val tup11: (T1, T2, T3, T4) = tup1
17+
summon[Reverse[tup11.type] =:= (T4, T3, T2, T1)]

Diff for: tests/pos/TupleReverseOnto.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def test2[Tup1 <: Tuple, Tup2 <: Tuple] =
1313
summon[ReverseOnto[EmptyTuple, Tup1] =:= Tup1]
1414
summon[ReverseOnto[Tup1, EmptyTuple] =:= Reverse[Tup1]]
1515

16-
def test3[T1, T2, T3, T4](tup1: (T1, T2), tup2: (T3, T4)) =
17-
summon[ReverseOnto[tup1.type, tup2.type] <:< (T2, T1, T3, T4)]
18-
summon[ReverseOnto[tup1.type, tup2.type] =:= T2 *: T1 *: tup2.type]
16+
def test3[T1, T2, T3, T4](tup1: (T1, T2), tup2: (T3, T4)): Unit =
17+
val tup11: (T1, T2) = tup1
18+
summon[ReverseOnto[tup11.type, tup2.type] <:< (T2, T1, T3, T4)]
19+
summon[ReverseOnto[tup11.type, tup2.type] =:= T2 *: T1 *: tup2.type]

0 commit comments

Comments
 (0)