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

Point at const definition when used instead of a binding in a let statement #132708

Merged
merged 10 commits into from
Nov 21, 2024

Conversation

estebank
Copy link
Contributor

@estebank estebank commented Nov 6, 2024

Modify PatKind::InlineConstant to be ExpandedConstant standing in not only for inline const blocks but also for const items. This allows us to track named consts used in patterns when the pattern is a single binding. When we detect that there is a refutable pattern involving a const that could have been a binding instead, we point at the const item, and suggest renaming. We do this for both let bindings and match expressions missing a catch-all arm if there's at least one single binding pattern referenced.

After:

error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |     const PAT: u32 = 0;
   |     -------------- missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
...
LL |         let PAT = v1;
   |             ^^^ pattern `1_u32..=u32::MAX` not covered
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`
help: introduce a variable instead
   |
LL |         let PAT_var = v1;
   |             ~~~~~~~

Before:

error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |         let PAT = v1;
   |             ^^^
   |             |
   |             pattern `1_u32..=u32::MAX` not covered
   |             missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
   |             help: introduce a variable instead: `PAT_var`
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`

CC #132582.

@rustbot
Copy link
Collaborator

rustbot commented Nov 6, 2024

r? @BoxyUwU

rustbot has assigned @BoxyUwU.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 6, 2024
@rustbot
Copy link
Collaborator

rustbot commented Nov 6, 2024

Some changes occurred in match lowering

cc @Nadrieril

Some changes occurred in exhaustiveness checking

cc @Nadrieril

Some changes occurred in match checking

cc @Nadrieril

Some changes occurred in rustc_ty_utils::consts.rs

cc @BoxyUwU

Copy link
Member

@compiler-errors compiler-errors left a comment

Choose a reason for hiding this comment

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

This PR needs a lot more explanation that details why we need to add new THIR, or better yet, it could use some more investigation towards how we can avoid needing to add THIR at all -- since this is for the diagnostic path. Like, why can't we just extract the def span of the const from the UnevaluatedConst that is inside the mir::Const?

Adding new THIR nodes seems like it's ripe for adding confusion to an already confusing area of the compiler, and this PR has no explanation or documentation other than a "before" and "after". I'm not certain if it's worth the overhead to add all this to the compiler to improve this diagnostic if this is the only way to do it.

compiler/rustc_mir_build/src/thir/pattern/mod.rs Outdated Show resolved Hide resolved
@compiler-errors
Copy link
Member

Even if we need to end up plumbing this span separately, why not just add an opt_def_span: Option<Span> to the existing Constant patkind?

@estebank
Copy link
Contributor Author

estebank commented Nov 6, 2024

why can't we just extract the def span of the const from the UnevaluatedConst that is inside the mir::Const?

Because by the time the error occurs, it is no longer an UnevaluatedConst.

and this PR has no explanation or documentation other than a "before" and "after".

It's providing additional context to the output from what we have today. Today the output can be quite confusing, particularly if the constant is defined far away with a non-standard identifier, and it can be even worse if it happens due to a re-export that renames it. The error as it is today takes me a triple take to understand what it was telling me about.

I'm not certain if it's worth the overhead to add all this to the compiler to improve this diagnostic if this is the only way to do it.

I'm more than happy to hear about alternative ways to do this. I disagree that the output as is today is reasonable. I'd go as far as saying it is inescrutable for anyone who doesn't yet know about the handling of consts in patterns or that let bindings are patterns to begin with.

Even if we need to end up plumbing this span separately, why not just add an opt_def_span: Option to the existing Constant patkind?

I'm ok with that alternative.

@compiler-errors
Copy link
Member

All of that information would've been awesome to include in the PR description.

I hope you don't take this in the wrong way, but it's a bit annoying from a reviewer's perspective (and kinda leads to additional work on the reviewer) to have to try to deduce the reasoning that went into this approach from basically no information, or to have to leave a bunch of confused questions and go back and forth on a semi-synchronous github convo.

This is especially true when it touches something very important like THIR, as opposed to diagnostics PRs that generally stay out of the way of the good path. Divergence in the way we handle PatKinds can lead to bugs or ICEs.

I disagree that the output as is today is reasonable.

