Skip to content

Commit e95c640

Browse files
committed
Handle methodcalls & operators in patterns
1 parent 8424f8e commit e95c640

29 files changed

+797
-66
lines changed

compiler/rustc_parse/messages.ftl

+14
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,20 @@ parse_unexpected_const_param_declaration = unexpected `const` parameter declarat
770770
parse_unexpected_default_value_for_lifetime_in_generic_parameters = unexpected default lifetime parameter
771771
.label = lifetime parameters cannot have default values
772772
773+
parse_unexpected_expr_in_pat =
774+
expected {$is_bound ->
775+
[true] a pattern range bound
776+
*[false] a pattern
777+
}, found {$is_method_call ->
778+
[true] a method call
779+
*[false] an expression
780+
}
781+
782+
.label = {$is_method_call ->
783+
[true] method calls
784+
*[false] arbitrary expressions
785+
} are not allowed in patterns
786+
773787
parse_unexpected_if_with_if = unexpected `if` in the condition expression
774788
.suggestion = remove the `if`
775789

compiler/rustc_parse/src/errors.rs

+12
Original file line numberDiff line numberDiff line change
@@ -2402,6 +2402,18 @@ pub(crate) struct ExpectedCommaAfterPatternField {
24022402
pub span: Span,
24032403
}
24042404

2405+
#[derive(Diagnostic)]
2406+
#[diag(parse_unexpected_expr_in_pat)]
2407+
pub(crate) struct UnexpectedExpressionInPattern {
2408+
#[primary_span]
2409+
#[label]
2410+
pub span: Span,
2411+
/// Was a `RangePatternBound` expected?
2412+
pub is_bound: bool,
2413+
/// Was the unexpected expression a `MethodCallExpression`?
2414+
pub is_method_call: bool,
2415+
}
2416+
24052417
#[derive(Diagnostic)]
24062418
#[diag(parse_unexpected_paren_in_range_pat)]
24072419
pub(crate) struct UnexpectedParenInRangePat {

compiler/rustc_parse/src/parser/expr.rs

+13
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,19 @@ impl<'a> Parser<'a> {
445445
) if self.restrictions.contains(Restrictions::CONST_EXPR) => {
446446
return None;
447447
}
448+
// When recovering patterns as expressions, stop parsing when encountering an assignment `=`, an alternative `|`, or a range `..`.
449+
(
450+
Some(
451+
AssocOp::Assign
452+
| AssocOp::AssignOp(_)
453+
| AssocOp::BitOr
454+
| AssocOp::DotDot
455+
| AssocOp::DotDotEq,
456+
),
457+
_,
458+
) if self.restrictions.contains(Restrictions::IS_PAT) => {
459+
return None;
460+
}
448461
(Some(op), _) => (op, self.token.span),
449462
(None, Some((Ident { name: sym::and, span }, false))) if self.may_recover() => {
450463
self.dcx().emit_err(errors::InvalidLogicalOperator {

compiler/rustc_parse/src/parser/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ bitflags::bitflags! {
5353
const CONST_EXPR = 1 << 2;
5454
const ALLOW_LET = 1 << 3;
5555
const IN_IF_GUARD = 1 << 4;
56+
const IS_PAT = 1 << 5;
5657
}
5758
}
5859

compiler/rustc_parse/src/parser/pat.rs

+139-13
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
use super::{ForceCollect, Parser, PathStyle, TrailingToken};
1+
use super::{ForceCollect, Parser, PathStyle, Restrictions, TrailingToken};
22
use crate::errors::{
33
self, AmbiguousRangePattern, DotDotDotForRemainingFields, DotDotDotRangeToPatternNotAllowed,
44
DotDotDotRestPattern, EnumPatternInsteadOfIdentifier, ExpectedBindingLeftOfAt,
55
ExpectedCommaAfterPatternField, GenericArgsInPatRequireTurbofishSyntax,
66
InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern,
77
PatternOnWrongSideOfAt, RefMutOrderIncorrect, RemoveLet, RepeatedMutInPattern,
88
SwitchRefBoxOrder, TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg,
9-
TrailingVertNotAllowed, UnexpectedLifetimeInPattern, UnexpectedParenInRangePat,
10-
UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam,
11-
UnexpectedVertVertInPattern,
9+
TrailingVertNotAllowed, UnexpectedExpressionInPattern, UnexpectedLifetimeInPattern,
10+
UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
11+
UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern,
1212
};
1313
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
1414
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
1515
use rustc_ast::ptr::P;
16-
use rustc_ast::token::{self, Delimiter};
16+
use rustc_ast::token::{self, BinOpToken, Delimiter, Token};
1717
use rustc_ast::{
1818
self as ast, AttrVec, BindingAnnotation, ByRef, Expr, ExprKind, MacCall, Mutability, Pat,
1919
PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
@@ -23,7 +23,8 @@ use rustc_errors::{Applicability, DiagnosticBuilder, PResult};
2323
use rustc_session::errors::ExprParenthesesNeeded;
2424
use rustc_span::source_map::{respan, Spanned};
2525
use rustc_span::symbol::{kw, sym, Ident};
26-
use rustc_span::Span;
26+
use rustc_span::{ErrorGuaranteed, Span};
27+
use std::borrow::Cow;
2728
use thin_vec::{thin_vec, ThinVec};
2829

2930
#[derive(PartialEq, Copy, Clone)]
@@ -336,6 +337,88 @@ impl<'a> Parser<'a> {
336337
}
337338
}
338339

340+
/// Ensures that the last parsed pattern is not followed by a method call or an operator.
341+
#[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"]
342+
fn maybe_recover_trailing_expr<'b>(
343+
&'b mut self,
344+
pat: Cow<'b, P<Expr>>,
345+
is_end_bound: bool,
346+
) -> Option<ErrorGuaranteed> {
347+
if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() {
348+
// Don't recover anything after an `_` or if recovery is disabled.
349+
return None;
350+
}
351+
352+
// Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
353+
let has_trailing_method = self.check_noexpect(&token::Dot)
354+
&& self.look_ahead(1, |tok| {
355+
tok.ident()
356+
.and_then(|(ident, _)| ident.name.to_string().chars().next())
357+
.is_some_and(char::is_lowercase)
358+
})
359+
&& self.look_ahead(2, |tok| tok.kind == token::OpenDelim(Delimiter::Parenthesis));
360+
361+
// Check for operators.
362+
// `|` is excluded as it is used in pattern alternatives and lambdas,
363+
// `?` is included for error propagation,
364+
// `[` is included for indexing operations,
365+
// `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`)
366+
let has_trailing_operator = matches!(self.token.kind, token::BinOp(op) if op != BinOpToken::Or)
367+
|| self.token.kind == token::Question
368+
|| (self.token.kind == token::OpenDelim(Delimiter::Bracket)
369+
&& self.look_ahead(1, |tok| tok.kind != token::CloseDelim(Delimiter::Bracket)));
370+
371+
if !has_trailing_method && !has_trailing_operator {
372+
// Nothing to recover here.
373+
return None;
374+
}
375+
376+
// Let's try to parse an expression to emit a better diagnostic.
377+
let pat = pat.into_owned();
378+
let pat_span = pat.span;
379+
380+
let mut snapshot = self.create_snapshot_for_diagnostic();
381+
snapshot.restrictions.insert(Restrictions::IS_PAT);
382+
383+
// Parse `?`, `.f`, `(arg0, arg1, ...)` or `[expr]` until they've all been eaten.
384+
if let Ok(expr) = snapshot
385+
.parse_expr_dot_or_call_with(pat, pat_span, AttrVec::new())
386+
.map_err(|err| err.cancel())
387+
{
388+
let non_assoc_span = expr.span;
389+
390+
// Parse an associative expression such as `+ expr`, `% expr`, ...
391+
// Assignements, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
392+
if let Ok(expr) =
393+
snapshot.parse_expr_assoc_with(0, expr.into()).map_err(|err| err.cancel())
394+
{
395+
// We got a valid expression.
396+
self.restore_snapshot(snapshot);
397+
self.restrictions.remove(Restrictions::IS_PAT);
398+
399+
let span = expr.span;
400+
401+
let is_bound = is_end_bound
402+
// is_start_bound: either `..` or `)..`
403+
|| self.token.is_range_separator()
404+
|| self.token.kind == token::CloseDelim(Delimiter::Parenthesis)
405+
&& self.look_ahead(1, Token::is_range_separator);
406+
407+
// Check that `parse_expr_assoc_with` didn't eat a rhs.
408+
let is_method_call = has_trailing_method && non_assoc_span == span;
409+
410+
return Some(self.dcx().emit_err(UnexpectedExpressionInPattern {
411+
span,
412+
is_bound,
413+
is_method_call,
414+
}));
415+
}
416+
}
417+
418+
// We got a trailing method/operator, but we couldn't parse an expression.
419+
None
420+
}
421+
339422
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
340423
/// allowed).
341424
fn parse_pat_with_range_pat(
@@ -441,7 +524,11 @@ impl<'a> Parser<'a> {
441524
} else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
442525
self.parse_pat_tuple_struct(qself, path)?
443526
} else {
444-
PatKind::Path(qself, path)
527+
let pat = self.mk_expr(span, ExprKind::Path(qself.clone(), path.clone()));
528+
match self.maybe_recover_trailing_expr(Cow::Owned(pat), false) {
529+
Some(guar) => PatKind::Err(guar),
530+
None => PatKind::Path(qself, path),
531+
}
445532
}
446533
} else if matches!(self.token.kind, token::Lifetime(_))
447534
// In pattern position, we're totally fine with using "next token isn't colon"
@@ -470,10 +557,18 @@ impl<'a> Parser<'a> {
470557
} else {
471558
// Try to parse everything else as literal with optional minus
472559
match self.parse_literal_maybe_minus() {
473-
Ok(begin) => match self.parse_range_end() {
474-
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
475-
None => PatKind::Lit(begin),
476-
},
560+
Ok(begin) => {
561+
let begin = match self.maybe_recover_trailing_expr(Cow::Borrowed(&begin), false)
562+
{
563+
Some(_) => self.mk_expr_err(begin.span),
564+
None => begin,
565+
};
566+
567+
match self.parse_range_end() {
568+
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
569+
None => PatKind::Lit(begin),
570+
}
571+
}
477572
Err(err) => return self.fatal_unexpected_non_pat(err, expected),
478573
}
479574
};
@@ -615,6 +710,21 @@ impl<'a> Parser<'a> {
615710

616711
self.parse_pat_range_begin_with(begin.clone(), form)?
617712
}
713+
// recover ranges with parentheses around the `(start)..`
714+
PatKind::Err(_)
715+
if self.may_recover()
716+
&& let Some(form) = self.parse_range_end() =>
717+
{
718+
self.dcx().emit_err(UnexpectedParenInRangePat {
719+
span: vec![open_paren, close_paren],
720+
sugg: UnexpectedParenInRangePatSugg {
721+
start_span: open_paren,
722+
end_span: close_paren,
723+
},
724+
});
725+
726+
self.parse_pat_range_begin_with(self.mk_expr(pat.span, ExprKind::Err), form)?
727+
}
618728

619729
// (pat) with optional parentheses
620730
_ => PatKind::Paren(pat),
@@ -853,6 +963,8 @@ impl<'a> Parser<'a> {
853963
self.parse_literal_maybe_minus()
854964
}?;
855965

966+
let recovered = self.maybe_recover_trailing_expr(Cow::Borrowed(&bound), true);
967+
856968
// recover trailing `)`
857969
if let Some(open_paren) = open_paren {
858970
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
@@ -866,7 +978,10 @@ impl<'a> Parser<'a> {
866978
});
867979
}
868980

