Skip to content

Commit

Permalink
Fix #19746: Do not follow param term refs in isConcrete. (#20015)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sjrd authored Apr 4, 2024
2 parents 06066db + 0bf43b2 commit 0a3497b
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 30 deletions.
57 changes: 32 additions & 25 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3403,37 +3403,44 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
*
* See notably neg/wildcard-match.scala for examples of this.
*
* See neg/i13780.scala and neg/i13780-1.scala for ClassCastException
* reproducers if we disable this check.
* See neg/i13780.scala, neg/i13780-1.scala and neg/i19746.scala for
* ClassCastException reproducers if we disable this check.
*/

def followEverythingConcrete(tp: Type): Type =
val widenedTp = tp.widenDealias
val tp1 = widenedTp.normalized

def followTp1: Type =
// If both widenDealias and normalized did something, start again
if (tp1 ne widenedTp) && (widenedTp ne tp) then followEverythingConcrete(tp1)
else tp1
def isConcrete(tp: Type): Boolean =
val tp1 = tp.normalized

tp1 match
case tp1: TypeRef =>
tp1.info match
case TypeAlias(tl: HKTypeLambda) => tl
case MatchAlias(tl: HKTypeLambda) => tl
case _ => followTp1
case tp1 @ AppliedType(tycon, args) =>
val concreteTycon = followEverythingConcrete(tycon)
if concreteTycon eq tycon then followTp1
else followEverythingConcrete(concreteTycon.applyIfParameterized(args))
if tp1.symbol.isClass then true
else
tp1.info match
case info: AliasingBounds => isConcrete(info.alias)
case _ => false
case tp1: AppliedType =>
isConcrete(tp1.tycon) && isConcrete(tp1.superType)
case tp1: HKTypeLambda =>
true
case tp1: TermRef =>
!tp1.symbol.is(Param) && isConcrete(tp1.underlying)
case tp1: TermParamRef =>
false
case tp1: SingletonType =>
isConcrete(tp1.underlying)
case tp1: ExprType =>
isConcrete(tp1.underlying)
case tp1: AnnotatedType =>
isConcrete(tp1.parent)
case tp1: RefinedType =>
isConcrete(tp1.underlying)
case tp1: RecType =>
isConcrete(tp1.underlying)
case tp1: AndOrType =>
isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
case _ =>
followTp1
end followEverythingConcrete

def isConcrete(tp: Type): Boolean =
followEverythingConcrete(tp) match
case tp1: AndOrType => isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
case tp1 => tp1.underlyingClassRef(refinementOK = true).exists
val tp2 = tp1.stripped.stripLazyRef
(tp2 ne tp) && isConcrete(tp2)
end isConcrete

// Actual matching logic

Expand Down
7 changes: 7 additions & 0 deletions tests/neg/i19746.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg/i19746.scala:9:30 -------------------------------------------------------------
9 | def asX(w: W[Any]): w.X = self // error: Type Mismatch
| ^^^^
| Found: (self : Any)
| Required: w.X
|
| longer explanation available when compiling with `-explain`
15 changes: 15 additions & 0 deletions tests/neg/i19746.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
trait V:
type X = this.type match
case W[x] => x

trait W[+Y] extends V

object Test:
extension (self: Any) def as[T]: T =
def asX(w: W[Any]): w.X = self // error: Type Mismatch
asX(new W[T] {})

def main(args: Array[String]): Unit =
val b = 0.as[Boolean] // java.lang.ClassCastException if the code is allowed to compile
println(b)
end Test
5 changes: 3 additions & 2 deletions tests/pos/TupleReverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ def test[T1, T2, T3, T4] =
def test2[Tup <: Tuple] =
summon[Reverse[Tup] =:= Reverse[Tup]]

def test3[T1, T2, T3, T4](tup1: (T1, T2, T3, T4)) =
summon[Reverse[tup1.type] =:= (T4, T3, T2, T1)]
def test3[T1, T2, T3, T4](tup1: (T1, T2, T3, T4)): Unit =
val tup11: (T1, T2, T3, T4) = tup1
summon[Reverse[tup11.type] =:= (T4, T3, T2, T1)]
7 changes: 4 additions & 3 deletions tests/pos/TupleReverseOnto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def test2[Tup1 <: Tuple, Tup2 <: Tuple] =
summon[ReverseOnto[EmptyTuple, Tup1] =:= Tup1]
summon[ReverseOnto[Tup1, EmptyTuple] =:= Reverse[Tup1]]

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

0 comments on commit 0a3497b

Please sign in to comment.