1
Fork 0

Auto merge of #118527 - Nadrieril:never_patterns_parse, r=compiler-errors

never_patterns: Parse match arms with no body

Never patterns are meant to signal unreachable cases, and thus don't take bodies:
```rust
let ptr: *const Option<!> = ...;
match *ptr {
    None => { foo(); }
    Some(!),
}
```
This PR makes rustc accept the above, and enforces that an arm has a body xor is a never pattern. This affects parsing of match arms even with the feature off, so this is delicate. (Plus this is my first non-trivial change to the parser).

~~The last commit is optional; it introduces a bit of churn to allow the new suggestions to be machine-applicable. There may be a better solution? I'm not sure.~~ EDIT: I removed that commit

r? `@compiler-errors`
This commit is contained in:
bors 2023-12-08 17:08:52 +00:00
commit 2b399b5275
47 changed files with 777 additions and 241 deletions

View file

@ -2839,7 +2839,6 @@ impl<'a> Parser<'a> {
pub(crate) fn maybe_recover_unexpected_comma(
&mut self,
lo: Span,
is_mac_invoc: bool,
rt: CommaRecoveryMode,
) -> PResult<'a, ()> {
if self.token != token::Comma {
@ -2860,28 +2859,24 @@ impl<'a> Parser<'a> {
let seq_span = lo.to(self.prev_token.span);
let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern");
if let Ok(seq_snippet) = self.span_to_snippet(seq_span) {
if is_mac_invoc {
err.note(fluent::parse_macro_expands_to_match_arm);
} else {
err.multipart_suggestion(
format!(
"try adding parentheses to match on a tuple{}",
if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." },
),
vec![
(seq_span.shrink_to_lo(), "(".to_string()),
(seq_span.shrink_to_hi(), ")".to_string()),
],
err.multipart_suggestion(
format!(
"try adding parentheses to match on a tuple{}",
if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." },
),
vec![
(seq_span.shrink_to_lo(), "(".to_string()),
(seq_span.shrink_to_hi(), ")".to_string()),
],
Applicability::MachineApplicable,
);
if let CommaRecoveryMode::EitherTupleOrPipe = rt {
err.span_suggestion(
seq_span,
"...or a vertical bar to match on multiple alternatives",
seq_snippet.replace(',', " |"),
Applicability::MachineApplicable,
);
if let CommaRecoveryMode::EitherTupleOrPipe = rt {
err.span_suggestion(
seq_span,
"...or a vertical bar to match on multiple alternatives",
seq_snippet.replace(',', " |"),
Applicability::MachineApplicable,
);
}
}
}
Err(err)

View file

@ -2899,127 +2899,155 @@ impl<'a> Parser<'a> {
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
let lo = this.token.span;
let (pat, guard) = this.parse_match_arm_pat_and_guard()?;
let arrow_span = this.token.span;
if let Err(mut err) = this.expect(&token::FatArrow) {
// We might have a `=>` -> `=` or `->` typo (issue #89396).
if TokenKind::FatArrow
.similar_tokens()
.is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind))
{
err.span_suggestion(
this.token.span,
"use a fat arrow to start a match arm",
"=>",
Applicability::MachineApplicable,
);
if matches!(
(&this.prev_token.kind, &this.token.kind),
(token::DotDotEq, token::Gt)
) {
// `error_inclusive_range_match_arrow` handles cases like `0..=> {}`,
// so we suppress the error here
err.delay_as_bug();
} else {
err.emit();
}
this.bump();
} else {
return Err(err);
}
}
let arm_start_span = this.token.span;
let expr = this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| {
err.span_label(arrow_span, "while parsing the `match` arm starting here");
err
})?;
let require_comma = classify::expr_requires_semi_to_be_stmt(&expr)
&& this.token != token::CloseDelim(Delimiter::Brace);
let hi = this.prev_token.span;
if require_comma {
let sm = this.sess.source_map();
if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) {
let span = body.span;
return Ok((
ast::Arm {
attrs,
pat,
guard,
body,
span,
id: DUMMY_NODE_ID,
is_placeholder: false,
},
TrailingToken::None,
));
}
let span_before_body = this.prev_token.span;
let arm_body;
let is_fat_arrow = this.check(&token::FatArrow);
let is_almost_fat_arrow = TokenKind::FatArrow
.similar_tokens()
.is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind));
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)])
.or_else(|mut err| {
if this.token == token::FatArrow {
if let Ok(expr_lines) = sm.span_to_lines(expr.span)
&& let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span)
&& arm_start_lines.lines[0].end_col == expr_lines.lines[0].end_col
&& expr_lines.lines.len() == 2
{
// We check whether there's any trailing code in the parse span,
// if there isn't, we very likely have the following:
//
// X | &Y => "y"
// | -- - missing comma
// | |
// | arrow_span
// X | &X => "x"
// | - ^^ self.token.span
// | |
// | parsed until here as `"y" & X`
err.span_suggestion_short(
arm_start_span.shrink_to_hi(),
"missing a comma here to end this `match` arm",
",",
Applicability::MachineApplicable,
);
return Err(err);
}
} else {
// FIXME(compiler-errors): We could also recover `; PAT =>` here
// Try to parse a following `PAT =>`, if successful
// then we should recover.
let mut snapshot = this.create_snapshot_for_diagnostic();
let pattern_follows = snapshot
.parse_pat_allow_top_alt(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::EitherTupleOrPipe,
)
.map_err(|err| err.cancel())
.is_ok();
if pattern_follows && snapshot.check(&TokenKind::FatArrow) {
err.cancel();
this.sess.emit_err(errors::MissingCommaAfterMatchArm {
span: hi.shrink_to_hi(),
});
return Ok(true);
}
}
err.span_label(arrow_span, "while parsing the `match` arm starting here");
Err(err)
})?;
} else {
this.eat(&token::Comma);
if let Err(mut err) = this.expect(&token::FatArrow) {
// We might have a `=>` -> `=` or `->` typo (issue #89396).
if is_almost_fat_arrow {
err.span_suggestion(
this.token.span,
"use a fat arrow to start a match arm",
"=>",
Applicability::MachineApplicable,
);
if matches!(
(&this.prev_token.kind, &this.token.kind),
(token::DotDotEq, token::Gt)
) {
// `error_inclusive_range_match_arrow` handles cases like `0..=> {}`,
// so we suppress the error here
err.delay_as_bug();
} else {
err.emit();
}
this.bump();
} else {
return Err(err);
}
}
let arrow_span = this.prev_token.span;
let arm_start_span = this.token.span;
let expr =
this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| {
err.span_label(arrow_span, "while parsing the `match` arm starting here");
err
})?;
let require_comma = classify::expr_requires_semi_to_be_stmt(&expr)
&& this.token != token::CloseDelim(Delimiter::Brace);
if !require_comma {
arm_body = Some(expr);
this.eat(&token::Comma);
Ok(false)
} else if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) {
arm_body = Some(body);
Ok(true)
} else {
let expr_span = expr.span;
arm_body = Some(expr);
this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)])
.map_err(|mut err| {
if this.token == token::FatArrow {
let sm = this.sess.source_map();
if let Ok(expr_lines) = sm.span_to_lines(expr_span)
&& let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span)
&& arm_start_lines.lines[0].end_col
== expr_lines.lines[0].end_col
&& expr_lines.lines.len() == 2
{
// We check whether there's any trailing code in the parse span,
// if there isn't, we very likely have the following:
//
// X | &Y => "y"
// | -- - missing comma
// | |
// | arrow_span
// X | &X => "x"
// | - ^^ self.token.span
// | |
// | parsed until here as `"y" & X`
err.span_suggestion_short(
arm_start_span.shrink_to_hi(),
"missing a comma here to end this `match` arm",
",",
Applicability::MachineApplicable,
);
}
} else {
err.span_label(
arrow_span,
"while parsing the `match` arm starting here",
);
}
err
})
}
};
let hi_span = arm_body.as_ref().map_or(span_before_body, |body| body.span);
let arm_span = lo.to(hi_span);
// We want to recover:
// X | Some(_) => foo()
// | - missing comma
// X | None => "x"
// | ^^^^ self.token.span
// as well as:
// X | Some(!)
// | - missing comma
// X | None => "x"
// | ^^^^ self.token.span
// But we musn't recover
// X | pat[0] => {}
// | ^ self.token.span
let recover_missing_comma = arm_body.is_some() || pat.could_be_never_pattern();
if recover_missing_comma {
result = result.or_else(|err| {
// FIXME(compiler-errors): We could also recover `; PAT =>` here
// Try to parse a following `PAT =>`, if successful
// then we should recover.
let mut snapshot = this.create_snapshot_for_diagnostic();
let pattern_follows = snapshot
.parse_pat_allow_top_alt(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::EitherTupleOrPipe,
)
.map_err(|err| err.cancel())
.is_ok();
if pattern_follows && snapshot.check(&TokenKind::FatArrow) {
err.cancel();
this.sess.emit_err(errors::MissingCommaAfterMatchArm {
span: arm_span.shrink_to_hi(),
});
return Ok(true);
}
Err(err)
});
}
result?;
Ok((
ast::Arm {
attrs,
pat,
guard,
body: expr,
span: lo.to(hi),
body: arm_body,
span: arm_span,
id: DUMMY_NODE_ID,
is_placeholder: false,
},

View file

@ -154,12 +154,8 @@ impl<'a> Parser<'a> {
}
Err(err) => return Err(err),
};
if rc == RecoverComma::Yes {
self.maybe_recover_unexpected_comma(
first_pat.span,
matches!(first_pat.kind, PatKind::MacCall(_)),
rt,
)?;
if rc == RecoverComma::Yes && !first_pat.could_be_never_pattern() {
self.maybe_recover_unexpected_comma(first_pat.span, rt)?;
}
// If the next token is not a `|`,
@ -200,8 +196,8 @@ impl<'a> Parser<'a> {
err.span_label(lo, WHILE_PARSING_OR_MSG);
err
})?;
if rc == RecoverComma::Yes {
self.maybe_recover_unexpected_comma(pat.span, false, rt)?;
if rc == RecoverComma::Yes && !pat.could_be_never_pattern() {
self.maybe_recover_unexpected_comma(pat.span, rt)?;
}
pats.push(pat);
}