Skip to content

Commit 0bf43b2

Browse files
committed
Fix #19746: Do not follow param term refs in isConcrete.
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.
1 parent 7171211 commit 0bf43b2

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
@@ -3360,37 +3360,44 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
33603360
*
33613361
* See notably neg/wildcard-match.scala for examples of this.
33623362
*
3363-
* See neg/i13780.scala and neg/i13780-1.scala for ClassCastException
3364-
* reproducers if we disable this check.
3363+
* See neg/i13780.scala, neg/i13780-1.scala and neg/i19746.scala for
3364+
* ClassCastException reproducers if we disable this check.
33653365
*/
33663366

3367-
def followEverythingConcrete(tp: Type): Type =
3368-
val widenedTp = tp.widenDealias
3369-
val tp1 = widenedTp.normalized
3370-
3371-
def followTp1: Type =
3372-
// If both widenDealias and normalized did something, start again
3373-
if (tp1 ne widenedTp) && (widenedTp ne tp) then followEverythingConcrete(tp1)
3374-
else tp1
3367+
def isConcrete(tp: Type): Boolean =
3368+
val tp1 = tp.normalized
33753369

33763370
tp1 match
33773371
case tp1: TypeRef =>
3378-
tp1.info match
3379-
case TypeAlias(tl: HKTypeLambda) => tl
3380-
case MatchAlias(tl: HKTypeLambda) => tl
3381-
case _ => followTp1
3382-
case tp1 @ AppliedType(tycon, args) =>
3383-
val concreteTycon = followEverythingConcrete(tycon)
3384-
if concreteTycon eq tycon then followTp1
3385-
else followEverythingConcrete(concreteTycon.applyIfParameterized(args))
3372+
if tp1.symbol.isClass then true
3373+
else
3374+
tp1.info match
3375+
case info: AliasingBounds => isConcrete(info.alias)
3376+
case _ => false
3377+
case tp1: AppliedType =>
3378+
isConcrete(tp1.tycon) && isConcrete(tp1.superType)
3379+
case tp1: HKTypeLambda =>
3380+
true
3381+
case tp1: TermRef =>
3382+
!tp1.symbol.is(Param) && isConcrete(tp1.underlying)
3383+
case tp1: TermParamRef =>
3384+
false
3385+
case tp1: SingletonType =>
3386+
isConcrete(tp1.underlying)
3387+
case tp1: ExprType =>
3388+
isConcrete(tp1.underlying)
3389+
case tp1: AnnotatedType =>
3390+
isConcrete(tp1.parent)
3391+
case tp1: RefinedType =>
3392+
isConcrete(tp1.underlying)
3393+
case tp1: RecType =>
3394+
isConcrete(tp1.underlying)
3395+
case tp1: AndOrType =>
3396+
isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
33863397
case _ =>
3387-
followTp1
3388-
end followEverythingConcrete
3389-
3390-
def isConcrete(tp: Type): Boolean =
3391-
followEverythingConcrete(tp) match
3392-
case tp1: AndOrType => isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
3393-
case tp1 => tp1.underlyingClassRef(refinementOK = true).exists
3398+
val tp2 = tp1.stripped.stripLazyRef
3399+
(tp2 ne tp) && isConcrete(tp2)
3400+
end isConcrete
33943401

33953402
// Actual matching logic
33963403

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)