-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Fix #1828: add warning when patternmatching on generics #1834
Conversation
else if (knownStatically) | ||
if (valueClassesOrAny) { | ||
if (selector eq defn.ObjectType) | ||
ctx.warning(i"abstract type pattern is unchecked since it is eliminated by erasure", tree.pos) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a Message class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@unchecked
should turn the warning off
if (valueClassesOrAny) tree | ||
else if (knownStatically) | ||
if (valueClassesOrAny) { | ||
if (selector eq defn.ObjectType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this limited to the selector being Object
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check the definition of valueClassesOrAny
. If the selector is also Object
here, we are dealing with a generic type param that's been erased.
We should also check to see that it was not originally Object either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that also work with class A; def unsafeCast[S <: A](a: Any) = a match { case s: S => s; case _ => ??? }
? In that case S
will be erased to A
, not Object
.
We also need warnings when pattern matching on something that will be erased like |
e27232a
to
8b7b0ef
Compare
@smarter: fixed your suggestions |
val selTypeParam = tree.args.head.tpe.widen match { | ||
case AppliedType(tycon, args) => | ||
ctx.uncheckedWarning( | ||
ErasedType(hl"""|Since type parameters are erased, you should not match on them in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's one exception to this, and that's arrays.
case xs: Array[Int]
is testable. We should (a) check that it is indeed tested and (b) adapt the error condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I'll fix this ASAP :)
A nuance of the Scalac implementation of this is that it allows you to match on List[Int] without warning if the expected type (ie, the scrutinee type) is Seq[Int]. There are a number of bugs that I've got half fixed on a branch, which will at least have some good test cases for you. I'll dig that up when I'm not on my phone. |
@retronym - thanks, that will surely come in handy! With the nuanced implementation isn't it possible for the user to define a subtype of i.e: class Foo extends Seq[Int] { ... }
(new Foo: Seq[Int]) match {
case xs: List[Int] => ... // no warning
} |
@felixmulder The rationale is that either if the erased instanceof check succeeds, def (x: Seq[Int]) = x match {
case xs: List[_] is xs.length > 0 => ... x.asInstanceOf[List[Int]].head + 1
} My WIP to better test and implement our unchecked warnings in in scala/scala#4366 |
One more thing: we can also silence the warning with |
423e6fb
to
2909603
Compare
@odersky - I think this is ready to go in once there's been a final review :) |
The current implementation gives a false warning for
We get:
This will not be easy to fix with the chosen approach. But it is essential that it is fixed. Otherwise, signalling false negatives will simply mean that people don't play attention to the real problems. |
One possible fix might be to refine the translation of the patterm matcher. Here, we currently see:
If the pattern matcher could figure out that the argument is always an
instead of currently:
|
@odersky, |
Rebased to master |
// param is: Any | AnyRef | java.lang.Object | ||
val topType = defn.ObjectType <:< arg | ||
// has @unchecked annotation to suppress warnings | ||
val hasUncheckedAnnot = arg.hasAnnotation(defn.UncheckedAnnot) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two of these conditions seem ad-hoc to me. Why exclude Object
and arrays of AnyVal
element types? I would suspect these are just symptoms and the real exclusion criteria are wider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The anyValArray
check is to make sure we allow: xs.isInstanceOf[Array[X]]
where X
is a primitive. However, before it would have allowed any value class - I constrained it to only allow checking on primitives (199fd9d).
The topType
check is to allow explicit checking on top types, eg:
xs.isInstanceOf[Array[Any]]
xs.isInstanceOf[Array[AnyRef]]
xs.isInstanceOf[Array[Object]]
@felixmulder Can you figure out why tests fail now? It seems we have some REPL test failures after rebasing. |
6974d2a
to
3dd2eb7
Compare
The tests were failing because after the rebase, the error number of |
Needs to be rebased to master. Also need to verify that
does not give a spurious error. If that's the case LGTM. |
@@ -147,7 +185,7 @@ class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer => | |||
|
|||
val inMatch = s.qualifier.symbol is Case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a correct discrimination for isInstanceOf's used in pattern matching! It would also cover case objects and enum cases. We need a more robust check. Maybe have a special, second isInstanceOf
symbol?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@odersky: This logic was proposed by @DarkDimius when I implemented the original IsInstanceOfEvaluator
, he could perhaps know of some reason why this isn't a problem. But the basic idea was that:
val
bindings created inside a pattern match desugaring would get the case
flag. This was so that we can know that they were specifically created by the PatternMatcher
.
I'd have to verify that this does indeed conflict with case object
s.
If you're sure (or if I get to verifying before you respond), then if I've understood your proposal correctly:
-
PatternMatcher
should be changed to emit a specialcaseIsInstanceOf
symbol (or similar) -
IsInstanceOfEvaluator
should intercept these and replace them with either regularisInstanceOf
checks or the evaluated expressions.should it exclusively do this for the special symbol, or do we still want the optimization to be performed elsewhere?
Last - do you want this fix included in this PR or can it be separate? The PR doesn't otherwise touch the original logic. I'd open a second PR with the implementation during the day so that we don't forget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The caseIsInstanceOf
idea looks right (exclusive or not: no idea, propose something :-) It could be a separate PR.
@odersky - added the missing |
Rebased |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general case of false negatives still needs to be solved.
case tr: TypeRef => | ||
tr.symbol.is(BindDefinedType) | ||
case TypeBounds(lo, hi) => | ||
(lo eq defn.NothingType) && (hi eq defn.AnyType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eq
usually works, but is not in general a reliable method to compare types, since hash consing is not guaranteed. I recommend to use instead:
lo.isRef(defn.NothingClass) && hi.isRef(defn.AnyClass)
@@ -357,6 +357,9 @@ class Definitions { | |||
List(AnyClass.typeRef), EmptyScope) | |||
def SingletonType = SingletonClass.typeRef | |||
|
|||
lazy val ListType: TypeRef = ctx.requiredClassRef("scala.collection.immutable.List") | |||
def ListClass(implicit ctx: Context) = ListType.symbol.asClass | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be needed (see below).
Needs |
Since |
review by: @smarter