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

Correctly gate the parsing of match arms without body #118868

Merged
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
13 changes: 13 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,19 @@ impl Pat {
});
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.
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
} else {
// Either `body.is_none()` or `is_never_pattern` here.
if !is_never_pattern {
let suggestion = span.shrink_to_hi();
self.tcx.sess.emit_err(MatchArmWithNoBody { span, suggestion });
if self.tcx.features().never_patterns {
// 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 {
self.tcx.sess.emit_err(NeverPatternWithBody { span: body.span });
guard = None;
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_ast_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ ast_passes_item_underscore = `{$kind}` items in this context need a name
ast_passes_keyword_lifetime =
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
.help = consider using the `#[path]` attribute to specify filesystem path

Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_ast_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,3 +758,12 @@ pub struct AnonStructOrUnionNotAllowed {
pub span: Span,
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,
}
29 changes: 28 additions & 1 deletion compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(generic_const_items, "generic const items are experimental");
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 {
for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() {
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2918,7 +2918,15 @@ impl<'a> Parser<'a> {
let mut result = if !is_fat_arrow && !is_almost_fat_arrow {
// A pattern without a body, allowed for never patterns.
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 {
if let Err(mut err) = this.expect(&token::FatArrow) {
// We might have a `=>` -> `=` or `->` typo (issue #89396).
Expand Down
61 changes: 54 additions & 7 deletions tests/ui/feature-gates/feature-gate-never_patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,63 @@ fn main() {
unsafe {
let ptr: *const Void = NonNull::dangling().as_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`.
#[cfg(FALSE)]
unsafe {
let ptr: *const Void = NonNull::dangling().as_ptr();
match *ptr {
! => {} //~ ERROR `!` patterns are experimental
}
match Some(0) {
None => {}
#[cfg(FALSE)]
Some(_)
//~^ ERROR `match` arm with no body
}
match Some(0) {
_ => {}
#[cfg(FALSE)]
Some(_) if false
//~^ ERROR `match` arm with no body
}
}
82 changes: 80 additions & 2 deletions tests/ui/feature-gates/feature-gate-never_patterns.stderr
Original file line number Diff line number Diff line change
@@ -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
--> $DIR/feature-gate-never_patterns.rs:8:19
|
Expand Down Expand Up @@ -25,15 +40,78 @@ LL | !
= help: add `#![feature(never_patterns)]` to the crate attributes to enable

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 | ! => {}
| ^
|
= 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: 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.
For more information about an error, try `rustc --explain E0408`.
2 changes: 0 additions & 2 deletions tests/ui/parser/match-arm-without-body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ fn main() {
pat!()
//~^ ERROR expected `,` following `match` arm
//~| HELP missing a comma here
//~| ERROR `match` arm with no body
//~| HELP add a body after the pattern
_ => {}
}
match Some(false) {
Expand Down
16 changes: 5 additions & 11 deletions tests/ui/parser/match-arm-without-body.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,19 @@ error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:30:9
|
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
--> $DIR/match-arm-without-body.rs:40:9
|
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
--> $DIR/match-arm-without-body.rs:45:9
|
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
--> $DIR/match-arm-without-body.rs:51:9
Expand All @@ -98,19 +98,13 @@ error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:61:9
|
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!(),`

error: `match` arm with no body
--> $DIR/match-arm-without-body.rs:74:9
--> $DIR/match-arm-without-body.rs:72:9
|
LL | pat!(),
| ^^^^^^- help: add a body after the pattern: `=> todo!(),`

error: aborting due to 14 previous errors
error: aborting due to 13 previous errors

Loading