Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit type specification in match worsens type checks #20705

Open
mohe2015 opened this issue Jun 20, 2024 · 1 comment
Open

Explicit type specification in match worsens type checks #20705

mohe2015 opened this issue Jun 20, 2024 · 1 comment

Comments

@mohe2015
Copy link

Compiler version

3.3.3

Minimized code

@main
def main(): Unit = {
  val example: Tuple2[Int | String, Int | String] = ("test", 2)
  example match {
    case newExample@Tuple2(a: Int, b: Int) => println("hi")
    case other => println("unmatched")
  }
}

prints "unmatched" but

@main
def main(): Unit = {
  val example: Tuple2[Int | String, Int | String] = ("test", 2)
  example match {
    case newExample@Tuple2[Int, Int](a: Int, b: Int) => println("hi")
    case other => println("unmatched")
  }
}

throws a ClassCastException.

Expectation

While it's understandable that the compiler can't infer the types correctly (this seems to be #15972), I think it's unexpected that just adding a type specifier removes the runtime type checks.

@mohe2015 mohe2015 added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Jun 20, 2024
@dwijnand dwijnand self-assigned this Jun 20, 2024
@dwijnand dwijnand added area:pattern-matching and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jun 20, 2024
@LucySMartin
Copy link

I don't nessaserilly agree with the assessment that this is removing an additional check - its simply erroring before you get to the check that you have in both.

If we break down the logic a bit more, in the first example this is trying to break down example into a tuple, and then attempting to pattern match inside of it.

In the second example, a and b do not exist unless you have already successfully run extractors on a Tuple2[Int, Int] - which is going to fail.

Imagine a case where the inner extractor was not a simple type check, but passing through to some custom unapply method, which accepted only positive Ints, and ascii strings. In this hypothetical, the user in the first case would be asking "Do I have two fields which are either posative ints or ascii strings" and in the second is asking "Do I have exactly two posative ints".

We could also consider examples which were more complex than a simple tuple. For example class Foo[T](foo: Int | T). In this case an extractor of Foo[Int](a: int) is clearly stronger than Foo(a:Int) - and it would be up to the implementation of Foo.unapply to determine how this further refinement was handled. In the case of case classes (and thus tuples) - unapply promises to preserve known types - and thus has to fail when the types is wrong.

If we wanted to make this work it would be opening up a whole heap of things in constructs like val Tuple2[Int,Int](a, b) = (???,???) with a and b not getting the fully validated type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants