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

never patterns: Check bindings wrt never patterns #119610

Merged
merged 7 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_resolve/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
.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 })
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_resolve/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
126 changes: 94 additions & 32 deletions compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -3190,12 +3192,31 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
self.resolve_pattern_top(&local.pat, PatternSource::Let);
}

/// build a map from pattern identifiers to binding-info's.
/// 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 binding_mode_map(&mut self, pat: &Pat) -> FxIndexMap<Ident, BindingInfo> {
/// Build a map from pattern identifiers to binding-info's, and check the bindings are
/// 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.
///
/// A never pattern by definition indicates an unreachable case. For example, matching on
/// `Result<T, &!>` could look like:
/// ```rust
/// # #![feature(never_type)]
/// # #![feature(never_patterns)]
/// # fn bar(_x: u32) {}
/// let foo: Result<u32, &!> = Ok(0);
/// match foo {
/// Ok(x) => bar(x),
/// Err(&!),
/// }
/// ```
/// This extends to product types: `(x, !)` is likewise unreachable. So it doesn't make sense to
/// have a binding here, and we tell the user to use `_` instead.
fn compute_and_check_binding_map(
&mut self,
pat: &Pat,
) -> Result<FxIndexMap<Ident, BindingInfo>, IsNeverPattern> {
Nadrieril marked this conversation as resolved.
Show resolved Hide resolved
let mut binding_map = FxIndexMap::default();
let mut is_never_pat = false;

pat.walk(&mut |pat| {
match pat.kind {
Expand All @@ -3207,18 +3228,27 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
PatKind::Or(ref ps) => {
// Check the consistency of this or-pattern and
// then add all bindings to the larger map.
for bm in self.check_consistent_bindings(ps) {
binding_map.extend(bm);
match self.compute_and_check_or_pat_binding_map(ps) {
Ok(bm) => binding_map.extend(bm),
Err(IsNeverPattern) => is_never_pat = true,
}
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 {
Expand All @@ -3228,33 +3258,52 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
)
}

/// Checks that all of the arms in an or-pattern have exactly the
/// same set of bindings, with the same binding modes for each.
fn check_consistent_bindings(
/// 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 and a boolean indicating whether the pattern is a never
/// pattern.
///
/// A never pattern by definition indicates an unreachable case. For example, destructuring a
/// `Result<T, &!>` could look like:
/// ```rust
/// # #![feature(never_type)]
/// # #![feature(never_patterns)]
/// # fn foo() -> Result<bool, &'static !> { Ok(true) }
/// let (Ok(x) | Err(&!)) = foo();
/// # let _ = x;
/// ```
/// Because the `Err(&!)` branch is never reached, it does not need to have the same bindings as
/// the other branches of the or-pattern. So we must ignore never pattern when checking the
/// bindings of an or-pattern.
/// Moreover, if all the subpatterns are never patterns (e.g. `Ok(!) | Err(!)`), then the
/// pattern as a whole counts as a never pattern (since it's definitionallly unreachable).
fn compute_and_check_or_pat_binding_map(
&mut self,
pats: &[P<Pat>],
) -> Vec<FxIndexMap<Ident, BindingInfo>> {
// pats is consistent.
) -> Result<FxIndexMap<Ident, BindingInfo>, IsNeverPattern> {
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.binding_mode_map(pat)).collect::<Vec<_>>();
// 1) Compute the binding maps of all arms; we must ignore never patterns here.
let not_never_pats = pats
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should use partition here. It would be nice to collect never pats even if we don't use them... 🤷

Copy link
Member Author

Choose a reason for hiding this comment

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

What would we do with them? Just store them in a Vec and drop it? I'm not sure I find that clearer; I could add more comments if you think this is confusing

.iter()
.filter_map(|pat| {
let binding_map = self.compute_and_check_binding_map(pat).ok()?;
Some((binding_map, pat))
})
.collect::<Vec<_>>();

// 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)
.map(|(key, binding)| (key.name, map_outer.get(key), binding));

let inners = inners.collect::<Vec<_>>();
.flat_map(|(map, _)| map);

for (name, info, &binding_inner) in inners {
match info {
for (key, binding_inner) in inners {
let name = key.name;
match map_outer.get(key) {
None => {
// The inner binding is missing in the outer.
let binding_error =
Expand Down Expand Up @@ -3295,19 +3344,32 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
self.report_error(v.0, ResolutionError::VariableBoundWithDifferentMode(name, v.1));
}

// 5) Finally bubble up all the binding maps.
maps
// 5) Bubble up the final binding map.
if not_never_pats.is_empty() {
// All the patterns are never patterns, so the whole or-pattern is one too.
Err(IsNeverPattern)
} else {
let mut binding_map = FxIndexMap::default();
for (bm, _) in not_never_pats {
binding_map.extend(bm);
}
Ok(binding_map)
}
}

/// Check the consistency of the outermost or-patterns.
fn check_consistent_bindings_top(&mut self, pat: &'ast Pat) {
/// Check the consistency of bindings wrt or-patterns and never patterns.
fn check_consistent_bindings(&mut self, pat: &'ast Pat) {
let mut is_or_or_never = false;
pat.walk(&mut |pat| match pat.kind {
PatKind::Or(ref ps) => {
self.check_consistent_bindings(ps);
PatKind::Or(..) | PatKind::Never => {
is_or_or_never = true;
false
}
_ => true,
})
});
if is_or_or_never {
let _ = self.compute_and_check_binding_map(pat);
}
}

fn resolve_arm(&mut self, arm: &'ast Arm) {
Expand Down Expand Up @@ -3336,7 +3398,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
visit::walk_pat(self, pat);
self.resolve_pattern_inner(pat, pat_src, bindings);
// This has to happen *after* we determine which pat_idents are variants:
self.check_consistent_bindings_top(pat);
self.check_consistent_bindings(pat);
}

/// Resolve bindings in a pattern. This is a helper to `resolve_pattern`.
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
1 change: 0 additions & 1 deletion tests/ui/feature-gates/feature-gate-never_patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ fn main() {
let res: Result<u32, Void> = 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();
Expand Down
37 changes: 14 additions & 23 deletions tests/ui/feature-gates/feature-gate-never_patterns.stderr
Original file line number Diff line number Diff line change
@@ -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(_),
| ^
Expand All @@ -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
|
Expand All @@ -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 | !
| ^
Expand All @@ -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 | !
| ^
Expand All @@ -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 | ! => {}
| ^
Expand All @@ -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(!),
| ^
Expand All @@ -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,
| ^
Expand All @@ -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`.
Loading
Loading