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

Add non_exhaustive_omitted_patterns lint related to rfc-2008-non_exhaustive #86809

Merged
merged 1 commit into from
Sep 16, 2021

Conversation

DevinR528
Copy link
Contributor

@DevinR528 DevinR528 commented Jul 2, 2021

Fixes: #84332

This PR adds non_exhaustive_omitted_patterns, an allow by default lint that is triggered when a non_exhaustive type is missing explicit patterns. The warning or deny attribute can be put above the wildcard _ pattern on enums or on the expression for enums or structs. The lint is capable of warning about multiple types within the same pattern. This lint will not be triggered for if let .. patterns.

// crate A
#[non_exhaustive]
pub struct Foo {
    a: u8,
    b: usize,
}
#[non_exhaustive]
pub enum Bar {
    A(Foo),
    B,
}

// crate B
#[deny(non_exhaustive_omitted_patterns)]
match Bar::B {
    Bar::B => {}
    _ => {}
}

#[warn(non_exhaustive_omitted_patterns)]
let Foo { a, .. } = Foo::default();

#[deny(non_exhaustive_omitted_patterns)]
match Bar::B {
    // triggers for Bar::B, and Foo.b
    Bar::A(Foo { a, .. }) => {}
    _ => {}
}

@rust-highfive
Copy link
Collaborator

r? @davidtwco

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 2, 2021
@rust-log-analyzer

This comment has been minimized.

@LeSeulArtichaut
Copy link
Contributor

LeSeulArtichaut commented Jul 2, 2021

Is there a way to check if an AdtDef is local to the place it's being used

I think you can use adt_def.did.is_local()

In the clippy issue it was said there was a better way to find missing variants

The exhaustiveness checker (the thing that checks that matches are exhaustive, FWIW) lives in rustc_mir_build::thir::pattern, but I don't know if it exposes public APIs. I haven't really read your code or what the lint should do, but what you might to do instead is emit the lint in the exhaustiveness checker itself, instead of in a lint pass.

@DevinR528
Copy link
Contributor Author

DevinR528 commented Jul 5, 2021

Perfect just what I was looking for thanks! I'll leave what I have in lints but start/continue working in the mod you mentioned above. What would make sense to me is add a call check_for_reachable_patterns_in_non_exhaustive here and then adding logic to this func for enums?

Seems to me if we could add a way to ignore _ wildcard matches in non_exhaustive struct/enum patterns we could get a list of UsefulnessReport::non_exhaustiveness_witnesses that has all the patterns missed for the non_exhaustive struct/enum. If we could make a new compute_match_usefulness maybe? I have a vague understanding of the code and where to at least look into doing this but if you have any pointers on how to do this that'd help!

@LeSeulArtichaut
Copy link
Contributor

Okay, I actually read #84332 this time. Just to make sure I understand correctly: the goal is to lint on matches that are non exhaustive if you remove the _ wildcard, not just check that every variant is mentioned, correct?

Or put another way, this should trigger the lint?

/// Latins are still figuring this out, please stand by
#[non_exhaustive]
enum Alphabet {
    A,
    B,
}

match foo {
    Alphabet::A => {}
    // This has a guard, so the match isn't exhaustive, but both A and B are mentioned
    Alphabet::B if guard => {}
    _ => {}
}

@DevinR528
Copy link
Contributor Author

DevinR528 commented Jul 6, 2021

Yeah that should trigger with

match foo {
    Alphabet::A => {}
    // This has a guard, so the match isn't exhaustive, but both A and B are mentioned
    Alphabet::B if guard => {}
    #[deny(reachable)]
    _ => {}
}

Copy link
Member

@davidtwco davidtwco left a comment

Choose a reason for hiding this comment

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

This is a good start. I've not checked the logic in too much detail, I will do after some initial changes are made.

There should only be a single lint name involved here - this currently uses reachable and reachable_patterns (reachable_patterns being the lint itself, and reachable being used to indicate a match expression ought to be being checked). I'd recommend changing this so that there's only reachable_patterns (perhaps renamed to non_exhaustive_reachable_patterns), that is allow-by-default. When you emit a lint, the logic for that will work out if it should be shown to the user based on the lint level, so you shouldn't need to check if the attribute is present or not, if the expression is annotated with a #[{allow,deny,warn}(reachable_patterns)] then it'll fire, if not, it won't.

Given that a new lint is a user-facing change, this probably warrants a major change proposal too.

