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

ClassCastException when using named pattern with Name-based Match #22900

Open
SrTobi opened this issue Apr 1, 2025 · 3 comments
Open

ClassCastException when using named pattern with Name-based Match #22900

SrTobi opened this issue Apr 1, 2025 · 3 comments
Assignees
Labels
area:named-tuples Issues tied to the named tuples feature. area:pattern-matching itype:bug itype:soundness Soundness bug (it lets us compile code that crashes at runtime with a ClassCastException)

Comments

@SrTobi
Copy link
Contributor

SrTobi commented Apr 1, 2025

Compiler version

3.7.1-RC1-bin-20250328-d519790-NIGHTLY

Minimized code

object NameBaseExtractor {
  def unapply(x: Int): Some[(someName: Int)] = Some((someName = x + 3))
}

@main
def run = {
  val NameBaseExtractor(someName = x) = 3
  println(x)
}

Output

Exception in thread "main" java.lang.ClassCastException: class scala.Tuple1 cannot be cast to class java.lang.Integer (scala.Tuple1 is in unnamed module of loader 'app'; java.lang.Integer is in module java.base of loader 'bootstrap')
	at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
	at org.test.Test$package$.run(Test.scala:11)
	at org.test.run.main(Test.scala:9)

Note that it also prints the warning:

pattern's type (x : Int) is more specialized than the right hand side expression's type Int

If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
which may result in a MatchError at runtime.
This patch can be rewritten automatically under -rewrite -source 3.2-migration.
  val NameBaseExtractor(someName = x) = 3

But I think this is just #22899

Expectation

Prints 6

@SrTobi SrTobi added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 1, 2025
@SrTobi
Copy link
Contributor Author

SrTobi commented Apr 1, 2025

I found this while implementing the logic in IntelliJ. Pls ping me if some underlying concept changes (like disallowing named tuples in name-based matches or something)

@Gedochao Gedochao added itype:soundness Soundness bug (it lets us compile code that crashes at runtime with a ClassCastException) area:named-tuples Issues tied to the named tuples feature. area:pattern-matching and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 3, 2025
@odersky
Copy link
Contributor

odersky commented Apr 8, 2025

I noted it works if I put the pattern in an extra pair of parens:

val NameBaseExtractor((someName = x)) = 3

The problem is that without the extra parens, (someName = x) is recognized as a named argument, not a named tuple. But named arguments make no sense in extractors, so we can probably disambiguate this better.

@odersky odersky self-assigned this Apr 8, 2025
@SrTobi
Copy link
Contributor Author

SrTobi commented Apr 8, 2025

Hmm... I thought that the sentence from the named tuple SIP:

Named patterns are compatible with extensible pattern matching simply because unapply results can be named tuples.

meant exactly that it was a feature to return a named tuple from an unapply method and use named pattern matching with it.

Note, that a plain named-tuple works without problems (except the above mentioned compiler warning):

object Extractor {
  def unapply(x: Int): (someName: Int) = (someName = x)
}

@main
def run = {
  val Extractor(someName = x) = 3
  println(x)
}

So it makes sense to me to also create an extractor returning a named tuple that is not irrefutable... and the canonical way to do so is using Option.

Though I admit that there is a conflict when Some is used. See:

object NameBaseExtractor {
  def unapply(x: Int): Some[(someName: Int)] = Some((someName = x + 3))
}

@main
def run = {
  // compiles and prints '(3)'
  val NameBaseExtractor(value = x) = 3 // value is the one parameter in the case class Some
  println(x)
}

Interestingly there is another ClassCastException when using a case class instead of named tuple

case class Test(someName: Int)

object NameBaseExtractor {
  def unapply(x: Int): Some[Test] = Some(Test(someName = x + 3))
}

@main
def run = {
  // compiles but gives ava.lang.ClassCastException: class org.test.Test cannot be cast to class java.lang.Integer
  val NameBaseExtractor(value = x) = 3
  println(x)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:named-tuples Issues tied to the named tuples feature. area:pattern-matching itype:bug itype:soundness Soundness bug (it lets us compile code that crashes at runtime with a ClassCastException)
Projects
None yet
Development

No branches or pull requests

3 participants