869-
Ok(bound)
981+
Ok(match recovered {
982+
Some(_) => self.mk_expr_err(bound.span),
983+
None => bound,
984+
})
870985
}
871986

872987
/// Is this the start of a pattern beginning with a path?
@@ -929,7 +1044,18 @@ impl<'a> Parser<'a> {
9291044
.create_err(EnumPatternInsteadOfIdentifier { span: self.prev_token.span }));
9301045
}
9311046

932-
Ok(PatKind::Ident(binding_annotation, ident, sub))
1047+
// Check for method calls after the `ident`,
1048+
// but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
1049+
1050+
let expr = self.mk_expr(ident.span, ExprKind::Path(None, Path::from_ident(ident)));
1051+
let pat = if sub.is_none()
1052+
&& let Some(guar) = self.maybe_recover_trailing_expr(Cow::Owned(expr), false)
1053+
{
1054+
PatKind::Err(guar)
1055+
} else {
1056+
PatKind::Ident(binding_annotation, ident, sub)
1057+
};
1058+
Ok(pat)
9331059
}
9341060

9351061
/// Parse a struct ("record") pattern (e.g. `Foo { ... }` or `Foo::Bar { ... }`).

tests/ui/half-open-range-patterns/range_pat_interactions1.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@ fn main() {
1717
}
1818
match x as i32 {
1919
0..5+1 => errors_only.push(x),
20-
//~^ error: expected one of `=>`, `if`, or `|`, found `+`
20+
//~^ error: expected a pattern range bound, found an expression
21+
//~| error: exclusive range pattern syntax is experimental
2122
1 | -3..0 => first_or.push(x),
23+
//~^ error: exclusive range pattern syntax is experimental
2224
y @ (0..5 | 6) => or_two.push(y),
25+
//~^ error: exclusive range pattern syntax is experimental
2326
y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
27+
//~^ error: exclusive range pattern syntax is experimental
28+
//~| error: inline-const in pattern position is experimental
2429
y @ -5.. => range_from.push(y),
2530
y @ ..-7 => assert_eq!(y, -8),
31+
//~^ error: exclusive range pattern syntax is experimental
2632
y => bottom.push(y),
2733
}
2834
}

tests/ui/half-open-range-patterns/range_pat_interactions1.stderr

+64-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected one of `=>`, `if`, or `|`, found `+`
2-
--> $DIR/range_pat_interactions1.rs:19:17
1+
error: expected a pattern range bound, found an expression
2+
--> $DIR/range_pat_interactions1.rs:19:16
33
|
44
LL | 0..5+1 => errors_only.push(x),
5-
| ^ expected one of `=>`, `if`, or `|`
5+
| ^^^ arbitrary expressions are not allowed in patterns
66

77
error[E0408]: variable `n` is not bound in all patterns
88
--> $DIR/range_pat_interactions1.rs:10:25
@@ -12,6 +12,16 @@ LL | if let n @ 2..3|4 = x {
1212
| |
1313
| variable not in all patterns
1414

15+
error[E0658]: inline-const in pattern position is experimental
16+
--> $DIR/range_pat_interactions1.rs:26:20
17+
|
18+
LL | y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
19+
| ^^^^^
20+
|
21+
= note: see issue #76001 <https://github.com/rust-lang/rust/issues/76001> for more information
22+
= help: add `#![feature(inline_const_pat)]` to the crate attributes to enable
23+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
24+
1525
error[E0658]: exclusive range pattern syntax is experimental
1626
--> $DIR/range_pat_interactions1.rs:10:20
1727
|
@@ -32,7 +42,57 @@ LL | } else if let 2..3 | 4 = x {
3242
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
3343
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
3444

35-
error: aborting due to 4 previous errors
45+
error[E0658]: exclusive range pattern syntax is experimental
46+
--> $DIR/range_pat_interactions1.rs:19:13
47+
|
48+
LL | 0..5+1 => errors_only.push(x),
49+
| ^^^^^^
50+
|
51+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
52+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
53+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
54+
55+
error[E0658]: exclusive range pattern syntax is experimental
56+
--> $DIR/range_pat_interactions1.rs:22:17
57+
|
58+
LL | 1 | -3..0 => first_or.push(x),
59+
| ^^^^^
60+
|
61+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
62+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
63+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
64+
65+
error[E0658]: exclusive range pattern syntax is experimental
66+
--> $DIR/range_pat_interactions1.rs:24:18
67+
|
68+
LL | y @ (0..5 | 6) => or_two.push(y),
69+
| ^^^^
70+
|
71+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
72+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
73+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
74+
75+
error[E0658]: exclusive range pattern syntax is experimental
76+
--> $DIR/range_pat_interactions1.rs:26:17
77+
|
78+
LL | y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
79+
| ^^^^^^^^^^^^^^^^^^
80+
|
81+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
82+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
83+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
84+
85+
error[E0658]: exclusive range pattern syntax is experimental
86+
--> $DIR/range_pat_interactions1.rs:30:17
87+
|
88+
LL | y @ ..-7 => assert_eq!(y, -8),
89+
| ^^^^
90+
|
91+
= note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information
92+
= help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable
93+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
94+
95+
error: aborting due to 10 previous errors
3696

3797
Some errors have detailed explanations: E0408, E0658.
3898
For more information about an error, try `rustc --explain E0408`.

0 commit comments

Comments
 (0)