Rollup merge of #118868 - Nadrieril:correctly-gate-never_patterns-parsing, r=petrochenkov

Correctly gate the parsing of match arms without body

https://github.com/rust-lang/rust/pull/118527 accidentally allowed the following to parse on stable:
```rust
match Some(0) {
    None => { foo(); }
    #[cfg(FALSE)]
    Some(_)
}
```

This fixes that oversight. The way I choose which error to emit is the best I could think of, I'm open if you know a better way.

r? `@petrochenkov` since you're the one who noticed
This commit is contained in:
Matthias Krüger 2023-12-12 17:40:56 +01:00 committed by GitHub
commit d661974017
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 207 additions and 26 deletions

View File

@ -676,6 +676,19 @@ impl Pat {
}); });
could_be_never_pattern could_be_never_pattern
} }
/// Whether this contains a `!` pattern. This in particular means that a feature gate error will
/// be raised if the feature is off. Used to avoid gating the feature twice.
pub fn contains_never_pattern(&self) -> bool {
let mut contains_never_pattern = false;
self.walk(&mut |pat| {
if matches!(pat.kind, PatKind::Never) {
contains_never_pattern = true;
}
true
});
contains_never_pattern
}
} }
/// A single field in a struct pattern. /// A single field in a struct pattern.

View File

