Skip to content

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

Open
@smarter

Description

@smarter

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

|| pt.stripNamedTuple <:< pat.tpe
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:

val sym = newPatternBoundSymbol(name, symTp, tree.span)
for named pattern-bound type variables and in
case tree1: TypeBoundsTree =>
// Associate a pattern-bound type symbol with the wildcard.
// The bounds of the type symbol can be constrained when comparing a pattern type
// with an expected type in typedTyped. The type symbol and the defining Bind node
// are eliminated once the enclosing pattern has been typechecked; see `indexPattern`
// in `typedCase`.
val boundName = WildcardParamName.fresh().toTypeName
val wildcardSym = newPatternBoundSymbol(boundName, tree1.tpe & pt, tree.span)
untpd.Bind(boundName, tree1).withType(wildcardSym.typeRef)
for wildcards (the comment claims that they are subsequently eliminated by indexPattern but this is no longer the case since 7801c57 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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions