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

Error on unreachable cases in match expressions and illegal as expressions. #2289

Merged
merged 2 commits into from
Oct 27, 2017

Conversation

mfelsche
Copy link
Contributor

@mfelsche mfelsche commented Oct 20, 2017

this checks for exhaustive matches that have an else clause, which is unreachable,
this also checks for cases that are unreachable because previous cases are already exhaustive,

this also affects the 'as' clause as it is rewritten to a match internally:

this checks for 'as' clauses that try to cast to the same type or a subtype
which would result in an exhaustive match internally and can be considered illegal use of 'as'.

This is a breaking change, but for good. If it affects any pony codebase, then it had a possible source of bugs.

The following examples will be invalid with this PR:

class Foo
  fun ex_match(a: (String| Bool)): String => 
    match a
    | let a: Stringable => a.string()
    | let b: Bool => if b then "true" else "false" end // unreachable already
    else
      "unreachable"
    end
class Foo
   fun subtype_cast(a: String): Stringable ? =>
     a as Stringable // useless as, is actually not partial

   fun eq_cast(a: Array[String]) =>
     let tmp = a as Array[String] // no as needed
    ...
class Foo[A]
  fun alias_cast(a: A!): A ? =>
    a as A

@mfelsche mfelsche added the changelog - changed Automatically add "Changed" CHANGELOG entry on merge label Oct 20, 2017
@SeanTAllen
Copy link
Member

Before this gets merged, can you write up release notes for this and add to this PR as a comment.

What I'm looking for is an explanation of invalid code that folks might have in the wild and how they fix it. Also an explanation of why this breaking change is required, and what "eek!" it fixes.

Thanks.

this checks for exhaustive matches that have an else clause, which is unreachable,
this also checks for cases that are unreachable because previous cases are already exhaustive,

this also affects the 'as' clause as it is rewritten to a match internally:

this checks for 'as' clauses that try to cast to the same type or a subtype
which would result in an exhaustive match internally and can be considered illegal use of 'as'.
@mfelsche
Copy link
Contributor Author

Possible Release Notes Entry:


This change is adding some sanity checks to usages of match and as expressions.
It does two things:

Match Expressions

It validates that there is no unreachable code left in your match expressions. This was a subtle source of bugs and left dead code linger in your codebase. Unreachable cases or else clauses are triggering a compiler error now.

Example:

class Foo
  fun ex_match(a: (String| Bool)): String => 
    match a
    | let a: Stringable => a.string()
    | let b: Bool => if b then "true" else "false" end // unreachable already
    else
      "unreachable"
    end

The second branch and the else clause are both unreachable and thus can (and should) be safely removed. In some cases it might instead make sense to rewrite the match, reordering the cases from more specific checks to less specific ones.

As Expressions

It also validates correct use of as, which should only be used to safely increase the specificity of an object's type, that is casting. Previously it was possible to cast to the type of the expression to be casted or to one of its subtypes, which can be achieved by simple assignment. Using as here introduces a false positive partiality. Those incorrect or unnecessary usages of as trigger a compiler error with this change.

Example:

class Foo
   fun subtype_cast(a: String): Stringable ? =>
     a as Stringable // useless as, is actually not partial

   fun eq_cast(a: Array[String]) =>
     let tmp = a as Array[String] // no as needed
     ...

This error can easily be fixed by removing the as as it is not necessary in both cases.

@mfelsche
Copy link
Contributor Author

This finally fixes #2253

@@ -549,6 +549,23 @@ bool expr_as(pass_opt_t* opt, ast_t** astp)
return false;
}

if(is_eqtype(expr_type, type, NULL, opt))
Copy link
Member

Choose a reason for hiding this comment

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

Note that the definition of is_eqtype is equivalent to an is_subtype call in each direction:

bool is_eqtype(ast_t* a, ast_t* b, errorframe_t* errorf, pass_opt_t* opt)
{
  return is_subtype(a, b, errorf, opt) && is_subtype(b, a, errorf, opt);
}

Therefore, this strategy of testing is_eqtype, then testing is_subtype is kind of wasteful - because we are doing three is_subtypechecks every time, even in the "happy path". I've found that excess is_subtype calls can sometimes be very costly to compilation time and memory.

I'd suggest an approach of testing is_subtype first, then if that is true, continuing on to test is_subtype in the other direction to determine the appropriate error message. This approach would only have one is_subtype check in the "happy path", and two is_subtype checks in the error path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thank you!

@jemc jemc changed the title error on unreachable code in match expressions and illegal as expressions Error on unreachable cases in match expressions and illegal as expressions. Oct 27, 2017
@mfelsche mfelsche merged commit e256eed into master Oct 27, 2017
ponylang-main added a commit that referenced this pull request Oct 27, 2017
@mfelsche mfelsche deleted the exhaustive-match-else branch October 27, 2017 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog - changed Automatically add "Changed" CHANGELOG entry on merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants