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

warn on refutable patterns in pattern valdef #5898

Closed
scabug opened this issue Jun 3, 2011 · 13 comments
Closed

warn on refutable patterns in pattern valdef #5898

scabug opened this issue Jun 3, 2011 · 13 comments

Comments

@scabug
Copy link

scabug commented Jun 3, 2011

Value/variable definitions with patterns expand into unsafe matches in cases where a safe alternative is feasible. For example, all of the following definitions compile without even a warning, but inevitably fail with MatchErrors at runtime:

val (a,b) = 1: Any
val (a,b) = Person("f", "l"): Product // with Person defined as `case class Person(first: String, last: String)`
val Person(f, l) = ("f", "l"): Product 

Desired behavior:
The definitions above should not compile. In cases where unsafe pattern matching cannot be avoided (e.g. val x :: xs = list), the compiler should issue a warning. Additional syntax could be used to eliminate the warning (e.g. val case x :: xs = list) or to indicate the desire for an unsafe pattern match (e.g. val case (a,b) = x: Any). The existing behavior is per spec, but given the violation of type safety it seems appropriate to consider this issue a "Bug" rather than an "Improvement".

Proposed solution:
To facilitate the desired behavior, unapply methods should declare Some as their return type whenever possible. In particular, the generated unapply methods for case classes should guarantee a Some return value. For example, Tuple2.unapply could be written

def unapply[T1, T2](x: Tuple2[T1, T2]): Some[Tuple2[T1,T2]] = Some(x)

Then, when an appropriate unapply method is available (i.e. one that is guaranteed to return a Some), the expansions of value/variable definitions would call these methods directly rather than resorting to pattern matching. Thus

val (a,b) = expr
val Person(n) = expr2

would expand to

val $ab = Tuple2.unapply(expr).x
val a = $ab._1
val b = $ab._2
val n = Person.unapply(expr2).x

When an appropriate unapply method is not available (i.e. unapply returns an Option), the compiler would expand the definition into a pattern match but issue a warning.

@scabug
Copy link
Author

scabug commented Jun 3, 2011

Imported From: https://issues.scala-lang.org/browse/SI-5898?orig=1
Reporter: Aaron Novstrup (anovstrup)
See #140, #900

@scabug
Copy link
Author

scabug commented Jun 4, 2011

Aaron Novstrup (anovstrup) said:
Related discussion:
Is MatchError becoming Scala's NullPointerException?
type matching in for comprehensions

@scabug
Copy link
Author

scabug commented Jun 19, 2011

Aaron Novstrup (anovstrup) said:
I've looked at this more closely and realized that a major premise of my proposal is flawed: namely, that many extractors could guarantee a Some return value. Because of our old friend null, this is not possible. For case classes, for example, the generated unapply method returns None when passed a null.

Some alternative solutions:

  • Type check these definitions more strictly, and require additional syntax (e.g. the case keyword) to obtain the (current) less strict type checking
  • Annotate unapply methods such as Tuple2.unapply to indicate that they are guaranteed to return a Some as long as the argument is non-null, and directly invoke unapply (as in the original proposal) when the annotation is present. If the rhs is null, throw an appropriate exception (preferably not MatchError)

@scabug
Copy link
Author

scabug commented Jun 20, 2011

@LilyLambda said:
This is not a type safety violation. If it fails at runtime with a MatchError then there's no safety problem. As for detecting necessarily failing patterns at compile time ... well, that's a major area of research in static analysis. The compiler does not even attempt, in general, to detect failing matches in constructions (like val assignments) where only one pattern is given. I think this is very much an improvement and not a bug.

@scabug
Copy link
Author

scabug commented Jun 20, 2011

Aaron Novstrup (anovstrup) said (edited on Jun 20, 2011 3:24:44 AM UTC):
@yuvi I understand that there a different notions of type safety, but in the context of a statically typed language such as Java or Scala it is pretty common to view unexpected runtime type errors as type safety violations.

Semantics aside (I'm more concerned with the issue than with the title), the problem with these constructs is that there is no clue in the syntax that unchecked matching is going on and that a runtime error may arise. Furthermore, there is no reason that unchecked matching should be going on (in the very common, specific cases considered here, such as tuples and case classes).

Having a match error occur in what appears to be a perfectly safe assignment is similar to having something like a MissingMethodError occur on a method call when it is known at compile time that the method is not a member of the target object's type.

@scabug
Copy link
Author

scabug commented Jun 20, 2011

@SethTisue said:
"val (a,b) = 1" in fact fails to compile.

"val (a,b) = 1: Any" is pretty strange code. The ": Any" tells the compiler, "forget
everything you know about this expression, just treat it as Any". Then when compilation
succeeds, it's a little funny to complain that the compiler didn't use its knowledge of
what "1" is, when you explicitly told it not to.

Why I would be using practically useless, vague types like Any and Product anyway...?

I'm not critiquing your overall goals here, I'm just saying you need more compelling
use cases if you want to build support for changes in this area.

@scabug
Copy link
Author

scabug commented Jun 20, 2011

Aaron Novstrup (anovstrup) said (edited on Jun 20, 2011 9:27:18 PM UTC):
@seth I agree that these examples aren't compelling -- my intent was to give examples that would exhibit the behavior with the least amount of extraneous cruft to wade through (i.e. simplicity over realism). My related comments on the mailing list make the case a bit more clearly:

https://groups.google.com/d/topic/scala-user/21IbHRhgxQU/discussion

"The real danger, of course, is that the right-hand side need not be
typed explicitly. The type may be inferred locally, or it may be the
return type of a method, etc. A refactoring could easily lead to
runtime failures."

The complaint is not that the compiler does not use its knowledge of what "1" is, but rather that it doesn't use its knowledge of what Tuple2 is. Any unintended widening of the static type on the right-hand side could lead to runtime failure. Here's a more realistic example (although it fails the simplicity test):

class Character(name: String)
case class Player(name: String, age: Int, gender: Gender) extends Character(name)
case class NPC(name: String, species: Species, language: Lang) extends Character(name)

// Originally, getCharacter returns a Player.  Later, it's modified 
// to return a Character. After the change, the following line of 
// code will still compile but may fail at runtime.  
val Player(_, a, g) = getCharacter("Dingus")

The original developer intended his code as shorthand for

val c = getCharacter("Dingus")
val a = c.age
val g = c.gender

which would fail to compile after the change in getCharacter.

@scabug
Copy link
Author

scabug commented Jun 20, 2011

Aaron Novstrup (anovstrup) said (edited on Jun 20, 2011 9:31:50 PM UTC):
Another (more) realistic example:

// original
List(1 -> "a", 2 -> "b") map { p => val (a,b) = p; a + " -> " + b }

// changed: oops!
List(1 -> "a", 2 -> "b", 3) map { p => val (a,b) = p; a + " -> " + b }

@SethTisue
Copy link
Member

@som-snytt is scala/scala#7526 something you plan to pick up again, or is this up for grabs?

@som-snytt
Copy link

@SethTisue that one was resubmitted and merged. I don't remember, but I don't think it's enough to close the ticket.

@SethTisue
Copy link
Member

oh, scala/scala#7687

@som-snytt
Copy link

@SethTisue sorry to make work for you. Unless you need work? Probably you don't need work.

@SethTisue
Copy link
Member

it beats watching TV

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