From 560beb1ad423a9f3e447bdeefded8994cd8af75c Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 5 Jan 2024 10:11:18 +0100 Subject: [PATCH] Check bindings around never patterns --- compiler/rustc_resolve/messages.ftl | 4 ++ compiler/rustc_resolve/src/diagnostics.rs | 3 + compiler/rustc_resolve/src/errors.rs | 9 +++ compiler/rustc_resolve/src/late.rs | 59 ++++++++++++------- compiler/rustc_resolve/src/lib.rs | 2 + .../feature-gate-never_patterns.rs | 1 - .../feature-gate-never_patterns.stderr | 37 +++++------- tests/ui/pattern/never_patterns.rs | 5 +- tests/ui/pattern/never_patterns.stderr | 28 ++++----- .../rfcs/rfc-0000-never_patterns/bindings.rs | 11 ++-- .../rfc-0000-never_patterns/bindings.stderr | 47 +++++---------- .../ui/rfcs/rfc-0000-never_patterns/parse.rs | 1 + .../rfcs/rfc-0000-never_patterns/parse.stderr | 8 ++- 13 files changed, 110 insertions(+), 105 deletions(-) diff --git a/compiler/rustc_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl index 3f8df16e03f..c8ec10cad17 100644 --- a/compiler/rustc_resolve/messages.ftl +++ b/compiler/rustc_resolve/messages.ftl @@ -36,6 +36,10 @@ resolve_attempt_to_use_non_constant_value_in_constant_with_suggestion = resolve_attempt_to_use_non_constant_value_in_constant_without_suggestion = this would need to be a `{$suggestion}` +resolve_binding_in_never_pattern = + never patterns cannot contain variable bindings + .suggestion = use a wildcard `_` instead + resolve_binding_shadows_something_unacceptable = {$shadowing_binding}s cannot shadow {$shadowed_binding}s .label = cannot be named the same as {$article} {$shadowed_binding} diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index c1cb20f6bb0..5f53733276a 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -960,6 +960,9 @@ pub(crate) fn into_struct_error( .create_err(errs::TraitImplDuplicate { span, name, trait_item_span, old_span }), ResolutionError::InvalidAsmSym => self.dcx().create_err(errs::InvalidAsmSym { span }), ResolutionError::LowercaseSelf => self.dcx().create_err(errs::LowercaseSelf { span }), + ResolutionError::BindingInNeverPattern => { + self.dcx().create_err(errs::BindingInNeverPattern { span }) + } } } diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 1fdb193e571..821b1e946f3 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -486,6 +486,15 @@ pub(crate) struct LowercaseSelf { pub(crate) span: Span, } +#[derive(Debug)] +#[derive(Diagnostic)] +#[diag(resolve_binding_in_never_pattern)] +pub(crate) struct BindingInNeverPattern { + #[primary_span] + #[suggestion(code = "_", applicability = "machine-applicable", style = "short")] + pub(crate) span: Span, +} + #[derive(Diagnostic)] #[diag(resolve_trait_impl_duplicate, code = "E0201")] pub(crate) struct TraitImplDuplicate { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 536ecd0fa24..90b27856161 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -65,6 +65,8 @@ enum IsRepeatExpr { Yes, } +struct IsNeverPattern; + /// Describes whether an `AnonConst` is a type level const arg or /// some other form of anon const (i.e. inline consts or enum discriminants) #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -3191,11 +3193,15 @@ fn resolve_local(&mut self, local: &'ast Local) { } /// Build a map from pattern identifiers to binding-info's, and check the bindings are - /// consistent when encountering or-patterns. + /// consistent when encountering or-patterns and never patterns. /// This is done hygienically: this could arise for a macro that expands into an or-pattern /// where one 'x' was from the user and one 'x' came from the macro. - fn compute_and_check_binding_map(&mut self, pat: &Pat) -> FxIndexMap { + fn compute_and_check_binding_map( + &mut self, + pat: &Pat, + ) -> Result, IsNeverPattern> { let mut binding_map = FxIndexMap::default(); + let mut is_never_pat = false; pat.walk(&mut |pat| { match pat.kind { @@ -3207,17 +3213,26 @@ fn compute_and_check_binding_map(&mut self, pat: &Pat) -> FxIndexMap { // Check the consistency of this or-pattern and // then add all bindings to the larger map. - let bm = self.compute_and_check_or_pat_binding_map(ps); + let (bm, np) = self.compute_and_check_or_pat_binding_map(ps); binding_map.extend(bm); + is_never_pat |= np; return false; } + PatKind::Never => is_never_pat = true, _ => {} } true }); - binding_map + if is_never_pat { + for (_, binding) in binding_map { + self.report_error(binding.span, ResolutionError::BindingInNeverPattern); + } + Err(IsNeverPattern) + } else { + Ok(binding_map) + } } fn is_base_res_local(&self, nid: NodeId) -> bool { @@ -3229,24 +3244,29 @@ fn is_base_res_local(&self, nid: NodeId) -> bool { /// Compute the binding map for an or-pattern. Checks that all of the arms in the or-pattern /// have exactly the same set of bindings, with the same binding modes for each. - /// Returns the computed binding map. + /// Returns the computed binding map and a boolean indicating whether the pattern is a never + /// pattern. fn compute_and_check_or_pat_binding_map( &mut self, pats: &[P], - ) -> FxIndexMap { + ) -> (FxIndexMap, bool) { let mut missing_vars = FxIndexMap::default(); let mut inconsistent_vars = FxIndexMap::default(); - // 1) Compute the binding maps of all arms. - let maps = - pats.iter().map(|pat| self.compute_and_check_binding_map(pat)).collect::>(); + // 1) Compute the binding maps of all arms; never patterns don't participate in this. + let not_never_pats = pats + .iter() + .filter_map(|pat| { + let binding_map = self.compute_and_check_binding_map(pat).ok()?; + Some((binding_map, pat)) + }) + .collect::>(); // 2) Record any missing bindings or binding mode inconsistencies. - for (map_outer, pat_outer) in maps.iter().zip(pats.iter()) { + for (map_outer, pat_outer) in not_never_pats.iter() { // Check against all arms except for the same pattern which is always self-consistent. - let inners = maps + let inners = not_never_pats .iter() - .zip(pats.iter()) .filter(|(_, pat)| pat.id != pat_outer.id) .flat_map(|(map, _)| map); @@ -3294,22 +3314,17 @@ fn compute_and_check_or_pat_binding_map( } // 5) Bubble up the final binding map. + let is_never_pat = not_never_pats.is_empty(); let mut binding_map = FxIndexMap::default(); - for bm in maps { + for (bm, _) in not_never_pats { binding_map.extend(bm); } - binding_map + (binding_map, is_never_pat) } - /// Check the consistency of bindings wrt or-patterns. + /// Check the consistency of bindings wrt or-patterns and never patterns. fn check_consistent_bindings(&mut self, pat: &'ast Pat) { - pat.walk(&mut |pat| match pat.kind { - PatKind::Or(ref ps) => { - let _ = self.compute_and_check_or_pat_binding_map(ps); - false - } - _ => true, - }) + let _ = self.compute_and_check_binding_map(pat); } fn resolve_arm(&mut self, arm: &'ast Arm) { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index a14f3d494fb..5a9bf22de51 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -265,6 +265,8 @@ enum ResolutionError<'a> { InvalidAsmSym, /// `self` used instead of `Self` in a generic parameter LowercaseSelf, + /// A never pattern has a binding. + BindingInNeverPattern, } enum VisResolutionError<'a> { diff --git a/tests/ui/feature-gates/feature-gate-never_patterns.rs b/tests/ui/feature-gates/feature-gate-never_patterns.rs index f3910622313..d23405ada2d 100644 --- a/tests/ui/feature-gates/feature-gate-never_patterns.rs +++ b/tests/ui/feature-gates/feature-gate-never_patterns.rs @@ -7,7 +7,6 @@ fn main() { let res: Result = Ok(0); let (Ok(_x) | Err(&!)) = res.as_ref(); //~^ ERROR `!` patterns are experimental - //~| ERROR: is not bound in all patterns unsafe { let ptr: *const Void = NonNull::dangling().as_ptr(); diff --git a/tests/ui/feature-gates/feature-gate-never_patterns.stderr b/tests/ui/feature-gates/feature-gate-never_patterns.stderr index dd10829d495..3181dac69e5 100644 --- a/tests/ui/feature-gates/feature-gate-never_patterns.stderr +++ b/tests/ui/feature-gates/feature-gate-never_patterns.stderr @@ -1,5 +1,5 @@ error: unexpected `,` in pattern - --> $DIR/feature-gate-never_patterns.rs:34:16 + --> $DIR/feature-gate-never_patterns.rs:33:16 | LL | Some(_), | ^ @@ -13,14 +13,6 @@ help: ...or a vertical bar to match on multiple alternatives LL | Some(_) | | -error[E0408]: variable `_x` is not bound in all patterns - --> $DIR/feature-gate-never_patterns.rs:8:19 - | -LL | let (Ok(_x) | Err(&!)) = res.as_ref(); - | -- ^^^^^^^ pattern doesn't bind `_x` - | | - | variable not in all patterns - error[E0658]: `!` patterns are experimental --> $DIR/feature-gate-never_patterns.rs:8:24 | @@ -31,7 +23,7 @@ LL | let (Ok(_x) | Err(&!)) = res.as_ref(); = help: add `#![feature(never_patterns)]` to the crate attributes to enable error[E0658]: `!` patterns are experimental - --> $DIR/feature-gate-never_patterns.rs:15:13 + --> $DIR/feature-gate-never_patterns.rs:14:13 | LL | ! | ^ @@ -40,7 +32,7 @@ LL | ! = help: add `#![feature(never_patterns)]` to the crate attributes to enable error[E0658]: `!` patterns are experimental - --> $DIR/feature-gate-never_patterns.rs:21:13 + --> $DIR/feature-gate-never_patterns.rs:20:13 | LL | ! | ^ @@ -49,7 +41,7 @@ LL | ! = help: add `#![feature(never_patterns)]` to the crate attributes to enable error[E0658]: `!` patterns are experimental - --> $DIR/feature-gate-never_patterns.rs:26:13 + --> $DIR/feature-gate-never_patterns.rs:25:13 | LL | ! => {} | ^ @@ -58,25 +50,25 @@ LL | ! => {} = help: add `#![feature(never_patterns)]` to the crate attributes to enable error: `match` arm with no body - --> $DIR/feature-gate-never_patterns.rs:39:9 + --> $DIR/feature-gate-never_patterns.rs:38:9 | LL | Some(_) | ^^^^^^^- help: add a body after the pattern: `=> todo!(),` error: `match` arm with no body - --> $DIR/feature-gate-never_patterns.rs:44:9 + --> $DIR/feature-gate-never_patterns.rs:43:9 | LL | Some(_) if false, | ^^^^^^^- help: add a body after the pattern: `=> todo!(),` error: `match` arm with no body - --> $DIR/feature-gate-never_patterns.rs:46:9 + --> $DIR/feature-gate-never_patterns.rs:45:9 | LL | Some(_) if false | ^^^^^^^- help: add a body after the pattern: `=> todo!(),` error[E0658]: `!` patterns are experimental - --> $DIR/feature-gate-never_patterns.rs:51:13 + --> $DIR/feature-gate-never_patterns.rs:50:13 | LL | Err(!), | ^ @@ -85,7 +77,7 @@ LL | Err(!), = help: add `#![feature(never_patterns)]` to the crate attributes to enable error[E0658]: `!` patterns are experimental - --> $DIR/feature-gate-never_patterns.rs:55:13 + --> $DIR/feature-gate-never_patterns.rs:54:13 | LL | Err(!) if false, | ^ @@ -94,24 +86,23 @@ LL | Err(!) if false, = help: add `#![feature(never_patterns)]` to the crate attributes to enable error: `match` arm with no body - --> $DIR/feature-gate-never_patterns.rs:65:9 + --> $DIR/feature-gate-never_patterns.rs:64:9 | LL | Some(_) | ^^^^^^^- help: add a body after the pattern: `=> todo!(),` error: `match` arm with no body - --> $DIR/feature-gate-never_patterns.rs:71:9 + --> $DIR/feature-gate-never_patterns.rs:70:9 | LL | Some(_) if false | ^^^^^^^- help: add a body after the pattern: `=> todo!(),` error: a guard on a never pattern will never be run - --> $DIR/feature-gate-never_patterns.rs:55:19 + --> $DIR/feature-gate-never_patterns.rs:54:19 | LL | Err(!) if false, | ^^^^^ help: remove this guard -error: aborting due to 14 previous errors +error: aborting due to 13 previous errors -Some errors have detailed explanations: E0408, E0658. -For more information about an error, try `rustc --explain E0408`. +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/pattern/never_patterns.rs b/tests/ui/pattern/never_patterns.rs index eb2421204ae..8f44f8a6559 100644 --- a/tests/ui/pattern/never_patterns.rs +++ b/tests/ui/pattern/never_patterns.rs @@ -7,12 +7,9 @@ fn main() {} // The classic use for empty types. fn safe_unwrap_result(res: Result) { - let Ok(_x) = res; - // FIXME(never_patterns): These should be allowed + let Ok(_x) = res; //~ ERROR refutable pattern in local binding let (Ok(_x) | Err(!)) = &res; - //~^ ERROR: is not bound in all patterns let (Ok(_x) | Err(&!)) = res.as_ref(); - //~^ ERROR: is not bound in all patterns } // Check we only accept `!` where we want to. diff --git a/tests/ui/pattern/never_patterns.stderr b/tests/ui/pattern/never_patterns.stderr index f3787f9816a..20eeb01cf71 100644 --- a/tests/ui/pattern/never_patterns.stderr +++ b/tests/ui/pattern/never_patterns.stderr @@ -1,19 +1,17 @@ -error[E0408]: variable `_x` is not bound in all patterns - --> $DIR/never_patterns.rs:12:19 +error[E0005]: refutable pattern in local binding + --> $DIR/never_patterns.rs:10:9 | -LL | let (Ok(_x) | Err(!)) = &res; - | -- ^^^^^^ pattern doesn't bind `_x` - | | - | variable not in all patterns - -error[E0408]: variable `_x` is not bound in all patterns - --> $DIR/never_patterns.rs:14:19 +LL | let Ok(_x) = res; + | ^^^^^^ pattern `Err(_)` not covered | -LL | let (Ok(_x) | Err(&!)) = res.as_ref(); - | -- ^^^^^^^ pattern doesn't bind `_x` - | | - | variable not in all patterns + = 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 `Result` +help: you might want to use `let else` to handle the variant that isn't matched + | +LL | let Ok(_x) = res else { todo!() }; + | ++++++++++++++++ -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0408`. +For more information about this error, try `rustc --explain E0005`. diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/bindings.rs b/tests/ui/rfcs/rfc-0000-never_patterns/bindings.rs index 61a4231e5da..756ead9e184 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/bindings.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/bindings.rs @@ -6,23 +6,20 @@ enum Void {} fn main() { let x: Result = Ok(false); - // FIXME(never_patterns): Never patterns in or-patterns don't need to share the same bindings. match x { Ok(_x) | Err(&!) => {} - //~^ ERROR: is not bound in all patterns } let (Ok(_x) | Err(&!)) = x; - //~^ ERROR: is not bound in all patterns - // FIXME(never_patterns): A never pattern mustn't have bindings. match x { Ok(_) => {} Err(&(_a, _b, !)), + //~^ ERROR: never patterns cannot contain variable bindings + //~| ERROR: never patterns cannot contain variable bindings } match x { Ok(_ok) | Err(&(_a, _b, !)) => {} - //~^ ERROR: is not bound in all patterns - //~| ERROR: is not bound in all patterns - //~| ERROR: is not bound in all patterns + //~^ ERROR: never patterns cannot contain variable bindings + //~| ERROR: never patterns cannot contain variable bindings } } diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/bindings.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/bindings.stderr index 56cefcb51f3..4e83b843ae8 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/bindings.stderr +++ b/tests/ui/rfcs/rfc-0000-never_patterns/bindings.stderr @@ -1,43 +1,26 @@ -error[E0408]: variable `_x` is not bound in all patterns - --> $DIR/bindings.rs:11:18 +error: never patterns cannot contain variable bindings + --> $DIR/bindings.rs:16:15 | -LL | Ok(_x) | Err(&!) => {} - | -- ^^^^^^^ pattern doesn't bind `_x` - | | - | variable not in all patterns +LL | Err(&(_a, _b, !)), + | ^^ help: use a wildcard `_` instead -error[E0408]: variable `_x` is not bound in all patterns - --> $DIR/bindings.rs:14:19 +error: never patterns cannot contain variable bindings + --> $DIR/bindings.rs:16:19 | -LL | let (Ok(_x) | Err(&!)) = x; - | -- ^^^^^^^ pattern doesn't bind `_x` - | | - | variable not in all patterns +LL | Err(&(_a, _b, !)), + | ^^ help: use a wildcard `_` instead -error[E0408]: variable `_a` is not bound in all patterns - --> $DIR/bindings.rs:23:9 +error: never patterns cannot contain variable bindings + --> $DIR/bindings.rs:21:25 | LL | Ok(_ok) | Err(&(_a, _b, !)) => {} - | ^^^^^^^ -- variable not in all patterns - | | - | pattern doesn't bind `_a` + | ^^ help: use a wildcard `_` instead -error[E0408]: variable `_b` is not bound in all patterns - --> $DIR/bindings.rs:23:9 +error: never patterns cannot contain variable bindings + --> $DIR/bindings.rs:21:29 | LL | Ok(_ok) | Err(&(_a, _b, !)) => {} - | ^^^^^^^ -- variable not in all patterns - | | - | pattern doesn't bind `_b` + | ^^ help: use a wildcard `_` instead -error[E0408]: variable `_ok` is not bound in all patterns - --> $DIR/bindings.rs:23:19 - | -LL | Ok(_ok) | Err(&(_a, _b, !)) => {} - | --- ^^^^^^^^^^^^^^^^^ pattern doesn't bind `_ok` - | | - | variable not in all patterns +error: aborting due to 4 previous errors -error: aborting due to 5 previous errors - -For more information about this error, try `rustc --explain E0408`. diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/parse.rs b/tests/ui/rfcs/rfc-0000-never_patterns/parse.rs index f254b9c201c..566bb071646 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/parse.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/parse.rs @@ -71,6 +71,7 @@ fn parse(x: Void) { let ! = x; let y @ ! = x; + //~^ ERROR: never patterns cannot contain variable bindings } fn foo(!: Void) {} diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/parse.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/parse.stderr index e81a13a3967..17d1b7e0d43 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/parse.stderr +++ b/tests/ui/rfcs/rfc-0000-never_patterns/parse.stderr @@ -22,6 +22,12 @@ error: top-level or-patterns are not allowed in `let` bindings LL | let Ok(_) | Err(!) = &res; // Disallowed; see #82048. | ^^^^^^^^^^^^^^ help: wrap the pattern in parentheses: `(Ok(_) | Err(!))` +error: never patterns cannot contain variable bindings + --> $DIR/parse.rs:73:9 + | +LL | let y @ ! = x; + | ^ help: use a wildcard `_` instead + error: a guard on a never pattern will never be run --> $DIR/parse.rs:31:20 | @@ -40,5 +46,5 @@ error: a guard on a never pattern will never be run LL | never!() if true, | ^^^^ help: remove this guard -error: aborting due to 7 previous errors +error: aborting due to 8 previous errors