@ -581,8 +581,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
} else { } else {
// Either `body.is_none()` or `is_never_pattern` here. // Either `body.is_none()` or `is_never_pattern` here.
if !is_never_pattern { if !is_never_pattern {
let suggestion = span.shrink_to_hi(); if self.tcx.features().never_patterns {
self.tcx.sess.emit_err(MatchArmWithNoBody { span, suggestion }); // If the feature is off we already emitted the error after parsing.
let suggestion = span.shrink_to_hi();
self.tcx.sess.emit_err(MatchArmWithNoBody { span, suggestion });
}
} else if let Some(body) = &arm.body { } else if let Some(body) = &arm.body {
self.tcx.sess.emit_err(NeverPatternWithBody { span: body.span }); self.tcx.sess.emit_err(NeverPatternWithBody { span: body.span });
guard = None; guard = None;

View File

@ -174,6 +174,10 @@ ast_passes_item_underscore = `{$kind}` items in this context need a name
ast_passes_keyword_lifetime = ast_passes_keyword_lifetime =
lifetimes cannot use keyword names lifetimes cannot use keyword names
ast_passes_match_arm_with_no_body =
`match` arm with no body
.suggestion = add a body after the pattern
ast_passes_module_nonascii = trying to load file for module `{$name}` with non-ascii identifier name ast_passes_module_nonascii = trying to load file for module `{$name}` with non-ascii identifier name
.help = consider using the `#[path]` attribute to specify filesystem path .help = consider using the `#[path]` attribute to specify filesystem path

View File

@ -758,3 +758,12 @@ pub struct AnonStructOrUnionNotAllowed {
pub span: Span, pub span: Span,
pub struct_or_union: &'static str, pub struct_or_union: &'static str,
} }
#[derive(Diagnostic)]
#[diag(ast_passes_match_arm_with_no_body)]
pub struct MatchArmWithNoBody {
#[primary_span]
pub span: Span,
#[suggestion(code = " => todo!(),", applicability = "has-placeholders")]
pub suggestion: Span,
}

View File

@ -554,7 +554,34 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(explicit_tail_calls, "`become` expression is experimental"); gate_all!(explicit_tail_calls, "`become` expression is experimental");
gate_all!(generic_const_items, "generic const items are experimental"); gate_all!(generic_const_items, "generic const items are experimental");
gate_all!(unnamed_fields, "unnamed fields are not yet fully implemented"); gate_all!(unnamed_fields, "unnamed fields are not yet fully implemented");
gate_all!(never_patterns, "`!` patterns are experimental");
if !visitor.features.never_patterns {
if let Some(spans) = spans.get(&sym::never_patterns) {
for &span in spans {
if span.allows_unstable(sym::never_patterns) {
continue;
}
let sm = sess.source_map();
// We gate two types of spans: the span of a `!` pattern, and the span of a
// match arm without a body. For the latter we want to give the user a normal
// error.
if let Ok(snippet) = sm.span_to_snippet(span)
&& snippet == "!"
{
feature_err(
&sess.parse_sess,
sym::never_patterns,
span,
"`!` patterns are experimental",
)
.emit();
} else {
let suggestion = span.shrink_to_hi();
sess.emit_err(errors::MatchArmWithNoBody { span, suggestion });
}
}
}
}
if !visitor.features.negative_bounds { if !visitor.features.negative_bounds {
for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() { for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() {

View File

@ -2918,7 +2918,15 @@ impl<'a> Parser<'a> {
let mut result = if !is_fat_arrow && !is_almost_fat_arrow { let mut result = if !is_fat_arrow && !is_almost_fat_arrow {
// A pattern without a body, allowed for never patterns. // A pattern without a body, allowed for never patterns.
arm_body = None; arm_body = None;
this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]) this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]).map(
|x| {
// Don't gate twice
if !pat.contains_never_pattern() {
this.sess.gated_spans.gate(sym::never_patterns, pat.span);
}
x
},
)
} else { } else {
if let Err(mut err) = this.expect(&token::FatArrow) { if let Err(mut err) = this.expect(&token::FatArrow) {
// We might have a `=>` -> `=` or `->` typo (issue #89396). // We might have a `=>` -> `=` or `->` typo (issue #89396).

View File

@ -12,16 +12,63 @@ fn main() {
unsafe { unsafe {
let ptr: *const Void = NonNull::dangling().as_ptr(); let ptr: *const Void = NonNull::dangling().as_ptr();
match *ptr { match *ptr {
! //~ ERROR `!` patterns are experimental !
//~^ ERROR `!` patterns are experimental
}
// Check that the gate operates even behind `cfg`.
#[cfg(FALSE)]
match *ptr {
!
//~^ ERROR `!` patterns are experimental
}
#[cfg(FALSE)]
match *ptr {
! => {}
//~^ ERROR `!` patterns are experimental
} }
} }
// Correctly gate match arms with no body.
match Some(0) {
None => {}
Some(_),
//~^ ERROR unexpected `,` in pattern
}
match Some(0) {
None => {}
Some(_)
//~^ ERROR `match` arm with no body
}
match Some(0) {
_ => {}
Some(_) if false,
//~^ ERROR `match` arm with no body
Some(_) if false
//~^ ERROR `match` arm with no body
}
match res {
Ok(_) => {}
Err(!),
//~^ ERROR `!` patterns are experimental
}
match res {
Err(!) if false,
//~^ ERROR `!` patterns are experimental
//~| ERROR a guard on a never pattern will never be run
_ => {}
}
// Check that the gate operates even behind `cfg`. // Check that the gate operates even behind `cfg`.
#[cfg(FALSE)] match Some(0) {
unsafe { None => {}
let ptr: *const Void = NonNull::dangling().as_ptr(); #[cfg(FALSE)]
match *ptr { Some(_)
! => {} //~ ERROR `!` patterns are experimental //~^ ERROR `match` arm with no body
} }
match Some(0) {
_ => {}
#[cfg(FALSE)]
Some(_) if false
//~^ ERROR `match` arm with no body
} }
} }

View File

@ -1,3 +1,18 @@
error: unexpected `,` in pattern
--> $DIR/feature-gate-never_patterns.rs:34:16
|
LL | Some(_),
| ^
|
help: try adding parentheses to match on a tuple...
|
LL | (Some(_),)
| + +
help: ...or a vertical bar to match on multiple alternatives
|
LL | Some(_) |
|
error[E0408]: variable `_x` is not bound in all patterns error[E0408]: variable `_x` is not bound in all patterns
--> $DIR/feature-gate-never_patterns.rs:8:19 --> $DIR/feature-gate-never_patterns.rs:8:19
| |
@ -25,7 +40,16 @@ LL | !
= help: add `#![feature(never_patterns)]` to the crate attributes to enable = help: add `#![feature(never_patterns)]` to the crate attributes to enable
error[E0658]: `!` patterns are experimental error[E0658]: `!` patterns are experimental
--> $DIR/feature-gate-never_patterns.rs:24:13 --> $DIR/feature-gate-never_patterns.rs:21:13
|
LL | !
| ^
|
= note: see issue #118155 <https://github.com/rust-lang/rust/issues/118155> for more information
= help: add `#![feature(never_patterns)]` to the crate attributes to enable
error[E0658]: `!` patterns are experimental
--> $DIR/feature-gate-never_patterns.rs:26:13
| |
LL | ! => {} LL | ! => {}
| ^ | ^
@ -33,7 +57,61 @@ LL | ! => {}
= note: see issue #118155 <https://github.com/rust-lang/rust/issues/118155> for more information = note: see issue #118155 <https://github.com/rust-lang/rust/issues/118155> for more information
= help: add `#![feature(never_patterns)]` to the crate attributes to enable = help: add `#![feature(never_patterns)]` to the crate attributes to enable
error: aborting due to 4 previous errors error: `match` arm with no body
--> $DIR/feature-gate-never_patterns.rs:39: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
|
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
|
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
|
LL | Err(!),
| ^
|
= note: see issue #118155 <https://github.com/rust-lang/rust/issues/118155> for more information
= help: add `#![feature(never_patterns)]` to the crate attributes to enable
error[E0658]: `!` patterns are experimental
--> $DIR/feature-gate-never_patterns.rs:55:13
|
LL | Err(!) if false,
| ^
|
= note: see issue #118155 <https://github.com/rust-lang/rust/issues/118155> for more information
= 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
|
LL | Some(_)
| ^^^^^^^- help: add a body after the pattern: `=> todo!(),`
error: `match` arm with no body
--> $DIR/feature-gate-never_patterns.rs:71: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
|
LL | Err(!) if false,
| ^^^^^ help: remove this guard
error: aborting due to 14 previous errors
Some errors have detailed explanations: E0408, E0658. Some errors have detailed explanations: E0408, E0658.
For more information about an error, try `rustc --explain E0408`. For more information about an error, try `rustc --explain E0408`.

View File

@ -66,8 +66,6 @@ fn main() {
pat!() pat!()
//~^ ERROR expected `,` following `match` arm //~^ ERROR expected `,` following `match` arm
//~| HELP missing a comma here //~| HELP missing a comma here
//~| ERROR `match` arm with no body
//~| HELP add a body after the pattern
_ => {} _ => {}
} }
match Some(false) { match Some(false) {

View File

@ -68,19 +68,19 @@ error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:30:9 --> $DIR/match-arm-without-body.rs:30:9
| |
LL | Some(_) if true LL | Some(_) if true
| ^^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` | ^^^^^^^- help: add a body after the pattern: `=> todo!(),`
error: `match` arm with no body error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:40:9 --> $DIR/match-arm-without-body.rs:40:9
| |
LL | Some(_) if true, LL | Some(_) if true,
| ^^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` | ^^^^^^^- help: add a body after the pattern: `=> todo!(),`
error: `match` arm with no body error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:45:9 --> $DIR/match-arm-without-body.rs:45:9
| |
LL | Some(_) if true, LL | Some(_) if true,
| ^^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` | ^^^^^^^- help: add a body after the pattern: `=> todo!(),`
error: `match` arm with no body error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:51:9 --> $DIR/match-arm-without-body.rs:51:9
@ -98,19 +98,13 @@ error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:61:9 --> $DIR/match-arm-without-body.rs:61:9
| |
LL | pat!() if true, LL | pat!() if true,
| ^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),`
error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:66:9
|
LL | pat!()
| ^^^^^^- help: add a body after the pattern: `=> todo!(),` | ^^^^^^- help: add a body after the pattern: `=> todo!(),`
error: `match` arm with no body error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:74:9 --> $DIR/match-arm-without-body.rs:72:9
| |
LL | pat!(), LL | pat!(),
| ^^^^^^- help: add a body after the pattern: `=> todo!(),` | ^^^^^^- help: add a body after the pattern: `=> todo!(),`
error: aborting due to 14 previous errors error: aborting due to 13 previous errors