I think you're misinterpreting me. What I mean is that I'm not certain if the approach is worth the overhead. I'm not saying that the output today is reasonable -- what I'm saying is that I'm not certain if we should land this approach, as opposed to deferring fixing this in favor of investigating a better solution for this.

Even if we need to end up plumbing this span separately, why not just add an opt_def_span: Option to the existing Constant patkind?

I'm ok with that alternative.

Well, if you plumb in the Option<DefId> of the unevaluated const, then you can avoid the span_to_snippet call in the suggestion producing code, because we can get the span and item name of the const at that point from the TyCtxt. It's probably best to do that if we're plumbing more information into the Const pattern-kind, since then we make things cleaner than we found them :)

subpattern: box Pat { kind: PatKind::Constant { opt_def: Some(def_id), .. }, .. },
..
} = pat.kind
&& let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(pat.span)
Copy link
Member

@compiler-errors compiler-errors Nov 6, 2024

Choose a reason for hiding this comment

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

Ah, you read my mind with the DefId -- but we can take it further and use the tcx.item_name here to get the name of the constant, rather than using snippets. This will regress reexported constants, but is more resilient IMO and is worth the change.

Copy link
Member

@compiler-errors compiler-errors Nov 6, 2024

Choose a reason for hiding this comment

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

Specifically, this pat name will not possibly be tied to arbitrary source code, in pat_macro!(/* uwu */).

@Nadrieril
Copy link
Member

This is a pleasant diagnostic improvement. Am I correct to understand that this only catches constants of primitive type? Is there a way to catch also

const PAT: (u32, u32) = ...;
let PAT = ...;

?

@estebank
Copy link
Contributor Author

estebank commented Nov 7, 2024

@Nadrieril for that case, the output is

error[E0005]: refutable pattern in local binding
 --> f702.rs:9:9
  |
9 |     let PAT = (1, 1); 
  |         ^^^ pattern `(1_u32..=u32::MAX, _)` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
  = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
  = note: the matched value is of type `(u32, u32)`
help: you might want to use `if let` to ignore the variant that isn't matched
  |
9 |     if let PAT = (1, 1) { todo!() }; 
  |     ++                  +++++++++++

Looking at the error it seems it is because it is checking the sub-pattern for the first tuple item. I can take a look to see if we can make it work for tuples (and arrays, and structs). For enums we already have reasonable output before this PR:

error[E0005]: refutable pattern in local binding
  --> f702.rs:15:9
   |
15 |     let PAT2 = E::B;
   |         ^^^^ pattern `E::B` not covered
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
note: `E` defined here
  --> f702.rs:5:6
   |
