scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

False error in pattern matching "pattern's type TC[_] is more specialized than the right hand side expression's type TC[?]"

Open smarter opened this issue 6 months ago • 0 comments

Compiler version

3.7.0 (and latest nightly)

Minimized code

//> using scala 3.7.0

trait TC[A]

object Test:
  def test1(foo: TC[?]): Unit = foo match
    case _: TC[?] => // ok


  def test2: Unit =
    for
      (_: TC[?]) <- List[TC[?]]() // error
    do ()

Output

-- Error: try/ttp.scala:10:7 ---------------------------------------------------
10 |      (_: TC[?]) <- List[TC[?]]() // error
   |       ^^^^^^^^
   |pattern's type TC[_] is more specialized than the right hand side expression's type TC[?]
   |
   |If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern,
   |which will result in a filtering for expression (using `withFilter`).
   |This patch can be rewritten automatically under -rewrite -source 3.2-migration.

Expectation

No error, the pattern is not more specialized and will always match.

The problem is easier to debug if we give a name to the type pattern instead of using ?:

    for
      (_: TC[xx]) <- List[TC[?]]() // error
    do ()
10 |      (_: TC[xx]) <- List[TC[?]]() // error
   |       ^^^^^^^^^
   |pattern's type TC[xx] is more specialized than the right hand side expression's type TC[?]

The subtype check that fails is https://github.com/scala/scala3/blob/515c3e1c166b308554e6f9bdb5bf43c591f89652/compiler/src/dotty/tools/dotc/typer/Checking.scala#L1031 because pat is Typed(Ident(_),AppliedTypeTree(Ident(TC),List(Bind(xx,Ident(_))))) with pat.tpe being AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),trait TC),List(TypeRef(NoPrefix,type xx)))

In both cases, the problem is that pattern matching introduces a local TypeRef and symbol to represent the pattern-bound type variable (if we write ? then we get a pattern-bound type variable called _ which is a bit confusing). They are introduced in: https://github.com/scala/scala3/blob/515c3e1c166b308554e6f9bdb5bf43c591f89652/compiler/src/dotty/tools/dotc/typer/Typer.scala#L2814 for named pattern-bound type variables and in https://github.com/scala/scala3/blob/515c3e1c166b308554e6f9bdb5bf43c591f89652/compiler/src/dotty/tools/dotc/typer/Typer.scala#L3905-L3913 for wildcards (the comment claims that they are subsequently eliminated by indexPattern but this is no longer the case since https://github.com/scala/scala3/commit/7801c578301201e3c4c9663a529b3fddec3191ea which harmonized non-wildcard and wildcard handling).

This could be fixed by carefully transforming pattern types in checkIrrefutable to replace TypeRefs of pattern-bound type variables by their bounds. That feels a bit ad-hoc and inefficient, but I can't think of a more general fix that wouldn't break actual use of pattern-bound type variables.

Workaround

Define type AnyTC = TC[?] and match on AnyTC.

smarter avatar May 27 '25 17:05 smarter