compiler/rustc_lint/src/reachable_patterns.rs Outdated Show resolved Hide resolved
compiler/rustc_lint/src/reachable_patterns.rs Outdated Show resolved Hide resolved
compiler/rustc_lint/src/reachable_patterns.rs Outdated Show resolved Hide resolved
@davidtwco davidtwco added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 9, 2021
@DevinR528
Copy link
Contributor Author

DevinR528 commented Jul 10, 2021

I opened an MCP rust-lang/compiler-team#445, I hope I followed the structure ok enough.

The problem with the logic that is currently in the PR is that it only accounts for variants it will not take guards or nesting into account. It seems to me the options are

  1. keep it a lint (as it is now) but use some of the usefulness code in the checks in the lint
  2. emit the warning/error from inside of the various pattern checks in check_match.rs
  3. Something better that I have no idea since I'm not super familiar with the code??

@davidtwco
Copy link
Member

I opened an MCP rust-lang/compiler-team#445, I hope I followed the structure ok enough.

The problem with the logic that is currently in the PR is that it only accounts for variants it will not take guards or nesting into account. It seems to me the options are

1. keep it a lint (as it is now) but use some of the [usefulness code](https://github.com/rust-lang/rust/blob/master/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs?rgh-link-date=2021-07-10T01%3A01%3A45Z) in the checks in the lint

2. emit the warning/error from inside of the various pattern checks [in check_match.rs](https://github.com/rust-lang/rust/blob/master/compiler/rustc_mir_build/src/thir/pattern/check_match.rs?rgh-link-date=2021-07-10T01%3A01%3A45Z#L214)

3. Something better that I have no idea since I'm not super familiar with the code??

Let's not invest much more effort into this until the MCP has been looked at by the compiler team, I wouldn't want your time to go to waste if we took the wrong approach here or the MCP wasn't accepted.

@DevinR528 DevinR528 marked this pull request as draft July 15, 2021 10:51
@DevinR528
Copy link
Contributor Author

Thanks for your help, I kinda figured waiting for the MCP was the next step. I have some rough logic to check enums in the usefulness code I was going to push it just so there are 2 commits that can be referenced for an idea of what to do (since they both aren't fully functional). I think if I can make the non_exhaustive variant work it'll be much closer to working for destructuring let stmts too, so I might fiddle with that be for I push another commit, should be by the end of the day.

@DevinR528
Copy link
Contributor Author

This is now completely done in usefulness checking I removed the LateLintPass implementation. This is now a working lint called non_exhaustive_reachable_patterns!

The lint currently only works for enums and only top-level non_exhaustive enums

// works fine
#[non_exhaustive]
pub enum Bar {
    A,
    B(usize),
    C { field: Foo },
}

// does not work
pub enum VariantNonExhaustive {
    #[non_exhaustive]
    Bar { x: u32 },
    Baz(u32),
}

For the latter to work the lint would have to be implemented for structs which it is not yet.

The major change is to compute_match_usefulness and some of the functions/methods it calls

If we encounter a wildcard entry for a non_exhaustive enum we keep this information in PatCtxt (by way of is_useful -> Usefulness::apply_constructor) which passes it along to SplitWildcard::new to return the list of missing variants instead of Constructor::NonExhaustive, since we don't push the wildcard into the matrix we check that no real variants are missed.

The lint is defined in rustc_lint_defs. It uses the regular lint mechanics instead of checking for the attribute and level in the usefulness code. It emits the lint in check_match and the emitting code is here.

@rust-log-analyzer

This comment has been minimized.

@bors
Copy link
Contributor

bors commented Aug 16, 2021

☔ The latest upstream changes (presumably #80357) made this pull request unmergeable. Please resolve the merge conflicts.

@rust-log-analyzer

This comment has been minimized.

@sunshowers
Copy link
Contributor

Thank you for doing this!

Out of curiosity how does this handle matching multiple enums at once? For example match (enum_1, enum_2) where:

  • both enums are non-exhaustive
  • one of the enums is non-exhaustive but not the other

While thinking about this I came to the conclusion that it may not be possible to support adding the lint to the wildcard pattern itself, just to the parent statement -- could be wrong though.

@sunshowers
Copy link
Contributor

BTW I wrote this up (along with alternatives) as an RFC at https://github.com/sunshowers/rfcs/blob/unknown-non-exhaustive/text/0000-unknown-non-exhaustive.md before being pointed to this PR :)

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 15, 2021
@bors
Copy link
Contributor

bors commented Sep 15, 2021

⌛ Testing commit 33a06b7 with merge a3b66f0209c4d1ddd6ab7d656aa94e139ebb6b55...

@bors
Copy link
Contributor

bors commented Sep 15, 2021

💔 Test failed - checks-actions

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Sep 15, 2021
@ehuss
Copy link
Contributor

ehuss commented Sep 15, 2021

@bors retry

#88977 - validate_maintainers is broken.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 15, 2021
@rust-log-analyzer
Copy link
Collaborator

The job mingw-check failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
Build completed successfully in 0:00:40
+ /scripts/validate-toolstate.sh
Cloning into 'rust-toolstate'...
<Nothing changed>
HTTPError: HTTP Error 403: Forbidden
b'{"message":"Must have admin access to view repository collaborators.","documentation_url":"https://docs.github.com/rest/reference/repos#list-repository-collaborators"}'
Traceback (most recent call last):
  File "../../src/tools/publish_toolstate.py", line 302, in <module>
    validate_maintainers(repo, github_token)
  File "../../src/tools/publish_toolstate.py", line 93, in validate_maintainers
    'Accept': 'application/vnd.github.hellcat-preview+json',
  File "/usr/lib/python3.6/urllib/request.py", line 223, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python3.6/urllib/request.py", line 532, in open
    response = meth(req, response)
  File "/usr/lib/python3.6/urllib/request.py", line 642, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python3.6/urllib/request.py", line 570, in error
    return self._call_chain(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 650, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 403: Forbidden

@bors
Copy link
Contributor

bors commented Sep 16, 2021

⌛ Testing commit 33a06b7 with merge 2b5ddf3...

@bors
Copy link
Contributor

bors commented Sep 16, 2021

☀️ Test successful - checks-actions
Approved by: Nadrieril
Pushing 2b5ddf3 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Sep 16, 2021
@bors bors merged commit 2b5ddf3 into rust-lang:master Sep 16, 2021
@rustbot rustbot added this to the 1.57.0 milestone Sep 16, 2021
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (2b5ddf3): comparison url.

Summary: This benchmark run did not return any relevant changes.

If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf.

@rustbot label: -perf-regression

@m-ou-se
Copy link
Member

m-ou-se commented Sep 17, 2021

The io::ErrorKind shows the unstable variants

It looks like that hasn't been solved. The ui test that was added even has unstable variants in the expected output.

Did that happen on purpose?

This goes against the point of #[unstable] and #[doc(hidden)]. Things like this would ideally have @rust-lang/libs-api sign off before merging, to make sure it doesn't break the tools we need to provide stability.

Opening an issue for this, as it should probably be solved before this lint hits stable.

Edit: Issue: #89042

@Nadrieril
Copy link
Member

Nadrieril commented Sep 17, 2021

Ah shoot. That's on me. I assumed that the exhaustiveness code already handled those variants in the expected way so I didn't double-check. Shouldn't be too hard to solve thankfully

@pnkfelix
Copy link
Member

Visiting for performance triage.

  • Moderate improvement in instruction counts (up to -0.6% on full builds of unicode_normalization)

perhaps noise, but 🎉 nonetheless. :)

@Nadrieril Nadrieril added the F-non_exhaustive_omitted_patterns_lint `#![feature(non_exhaustive_omitted_patterns_lint)]` label Oct 5, 2021
bors added a commit to rust-lang-ci/rust that referenced this pull request Oct 12, 2021
Fix: non_exhaustive_omitted_patterns by filtering unstable and doc hidden variants

Fixes: rust-lang#89042

Now that rust-lang#86809 has been merged there are cases (std::io::ErrorKind) where unstable feature gated variants were included in warning/error messages when the feature was not turned on. This filters those variants out of the return of `SplitWildcard::new`.

Variants marked `doc(hidden)` are filtered out of the witnesses list in `Usefulness::apply_constructor`.

Probably worth a perf run :shrug: since this area can be sensitive.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-exhaustiveness-checking Relating to exhaustiveness / usefulness checking of patterns F-non_exhaustive_omitted_patterns_lint `#![feature(non_exhaustive_omitted_patterns_lint)]` F-non_exhaustive `#![feature(non_exhaustive)]` merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add an allow-by-default lint that triggers on reachable catch-all / rest patterns