5  | enum E {
   |      ^
6  |     A,
7  |     B,
   |     - not covered
   = note: the matched value is of type `E`
help: you might want to use `if let` to ignore the variant that isn't matched
   |
15 |     if let PAT2 = E::B { todo!() };
   |     ++                 +++++++++++

@Nadrieril
Copy link
Member

It would be nice to have it point at the definition of the constant in these cases too. Once we get to PatKind it's too late however so we'd have to get the info before. Hm, unless we want to add a PatKind::NamedConst { subpat: Pat, id: DefId } variant I guess, which isn't unreasonable given that we already have PatKind::InlineConst that looks very similar, no? Or did we remove that one

@estebank
Copy link
Contributor Author

estebank commented Nov 7, 2024

@Nadrieril We do have PatKind::InlineConst. Adding PatKind::NamedConst would indeed work. I leave it to you and @compiler-errors to decide whether the additional maintenance cost is reasonable or not. I believe it is, given that I'm finding another place to use these for patterns where the intended fall-through binding is mixed up with a const name, so there are already at least two cases where we want this metadata, and not supporting anything beyond literals (and enums, that we already accounted for) would be a shame.

@Nadrieril
Copy link
Member

If we rename InlineConstant to ExpandedConstant and add a is_inline: bool field if needed, that should be no extra maintenance cost. I'm in favor of that, it's more principled that the current approach in this PR

@estebank
Copy link
Contributor Author

estebank commented Nov 7, 2024

@Nadrieril there's one wrinkle there: InlineConstant stores a LocalDefId, but consts can come from other crates so it'd have to be a DefId.

@Nadrieril
Copy link
Member

We can store a DefId and add a comment "if is_inline is true, this def_id is local"

Comment on lines +165 to +169
PatKind::ExpandedConstant { subpattern: ref pattern, def_id: _, is_inline: false } => {
subpairs.push(MatchPairTree::for_pattern(place_builder, pattern, cx));
default_irrefutable()
}
PatKind::ExpandedConstant { subpattern: ref pattern, def_id, is_inline: true } => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here...

Comment on lines +389 to +394
PatKind::ExpandedConstant { def_id, is_inline, .. } => {
if let Some(def) = def_id.as_local()
&& *is_inline
{
self.visit_inner_body(def);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

...and here are the two places where is_inline has any effect in the constant evaluation. @compiler-errors can you take a look to see if this is a reasonable level of complexity? We can also add comments explaining why is_inline (or rather, why we don't want to evaluate the unsafety of const items). The current approach has the nice property of not making the types any larger than they were.

Comment on lines 576 to 577
// We only want to mark constants when referenced as bare names that could have been
// new bindings if the `const` didn't exist.
Copy link
Member

Choose a reason for hiding this comment

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

Hm, I assumed we'd be indiscriminately recording all constant expansions and would filter later. I'm not sure which is best, but at least please mention this choice in the PatKind variant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of only setting them in this case we could add another flag to the variant to track this and not suggest if the flag is set.

Copy link
Member

Choose a reason for hiding this comment

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

That would be an improvement imo. I would still prefer to avoid carrying a boolean in THIR for a single diagnostic suggestion tbh, depends how hard it is to do without the boolean.

Copy link
Member

Choose a reason for hiding this comment

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

My preference would be: ExpandedConst { def_id, subpattern } and do the span_to_snippet(subpattern.span).chars().all(is_alphanumeric) dance for diagnostics.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me know what you think with the last commit.

Copy link
Member

Choose a reason for hiding this comment

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

That's what I had in mind, ty. Could you add the ExpandedConstant layer inside const_to_pat by any chance? To be sure we don't miss one

Copy link
Member

Choose a reason for hiding this comment

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

I looked at const_to_pat, it wouldn't be super clean to add it there. I'm therefore ok with the current way of doing it.

Comment on lines +6 to +19
LL |
LL | E_SL => {}
| ---- this pattern doesn't introduce a new catch-all binding, but rather pattern matches against the value of constant `E_SL`
|
= note: the matched value is of type `&[E]`
note: constant `E_SL` defined here
--> $DIR/incomplete-slice.rs:6:1
|
LL | const E_SL: &[E] = &[E::A];
| ^^^^^^^^^^^^^^^^
help: if you meant to introduce a binding, use a different name
|
LL | E_SL_var => {}
| ++++
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 great!

@Nadrieril
Copy link
Member

Nadrieril commented Nov 9, 2024

In terms of additional complexity, this now seems perfectly fine. The one thing I have mixed feelings about is that this only adds an ExpandedConstant if the constant could be mistaken for an identifier. This is mixing diagnostics concerns with representation concerns; I'd much prefer if we always added ExpandedConstant to the output of const_to_pat, and checked whether the constant is relevant for diagnostics in the diagnostics path.

@rust-log-analyzer

This comment has been minimized.

@Nadrieril
Copy link
Member

I'm ok with this now! It's quite a nice diagnostic improvement. I think you have to fix THIR parsing, and I left one suggestion about a comment.

@Nadrieril Nadrieril 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 Nov 15, 2024
… statement

After:

```
error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |     const PAT: u32 = 0;
   |     -------------- missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
...
LL |         let PAT = v1;
   |             ^^^
   |             |
   |             pattern `1_u32..=u32::MAX` not covered
   |             help: introduce a variable instead: `PAT_var`
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`
```

Before:

```
error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |         let PAT = v1;
   |             ^^^
   |             |
   |             pattern `1_u32..=u32::MAX` not covered
   |             missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
   |             help: introduce a variable instead: `PAT_var`
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`
```
```
error[E0004]: non-exhaustive patterns: `i32::MIN..=3_i32` and `5_i32..=i32::MAX` not covered
  --> $DIR/intended-binding-pattern-is-const.rs:2:11
   |
LL |     match 1 {
   |           ^ patterns `i32::MIN..=3_i32` and `5_i32..=i32::MAX` not covered
LL |         x => {}
   |         - this pattern doesn't introduce a new catch-all binding, but rather pattern matches against the value of constant `x`
   |
   = note: the matched value is of type `i32`
note: constant `x` defined here
  --> $DIR/intended-binding-pattern-is-const.rs:7:5
   |
LL |     const x: i32 = 4;
   |     ^^^^^^^^^^^^
help: if you meant to introduce a binding, use a different name
   |
LL |         x_var => {}
   |          ++++
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
   |
LL |         x => {}, i32::MIN..=3_i32 | 5_i32..=i32::MAX => todo!()
   |                ++++++++++++++++++++++++++++++++++++++++++++++++
```
- Remove check for "how many path segments is the pattern"
- Check before suggesting if the path has multiple path segments
@estebank estebank added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Nov 18, 2024
@Nadrieril
Copy link
Member

Ty!

@bors r+

@bors
Copy link
Contributor

bors commented Nov 20, 2024

📌 Commit 29acf8b has been approved by Nadrieril

It is now in the queue for this repository.

@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 Nov 20, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Nov 20, 2024
…eril

Point at `const` definition when used instead of a binding in a `let` statement

Modify `PatKind::InlineConstant` to be `ExpandedConstant` standing in not only for inline `const` blocks but also for `const` items. This allows us to track named `const`s used in patterns when the pattern is a single binding. When we detect that there is a refutable pattern involving a `const` that could have been a binding instead, we point at the `const` item, and suggest renaming. We do this for both `let` bindings and `match` expressions missing a catch-all arm if there's at least one single binding pattern referenced.

After:

```
error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |     const PAT: u32 = 0;
   |     -------------- missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
...
LL |         let PAT = v1;
   |             ^^^ pattern `1_u32..=u32::MAX` not covered
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`
help: introduce a variable instead
   |
LL |         let PAT_var = v1;
   |             ~~~~~~~
```

Before:

```
error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |         let PAT = v1;
   |             ^^^
   |             |
   |             pattern `1_u32..=u32::MAX` not covered
   |             missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
   |             help: introduce a variable instead: `PAT_var`
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`
```

CC rust-lang#132582.
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 20, 2024
…iaskrgr

Rollup of 6 pull requests

Successful merges:

 - rust-lang#129838 (uefi: process: Add args support)
 - rust-lang#130800 (Mark `get_mut` and `set_position` in `std::io::Cursor` as const.)
 - rust-lang#132708 (Point at `const` definition when used instead of a binding in a `let` statement)
 - rust-lang#133226 (Make `PointerLike` opt-in instead of built-in)
 - rust-lang#133244 (Account for `wasm32v1-none` when exporting TLS symbols)
 - rust-lang#133257 (Add `UnordMap::clear` method)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 7fc2b33 into rust-lang:master Nov 21, 2024
6 checks passed
@rustbot rustbot added this to the 1.84.0 milestone Nov 21, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Nov 21, 2024
Rollup merge of rust-lang#132708 - estebank:const-as-binding, r=Nadrieril

Point at `const` definition when used instead of a binding in a `let` statement

Modify `PatKind::InlineConstant` to be `ExpandedConstant` standing in not only for inline `const` blocks but also for `const` items. This allows us to track named `const`s used in patterns when the pattern is a single binding. When we detect that there is a refutable pattern involving a `const` that could have been a binding instead, we point at the `const` item, and suggest renaming. We do this for both `let` bindings and `match` expressions missing a catch-all arm if there's at least one single binding pattern referenced.

After:

```
error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |     const PAT: u32 = 0;
   |     -------------- missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
...
LL |         let PAT = v1;
   |             ^^^ pattern `1_u32..=u32::MAX` not covered
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`
help: introduce a variable instead
   |
LL |         let PAT_var = v1;
   |             ~~~~~~~
```

Before:

```
error[E0005]: refutable pattern in local binding
  --> $DIR/bad-pattern.rs:19:13
   |
LL |         let PAT = v1;
   |             ^^^
   |             |
   |             pattern `1_u32..=u32::MAX` not covered
   |             missing patterns are not covered because `PAT` is interpreted as a constant pattern, not a new variable
   |             help: introduce a variable instead: `PAT_var`
   |
   = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
   = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
   = note: the matched value is of type `u32`
```

CC rust-lang#132582.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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.

7 participants