Skip to content

Pattern matching with type annotations allows impossible cases #3144

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

Closed
Jacoby6000 opened this issue Sep 21, 2017 · 16 comments
Closed

Pattern matching with type annotations allows impossible cases #3144

Jacoby6000 opened this issue Sep 21, 2017 · 16 comments

Comments

@Jacoby6000
Copy link

Jacoby6000 commented Sep 21, 2017

This should not compile:

  sealed trait Foo[T]
  case class Bar[T](s: String)
  
  def shouldError[T](foo: Foo[T]): String = 
    foo match {
      case bar: Bar[T] => bar.s
    }

Not even a warning is produced, as is.

@allanrenucci
Copy link
Contributor

allanrenucci commented Sep 21, 2017

Note that the warning produced by scalac is not an exhaustivity warning:

warning: abstract type T in type pattern Bar[T] is unchecked since it is eliminated by erasure

scalac (and dotc) will not complain for:

sealed trait Foo
class Bar
  
class Test {
  def shouldError(foo: Foo) = foo match {
    case _: Bar => 1
  }
}

If Foo is a class, both scalac and dotc reject the program.

@Jacoby6000
Copy link
Author

@allanrenucci will your changes cause the sample you and I provided produce an error? I haven't poked around in dotty at all, but in general compilers should error whenever you have a situation where the type being matched on is not a super type of the case.

liufengyun added a commit to dotty-staging/dotty that referenced this issue Sep 26, 2017
liufengyun added a commit to dotty-staging/dotty that referenced this issue Sep 27, 2017
liufengyun added a commit that referenced this issue Sep 27, 2017
Fix #3144: emit warnings for unchecked type patterns
@Jacoby6000
Copy link
Author

Jacoby6000 commented Nov 1, 2017

@liufengyun I'd like this to be re-opened, since what was resolved was actually unrelated to what I was concerned about. My example was perhaps bad. @allanrenucci touched on what I was talking about.

The below example compiles, but should error:

  sealed trait Foo
  case class Bar(s: String)
  
  def shouldError(foo: Foo): String = 
    foo match {
      case bar: Bar => bar.s
    }

I'm so used to generalizing that I did an un-necessary generalization in my earlier example which confused the issue.

@smarter smarter reopened this Nov 1, 2017
@liufengyun
Copy link
Contributor

Scalac accepts the code above, as Dotty does. @Jacoby6000 can you specify why you think it should error?

@allanrenucci
Copy link
Contributor

Since Foo is sealed, is it safe to say that case bar: Bar is unreachable?

@smarter
Copy link
Member

smarter commented Jan 11, 2018

No, you could have class Bla extends Bar("") with Foo.

@allanrenucci
Copy link
Contributor

No, you could have class Bla extends Bar("") with Foo.

I agree if Foo was not sealed. But in this example, we know such a class Bla does not exist

@smarter
Copy link
Member

smarter commented Jan 11, 2018

More complicated example:

sealed trait Foo
trait Child extends Foo
case class Bar(s: String)
class Bla extends Bar("") with Child // This could be in a different file

The only thing we know for sure is that Foo has a single direct child trait Child, but we know nothing about the children of the child trait.

@liufengyun
Copy link
Contributor

Yes, it's unreachable, because the checker knows that Foo doesn't have any child. In @smarter 's example, the checker knows that Child is the only child of Foo, which could have children elsewhere.

However, the Dotty checker tries to save some cycles by avoiding checking redundancy of the first clause. I guess Scalac checker intentionally does that as well. The Dotty checker also avoids doing an intersection of the pattern with the scrutinee for performance reasons.

We can revert the optimisations to generate more warnings, but maybe after we see a real-world example where it causes problems.

@Jacoby6000
Copy link
Author

Jacoby6000 commented Jan 11, 2018

I'm really confused about why there is so much discussion around this. The discussion I've read seems to be worrying about things which do not matter.

Even in the situation that smarter specified, it should not matter. The match is specifically matching on a Foo. It should only check for Foo. It should not allow for matching on types which do not extend Foo. What is the usecase for such a thing? Why should it ever be allowed? It seems like if you ever want to do this, you're doing something wrong anyway.

Ignoring dotty semantics and focusing on what's correct, the scenario @smarter mentioned (or any scenario) should not warrant the pattern matcher treating a concrete type as Any.

The real world example where it causes problems, is anywhere people want type-safety in a pattern match.

I'd argue that supporting this just because of the with case is confusing and surprising to anybody.

@Jacoby6000
Copy link
Author

Scalac currently agrees with me: https://scastie.scala-lang.org/Jacoby6000/QrCM9aQ5QVKS5aDMn2r2Yw. Though, in my opinion this should be a type error rather than a warning.

@allanrenucci
Copy link
Contributor

Scalac agrees with you for the same reasons described above. If you remove the sealed modifier from trait Foo then case b: Baz => becomes reachable and scalac won't emit a warning.

@Jacoby6000
Copy link
Author

Doesn't this mean that pattern matching on concrete types treats that type as Any? Isn't that wrong?

@Jacoby6000
Copy link
Author

Jacoby6000 commented Jan 11, 2018

I'm starting to understand that as long as the type system has covariance, as well as intersections between types, then this must be supported. I still think it's bad, but you do what you have to do. How about we throw away subtyping and variance and just use proper ADTs? 😃

@smarter
Copy link
Member

smarter commented Jan 11, 2018

A compromise would be to disallow (foo: Foo) match { case bar: Bar => ... but allow (foo: Foo) match { case bar: Foo & Bar => ...

@Jacoby6000
Copy link
Author

I like that. To me that makes much more sense.

liufengyun added a commit that referenced this issue Jan 29, 2018
Fix #3144: more aggressively check unreachability
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

4 participants