Skip to content

Closes #4300 adds better worded pattern match unreachability warning #4552

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

Conversation

zoltanelek
Copy link
Contributor

  • Used when isInstanceOf is called inside a match case
  • The new message is "this case will always be taken because the scrutinee has type <type>"
  • Highlights the position of x: Int instead of x

…ning

  - Used when isInstanceOf is called inside a match case
  - The new message is "this case will always be taken because the scrutinee has type `<type>`"
  - Highlights the position of `x: Int` instead of `x`
Copy link
Member

@dottybot dottybot left a comment

Choose a reason for hiding this comment

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

Hello, and thank you for opening this PR! 🎉

All contributors have signed the CLA, thank you! ❤️

Have an awesome day! ☀️

if (inMatch)
ctx.warning(
em"this case will always be taken because the scrutinee has type `$testType`",
tree.pos)
Copy link
Contributor

Choose a reason for hiding this comment

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

This error message doesn't make sense for the following test case:

object Foo {
  def unapply(x: Int): Option[Int] = if (x % 2 == 0) Some(x) else None
}

class Test {
  def foo(a: Int) = a match { case Foo(x: Int) => x }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

  • To address both @zoltanelek and @liufengyun's concerns, I'd at least say "this pattern will always succeed", where "this" refers to x: Int (as shown by the position) — I'd prefer in fact "the highlighted pattern match".

I think both this message and the current one can still be a bit confusing because they might only refer to one pattern out of many nested ones, but maybe that can be clarified in the message explanation.

  • I'd also please keep showing both $foundCls and $testCls, since in the presence of type aliases they might be nontrivial to deduce from the scrutinee type and the pattern match.

  • Separately, "since $foundCls is a subclass of $testCls" says more than "because the scrutinee has type $testType": who wrote the code and fixes the warning might know the scrutinee's type, but what they probably missed is that $foundCls is a subclass of $testCls.

Copy link
Contributor

@Blaisorblade Blaisorblade left a comment

Choose a reason for hiding this comment

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

This must be revised to address the comments. @zoltanelek do you think you can give it a try?

@zoltanelek
Copy link
Contributor Author

@Blaisorblade sure!

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

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

@zoltanelek
Copy link
Contributor Author

zoltanelek commented Jun 8, 2018

Hey guys! I went with
the type check in the highlighted part of the pattern match case statement will always succeed, since $foundCls is a subclass of $testCls

I think this is much more meaningful when matching on tuples.
E.g. here: "the highlighted pattern match will always succeed" would be actually false, only the first parameter, x is matched

|def foo(a: (Int,Int)) = a match { case (x: Int, y: String) => x }
|                                        ^^^^^^
|the type check in the highlighted part of the pattern match case statement will always succeed, since `class Int` is a subclass of `class Int`

And 'the type check in the highlighted part' would not imply that there can't be an unapply behind the scenes, which will fail to match.

scala> object Foo {
           def unapply(x: Int): Option[Int] = if (x % 2 == 0) Some(x) else None
         } 
// defined object Foo

scala> class Test {
           def foo(a: Int) = a match { case Foo(x: Int) => x }
         } 
2 |    def foo(a: Int) = a match { case Foo(x: Int) => x }
  |                                         ^^^^^^
  |the type check in the highlighted part of the pattern match case statement will always succeed, since `class Int` is a subclass of `class Int`
// defined class Test

Copy link
Contributor

@liufengyun liufengyun left a comment

Choose a reason for hiding this comment

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

LGTM, thanks a lot @zoltanelek 🎉

BTW, could you please squash the history to keep one commit?

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

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

@liufengyun @Blaisorblade Why not something like:

The highlighted type test will always succeed since the scrutiny type (${expr.tpe}) is a subtype of ${testType}

This wording is applicable to both isIntanceOf test and type test in pattern match. One could go into more detail (if needed) in the explanation part of the message

else
ctx.warning(
em"this will always yield true, since `$foundCls` is a subclass of `$testCls`",
expr.pos)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would merge the two together by making inMatch a parameter of the message class and name the class something like TypeTestAlwaysSucceeds

@@ -2117,4 +2117,38 @@ object messages {
val explanation: String = ""
}

case class PatternMatchAlwaysSucceeds(foundCls: Symbol, testCls: Symbol)(implicit ctx: Context) extends Message(PatternMatchAlwaysSucceedsID) {
val kind = "Syntax"
val msg: String =
Copy link
Contributor

Choose a reason for hiding this comment

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

type annotation not needed

hl"""
|The type check in the highlighted part of the pattern match case statement will always succeed,
|since `$foundCls` is a subclass of `$testCls`.
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

Rewrite as:

hl"""...
    |..."""

Otherwise the resulting string has unnecessary line breaks before and after

|since `$foundCls` is a subclass of `$testCls`.
"""
val codeExampleValidWarning = "def foo(a: Int) = a match { case x: Int => x }"
val codeExampleDisregardWarning =
Copy link
Contributor

Choose a reason for hiding this comment

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

The user should not disregard the warning. The warning is legitimate, he should have written case Foo(x) instead of case Foo(x: Int)

| def foo(a: Int) = a match { case Foo(x: Int) => x }
|}
"""
val explanation: String =
Copy link
Contributor

Choose a reason for hiding this comment

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

I would remove the current explanation. I don't think it gives you more information than the msg.

@zoltanelek
Copy link
Contributor Author

@liufengyun sure, I'll squash them when everybody approves.

@liufengyun
Copy link
Contributor

@Blaisorblade @allanrenucci I think it looks good now, do you have more comments?

@allanrenucci allanrenucci merged commit c13422f into scala:master Jun 13, 2018
@allanrenucci
Copy link
Contributor

Thanks @zoltanelek!

@zoltanelek zoltanelek deleted the 4300_zoltanelek-better_worded_pattern_match_reachability_warning branch June 13, 2018 14:46
@zoltanelek
Copy link
Contributor Author

Cool! Thanks for the help guys!

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.

5 participants