Skip to content

Fix #3144: emit warnings for unchecked type patterns #3156

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

Merged
merged 9 commits into from
Sep 27, 2017

Conversation

liufengyun
Copy link
Contributor

Fix #3144: emit warnings for unchecked type patterns

else {
val ignoreWarning = args.forall(p => p.typeSymbol.is(BindDefinedType) || p.isInstanceOf[TypeBounds])
if (!ignoreWarning)
ctx.warning(UncheckedTypePattern("type arguments are not unchecked since they are eliminated by erasure"), pos)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you meant to write "are not checked"

@allanrenucci
Copy link
Contributor

I think there are two separate issues described in #3144:

  • Missing unchecked warning for type pattern that are eliminated by erasure. This seems to be fixed in this PR. Do we want to support the @unchecked annotation like scalac?
class Test {
  def f(x: Any): Int = x match {
    case xs: List[Int] @unchecked => xs.head
  }
}
  • Unreachable case for unrelated type when one the type is a sealed trait. Maybe I am missing something but the case seems unreachable to me (scalac doesn't emit a warning either).
sealed trait Foo
class Bar
  
class Test {
  def shouldError(foo: Foo) = foo match {
    case _: Bar => 1
  }
}

@liufengyun
Copy link
Contributor Author

@allanrenucci The second is not an issue, as there could be a class like the follows:

class Ruu extends Bar with Foo

@allanrenucci
Copy link
Contributor

@liufengyun But Foo is sealed, so we know there is no such class Ruu, right?

@liufengyun
Copy link
Contributor Author

Ah I see, currently the algorithm just assumes the first case is always reachable to save some computation. We could also check if the first branch is reachable or not, but I'm not sure if it's worth it.

BTW, we've checks for hopeless matches, which are located in IsInstanceOfEvaluator.

@allanrenucci
Copy link
Contributor

Ah I see, currently the algorithm just assumes the first case is always reachable to save some computation.

If Foo is a class dotc gives me an error for the first case though:

-- Error: tests/allan/Test.scala:6:9 -------------------------------------------
6 |    case _: Foo => 1
  |         ^
  |      this case is unreachable since class Bar and class Foo are unrelated

one error found

@liufengyun
Copy link
Contributor Author

@allanrenucci That check is from Erasure, not from the exhaustivity check. The Erasure handles it more generally by checking if isInstanceOf is sensical.

@allanrenucci
Copy link
Contributor

You might want to add a test case for

case xs: List[Int @unchecked] =>

class Test {
def f(x: Any): Int = x match {
case xs: List[Int] @unchecked => xs.head
case xs: List[Int @unchecked] => xs.head
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should extract this case into another function. This case is unreachable here

@@ -880,6 +880,16 @@ object messages {
|"""
}

case class UncheckedTypePattern(msg: String)(implicit ctx: Context)
extends Message(UncheckedTypePatternID) {
val kind = "Unchecked Type Pattern"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very specific kind, kinds are supposed to apply to more than one message, I guess here it could be "Pattern Match Exhaustivity" (kinds should also be an enum, but that's a separate issue).


val explanation =
hl"""|Type arguments and type refinements are erased during compile time, thus it's
|impossible to check them at run-time.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explanation could mention possible fixes: either replace the type arguments by _ or use @unchecked.

case _ =>
debug.println(s"unknown pattern: $pat")
Empty
}

/* Erase a type binding according to erasure semantics in pattern matching */
def erase(tp: Type): Type = tp match {
def erase(tp: Type)(implicit warn: String => Unit): Type = tp match {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing an implicit around, you could have an outer function which defines warn and an inner recursive function which uses it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smarter thanks a lot, I've addressed other comments. For this one, doing the refactoring would require to pass an additional Position to erase, which will make the API a little ugly.

@liufengyun liufengyun merged commit c468843 into scala:master Sep 27, 2017
@liufengyun liufengyun deleted the fix-3144 branch September 27, 2017 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants