Skip to content

Commit ae8f558

Browse files
committed
Add suggestions for expressions in patterns
1 parent 56c8e66 commit ae8f558

23 files changed

+1416
-306
lines changed

compiler/rustc_errors/src/lib.rs

+30
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,8 @@ pub enum StashKey {
535535
/// Query cycle detected, stashing in favor of a better error.
536536
Cycle,
537537
UndeterminedMacroResolution,
538+
/// Used by `Parser::maybe_recover_trailing_expr`
539+
ExprInPat,
538540
}
539541

540542
fn default_track_diagnostic<R>(diag: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R {
@@ -837,6 +839,23 @@ impl<'a> DiagCtxtHandle<'a> {
837839
Some(Diag::new_diagnostic(self, diag))
838840
}
839841

842+
/// Steals a previously stashed error with the given `Span` and
843+
/// [`StashKey`] as the key, and cancels it if found.
844+
/// Panics if the found diagnostic's level isn't `Level::Error`.
845+
pub fn steal_err(&self, span: Span, key: StashKey, _: ErrorGuaranteed) -> bool {
846+
let key = (span.with_parent(None), key);
847+
// FIXME(#120456) - is `swap_remove` correct?
848+
self.inner
849+
.borrow_mut()
850+
.stashed_diagnostics
851+
.swap_remove(&key)
852+
.inspect(|(diag, guar)| {
853+
assert_eq!(diag.level, Error);
854+
assert!(guar.is_some())
855+
})
856+
.is_some()
857+
}
858+
840859
/// Steals a previously stashed error with the given `Span` and
841860
/// [`StashKey`] as the key, modifies it, and emits it. Returns `None` if
842861
/// no matching diagnostic is found. Panics if the found diagnostic's level
@@ -1281,6 +1300,17 @@ impl<'a> DiagCtxtHandle<'a> {
12811300
self.create_err(err).emit()
12821301
}
12831302

1303+
/// See [`DiagCtxt::stash_diagnostic`] for details.
1304+
#[track_caller]
1305+
pub fn stash_err(
1306+
&'a self,
1307+
span: Span,
1308+
key: StashKey,
1309+
err: impl Diagnostic<'a>,
1310+
) -> ErrorGuaranteed {
1311+
self.create_err(err).stash(span, key).unwrap()
1312+
}
1313+
12841314
/// Ensures that an error is printed. See `Level::DelayedBug`.
12851315
//
12861316
// No `#[rustc_lint_diagnostics]` and no `impl Into<DiagMessage>` because bug messages aren't

compiler/rustc_parse/messages.ftl

+8
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,14 @@ parse_unexpected_expr_in_pat =
782782
783783
.label = arbitrary expressions are not allowed in patterns
784784
785+
parse_unexpected_expr_in_pat_const_sugg = extract the expression into a `const` and refer to it
786+
787+
parse_unexpected_expr_in_pat_create_guard_sugg = check the value in an arm guard
788+
789+
parse_unexpected_expr_in_pat_inline_const_sugg = wrap the expression in a inline const (requires `{"#"}![feature(inline_const_pat)]`)
790+
791+
parse_unexpected_expr_in_pat_update_guard_sugg = check the value in the arm guard
792+
785793
parse_unexpected_if_with_if = unexpected `if` in the condition expression
786794
.suggestion = remove the `if`
787795

compiler/rustc_parse/src/errors.rs

+68
Original file line numberDiff line numberDiff line change
@@ -2424,6 +2424,74 @@ pub(crate) struct UnexpectedExpressionInPattern {
24242424
pub is_bound: bool,
24252425
}
24262426

2427+
#[derive(Subdiagnostic)]
2428+
pub(crate) enum UnexpectedExpressionInPatternArmSugg {
2429+
#[multipart_suggestion(
2430+
parse_unexpected_expr_in_pat_create_guard_sugg,
2431+
applicability = "maybe-incorrect"
2432+
)]
2433+
CreateGuard {
2434+
/// The span of the `PatKind:Err` to be transformed into a `PatKind::Ident`.
2435+
#[suggestion_part(code = "{ident}")]
2436+
ident_span: Span,
2437+
/// The end of the match arm's pattern.
2438+
#[suggestion_part(code = " if {ident} == {expr}")]
2439+
pat_hi: Span,
2440+
/// The suggested identifier.
2441+
ident: String,
2442+
/// `ident_span`'s snippet.
2443+
expr: String,
2444+
},
2445+
#[multipart_suggestion(
2446+
parse_unexpected_expr_in_pat_update_guard_sugg,
2447+
applicability = "maybe-incorrect"
2448+
)]
2449+
UpdateGuard {
2450+
/// The span of the `PatKind:Err` to be transformed into a `PatKind::Ident`.
2451+
#[suggestion_part(code = "{ident}")]
2452+
ident_span: Span,
2453+
/// The beginning of the match arm guard's expression.
2454+
#[suggestion_part(code = "(")]
2455+
guard_lo: Span,
2456+
/// The end of the match arm guard's expression.
2457+
#[suggestion_part(code = ") && {ident} == {expr}")]
2458+
guard_hi: Span,
2459+
/// The suggested identifier.
2460+
ident: String,
2461+
/// `ident_span`'s snippet.
2462+
expr: String,
2463+
},
2464+
}
2465+
2466+
#[derive(Subdiagnostic)]
2467+
#[multipart_suggestion(parse_unexpected_expr_in_pat_const_sugg, applicability = "has-placeholders")]
2468+
pub(crate) struct UnexpectedExpressionInPatternConstSugg {
2469+
/// The beginning of statement's line.
2470+
#[suggestion_part(code = "{indentation}const {ident}: _ = {expr};\n")]
2471+
pub stmt_lo: Span,
2472+
/// The span of the `PatKind:Err` to be transformed into a `PatKind::Ident`.
2473+
#[suggestion_part(code = "{ident}")]
2474+
pub ident_span: Span,
2475+
/// The suggested identifier.
2476+
pub ident: String,
2477+
/// `ident_span`'s snippet.
2478+
pub expr: String,
2479+
/// The statement's block's indentation.
2480+
pub indentation: String,
2481+
}
2482+
2483+
#[derive(Subdiagnostic)]
2484+
#[multipart_suggestion(
2485+
parse_unexpected_expr_in_pat_inline_const_sugg,
2486+
applicability = "maybe-incorrect"
2487+
)]
2488+
pub(crate) struct UnexpectedExpressionInPatternInlineConstSugg {
2489+
#[suggestion_part(code = "const {{ ")]
2490+
pub start_span: Span,
2491+
#[suggestion_part(code = " }}")]
2492+
pub end_span: Span,
2493+
}
2494+
24272495
#[derive(Diagnostic)]
24282496
#[diag(parse_unexpected_paren_in_range_pat)]
24292497
pub(crate) struct UnexpectedParenInRangePat {

compiler/rustc_parse/src/parser/item.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2406,6 +2406,7 @@ impl<'a> Parser<'a> {
24062406
}
24072407
(AttrVec::new(), None)
24082408
};
2409+
24092410
attrs.extend(inner_attrs);
24102411
Ok(body)
24112412
}

compiler/rustc_parse/src/parser/pat.rs

+195-11
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,23 @@ use crate::errors::{
66
InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern,
77
PatternOnWrongSideOfAt, RemoveLet, RepeatedMutInPattern, SwitchRefBoxOrder,
88
TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg, TrailingVertNotAllowed,
9-
UnexpectedExpressionInPattern, UnexpectedLifetimeInPattern, UnexpectedParenInRangePat,
10-
UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam,
11-
UnexpectedVertVertInPattern,
9+
UnexpectedExpressionInPattern, UnexpectedExpressionInPatternArmSugg,
10+
UnexpectedExpressionInPatternConstSugg, UnexpectedExpressionInPatternInlineConstSugg,
11+
UnexpectedLifetimeInPattern, UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
12+
UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern,
1213
};
1314
use crate::parser::expr::{could_be_unclosed_char_literal, DestructuredFloat, LhsExpr};
1415
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
1516
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
1617
use rustc_ast::ptr::P;
1718
use rustc_ast::token::{self, BinOpToken, Delimiter, Token};
19+
use rustc_ast::visit::{walk_arm, walk_pat, walk_pat_field, Visitor};
1820
use rustc_ast::{
19-
self as ast, AttrVec, BindingMode, ByRef, Expr, ExprKind, MacCall, Mutability, Pat, PatField,
20-
PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
21+
self as ast, Arm, AttrVec, BindingMode, ByRef, Expr, ExprKind, LocalKind, MacCall, Mutability,
22+
Pat, PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, Stmt, StmtKind,
2123
};
2224
use rustc_ast_pretty::pprust;
23-
use rustc_errors::{Applicability, Diag, PResult};
25+
use rustc_errors::{Applicability, Diag, PResult, StashKey};
2426
use rustc_session::errors::ExprParenthesesNeeded;
2527
use rustc_span::source_map::{respan, Spanned};
2628
use rustc_span::symbol::{kw, sym, Ident};
@@ -422,10 +424,15 @@ impl<'a> Parser<'a> {
422424
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
423425
&& self.look_ahead(1, Token::is_range_separator);
424426

427+
let span = expr.span;
428+
425429
return Some((
426-
self.dcx()
427-
.emit_err(UnexpectedExpressionInPattern { span: expr.span, is_bound }),
428-
expr.span,
430+
self.dcx().stash_err(
431+
span,
432+
StashKey::ExprInPat,
433+
UnexpectedExpressionInPattern { span, is_bound },
434+
),
435+
span,
429436
));
430437
}
431438
}
@@ -434,6 +441,161 @@ impl<'a> Parser<'a> {
434441
None
435442
}
436443

444+
pub(super) fn maybe_emit_stashed_expr_in_pat(&mut self, stmt: &Stmt) {
445+
if self.dcx().has_errors().is_none() {
446+
return;
447+
}
448+
449+
// WIP: once a fn body has been parsed, we walk through all its patterns,
450+
// and emit now what errors `maybe_recover_trailing_expr()` stashed,
451+
// with suggestions depending on which statement the pattern is.
452+
453+
struct PatVisitor<'a> {
454+
/// `self`
455+
parser: &'a Parser<'a>,
456+
/// The current statement.
457+
stmt: &'a Stmt,
458+
/// The current match arm.
459+
arm: Option<&'a Arm>,
460+
/// The current struct field.
461+
field: Option<&'a PatField>,
462+
}
463+
464+
impl<'a> Visitor<'a> for PatVisitor<'a> {
465+
fn visit_arm(&mut self, a: &'a Arm) -> Self::Result {
466+
self.arm = Some(a);
467+
walk_arm(self, a);
468+
self.arm = None;
469+
}
470+
471+
fn visit_pat_field(&mut self, fp: &'a PatField) -> Self::Result {
472+
self.field = Some(fp);
473+
walk_pat_field(self, fp);
474+
self.field = None;
475+
}
476+
477+
fn visit_pat(&mut self, p: &'a Pat) -> Self::Result {
478+
// Looks for stashed `ExprInPat` errors in `stash_span`, and emit them with suggestions.
479+
// `stash_span` is contained in `expr_span`, the latter being larger in borrow patterns;
480+
// ```txt
481+
// &mut x.y
482+
// -----^^^ `stash_span`
483+
// |
484+
// `expr_span`
485+
// ```
486+
let emit_now = |that: &Self, stash_span: Span, expr_span: Span| -> Self::Result {
487+
that.parser.dcx().try_steal_modify_and_emit_err(
488+
stash_span,
489+
StashKey::ExprInPat,
490+
|err| {
491+
let sm = that.parser.psess.source_map();
492+
let stmt = that.stmt;
493+
let line_lo = sm.span_extend_to_line(stmt.span).shrink_to_lo();
494+
let indentation = sm.indentation_before(stmt.span).unwrap_or_default();
495+
let expr = that.parser.span_to_snippet(expr_span).unwrap();
496+
497+
err.span.replace(stash_span, expr_span);
498+
499+
if let StmtKind::Let(local) = &stmt.kind {
500+
// If we have an `ExprInPat`, the user tried to assign a value to another value,
501+
// which doesn't makes much sense.
502+
match &local.kind {
503+
LocalKind::Decl => {}
504+
LocalKind::Init(_) => {}
505+
LocalKind::InitElse(_, _) => {}
506+
}
507+
} else {
508+
// help: use an arm guard `if val == expr`
509+
if let Some(arm) = &self.arm {
510+
let (ident, ident_span) = match self.field {
511+
Some(field) => (
512+
field.ident.to_string(),
513+
field.ident.span.to(expr_span),
514+
),
515+
None => ("val".to_owned(), expr_span),
516+
};
517+
518+
match &arm.guard {
519+
None => {
520+
err.subdiagnostic(
521+
UnexpectedExpressionInPatternArmSugg::CreateGuard {
522+
ident_span,
523+
pat_hi: arm.pat.span.shrink_to_hi(),
524+
ident,
525+
expr: expr.clone(),
526+
},
527+
);
528+
}
529+
Some(guard) => {
530+
err.subdiagnostic(
531+
UnexpectedExpressionInPatternArmSugg::UpdateGuard {
532+
ident_span,
533+
guard_lo: guard.span.shrink_to_lo(),
534+
guard_hi: guard.span.shrink_to_hi(),
535+
ident,
536+
expr: expr.clone(),
537+
},
538+
);
539+
}
540+
}
541+
}
542+
543+
// help: extract the expr into a `const VAL: _ = expr`
544+
let ident = match self.field {
545+
Some(field) => field.ident.as_str().to_uppercase(),
546+
None => "VAL".to_owned(),
547+
};
548+
err.subdiagnostic(UnexpectedExpressionInPatternConstSugg {
549+
stmt_lo: line_lo,
550+
ident_span: expr_span,
551+
expr,
552+
ident,
553+
indentation,
554+
});
555+
556+
// help: wrap the expr in a `const { expr }`
557+
// FIXME(inline_const_pat): once stabilized, remove this check and remove the `(requires #[feature(inline_const_pat)]` note from the message
558+
if that.parser.psess.unstable_features.is_nightly_build() {
559+
err.subdiagnostic(
560+
UnexpectedExpressionInPatternInlineConstSugg {
561+
start_span: expr_span.shrink_to_lo(),
562+
end_span: expr_span.shrink_to_hi(),
563+
},
564+
);
565+
}
566+
}
567+
},
568+
);
569+
}; // end of `emit_now` closure, we're back in `visit_pat`
570+
571+
match &p.kind {
572+
// Base expression
573+
PatKind::Err(_) => emit_now(self, p.span, p.span),
574+
// Sub-patterns
575+
PatKind::Box(subpat) | PatKind::Ref(subpat, _)
576+
if matches!(subpat.kind, PatKind::Err(_)) =>
577+
{
578+
emit_now(self, subpat.span, p.span)
579+
}
580+
// Sub-expressions
581+
PatKind::Range(start, end, _) => {
582+
if let Some(start) = start {
583+
emit_now(self, start.span, start.span);
584+
}
585+
586+
if let Some(end) = end {
587+
emit_now(self, end.span, end.span);
588+
}
589+
}
590+
// Walk continuation
591+
_ => walk_pat(self, p),
592+
}
593+
}
594+
} // end of `PatVisitor` impl, we're back in `maybe_emit_stashed_expr_in_pat`
595+
596+
PatVisitor { parser: self, stmt, arm: None, field: None }.visit_stmt(stmt);
597+
}
598+
437599
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
438600
/// allowed).
439601
fn parse_pat_with_range_pat(
@@ -583,7 +745,11 @@ impl<'a> Parser<'a> {
583745

584746
match self.parse_range_end() {
585747
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
586-
None => PatKind::Lit(begin),
748+
None => match &begin.kind {
749+
// Avoid `PatKind::Lit(ExprKind::Err)`
750+
ExprKind::Err(guar) => PatKind::Err(*guar),
751+
_ => PatKind::Lit(begin),
752+
},
587753
}
588754
}
589755
Err(err) => return self.fatal_unexpected_non_pat(err, expected),
@@ -755,7 +921,25 @@ impl<'a> Parser<'a> {
755921

756922
Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) {
757923
None => pat,
758-
Some((guar, _)) => PatKind::Err(guar),
924+
Some((guar, _)) => {
925+
// We just recovered a bigger expression, so cancel its children
926+
// (e.g. `(1 + 2) * 3`, cancel “`1 + 2` is not a pattern”).
927+
match pat {
928+
PatKind::Paren(pat) => {
929+
self.dcx().steal_err(pat.span, StashKey::ExprInPat, guar);
930+
}
931+
932+
PatKind::Tuple(fields) => {
933+
for pat in fields {
934+
self.dcx().steal_err(pat.span, StashKey::ExprInPat, guar);
935+
}
936+
}
937+
938+
_ => unreachable!(),
939+
}
940+
941+
PatKind::Err(guar)
942+
}
759943
})
760944
}
761945

0 commit comments

Comments
 (0)