Skip to content

Commit 6b71d23

Browse files
committed
Recover parentheses in range patterns
1 parent 3ee6710 commit 6b71d23

File tree

7 files changed

+180
-27
lines changed

7 files changed

+180
-27
lines changed

compiler/rustc_parse/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,9 @@ parse_unexpected_if_with_if = unexpected `if` in the condition expression
767767
parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in pattern
768768
.suggestion = remove the lifetime
769769
770+
parse_unexpected_paren_in_range_pat = range pattern bounds cannot have parentheses
771+
parse_unexpected_paren_in_range_pat_sugg = remove these parentheses
772+
770773
parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head
771774
.suggestion = remove parentheses in `for` loop
772775

compiler/rustc_parse/src/errors.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::borrow::Cow;
33
use rustc_ast::token::Token;
44
use rustc_ast::{Path, Visibility};
55
use rustc_errors::{
6-
AddToDiagnostic, Applicability, DiagCtxt, DiagnosticBuilder, IntoDiagnostic, Level,
6+
AddToDiagnostic, Applicability, DiagCtxt, DiagnosticBuilder, IntoDiagnostic, Level, MultiSpan,
77
SubdiagnosticMessage,
88
};
99
use rustc_macros::{Diagnostic, Subdiagnostic};
@@ -2378,6 +2378,27 @@ pub(crate) struct ExpectedCommaAfterPatternField {
23782378
pub span: Span,
23792379
}
23802380

2381+
#[derive(Diagnostic)]
2382+
#[diag(parse_unexpected_paren_in_range_pat)]
2383+
pub(crate) struct UnexpectedParenInRangePat {
2384+
#[primary_span]
2385+
pub span: MultiSpan,
2386+
#[subdiagnostic]
2387+
pub sugg: UnexpectedParenInRangePatSugg,
2388+
}
2389+
2390+
#[derive(Subdiagnostic)]
2391+
#[multipart_suggestion(
2392+
parse_unexpected_paren_in_range_pat_sugg,
2393+
applicability = "machine-applicable"
2394+
)]
2395+
pub(crate) struct UnexpectedParenInRangePatSugg {
2396+
#[suggestion_part(code = "")]
2397+
pub start_span: Span,
2398+
#[suggestion_part(code = "")]
2399+
pub end_span: Span,
2400+
}
2401+
23812402
#[derive(Diagnostic)]
23822403
#[diag(parse_return_types_use_thin_arrow)]
23832404
pub(crate) struct ReturnTypesUseThinArrow {

compiler/rustc_parse/src/parser/pat.rs

+81-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use super::{ForceCollect, Parser, PathStyle, TrailingToken};
22
use crate::errors::{
3-
self, AmbiguousRangePattern, DotDotDotForRemainingFields, DotDotDotRangeToPatternNotAllowed,
4-
DotDotDotRestPattern, EnumPatternInsteadOfIdentifier, ExpectedBindingLeftOfAt,
5-
ExpectedCommaAfterPatternField, GenericArgsInPatRequireTurbofishSyntax,
6-
InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern,
7-
PatternOnWrongSideOfAt, RefMutOrderIncorrect, RemoveLet, RepeatedMutInPattern,
8-
SwitchRefBoxOrder, TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg,
9-
TrailingVertNotAllowed, UnexpectedLifetimeInPattern, UnexpectedVertVertBeforeFunctionParam,
10-
UnexpectedVertVertInPattern,
3+
AmbiguousRangePattern, BoxNotPat, DotDotDotForRemainingFields,
4+
DotDotDotRangeToPatternNotAllowed, DotDotDotRestPattern, EnumPatternInsteadOfIdentifier,
5+
ExpectedBindingLeftOfAt, ExpectedCommaAfterPatternField,
6+
GenericArgsInPatRequireTurbofishSyntax, InclusiveRangeExtraEquals, InclusiveRangeMatchArrow,
7+
InclusiveRangeNoEnd, InvalidMutInPattern, PatternOnWrongSideOfAt, RefMutOrderIncorrect,
8+
RemoveLet, RepeatedMutInPattern, SwitchRefBoxOrder, TopLevelOrPatternNotAllowed,
9+
TopLevelOrPatternNotAllowedSugg, TrailingVertNotAllowed, UnexpectedLifetimeInPattern,
10+
UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
11+
UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern,
1112
};
1213
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
1314
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
@@ -18,7 +19,7 @@ use rustc_ast::{
1819
PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
1920
};
2021
use rustc_ast_pretty::pprust;
21-
use rustc_errors::{Applicability, DiagnosticBuilder, PResult};
22+
use rustc_errors::{Applicability, DiagnosticBuilder, MultiSpan, PResult};
2223
use rustc_session::errors::ExprParenthesesNeeded;
2324
use rustc_span::source_map::{respan, Spanned};
2425
use rustc_span::symbol::{kw, sym, Ident};
@@ -579,6 +580,8 @@ impl<'a> Parser<'a> {
579580

580581
/// Parse a tuple or parenthesis pattern.
581582
fn parse_pat_tuple_or_parens(&mut self) -> PResult<'a, PatKind> {
583+
let open_paren = self.token.span;
584+
582585
let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| {
583586
p.parse_pat_allow_top_alt(
584587
None,
@@ -591,7 +594,28 @@ impl<'a> Parser<'a> {
591594
// Here, `(pat,)` is a tuple pattern.
592595
// For backward compatibility, `(..)` is a tuple pattern as well.
593596
Ok(if fields.len() == 1 && !(trailing_comma || fields[0].is_rest()) {
594-
PatKind::Paren(fields.into_iter().next().unwrap())
597+
let pat = fields.into_iter().next().unwrap();
598+
let close_paren = self.prev_token.span;
599+
600+
match &pat.kind {
601+
// recover ranges with parentheses around the `(start)..`
602+
PatKind::Lit(begin)
603+
if let Some(form) = self.may_recover().then(|| self.parse_range_end()) =>
604+
{
605+
self.dcx().emit_err(UnexpectedParenInRangePat {
606+
span: MultiSpan::from_spans(vec![open_paren, close_paren]),
607+
sugg: UnexpectedParenInRangePatSugg {
608+
start_span: open_paren,
609+
end_span: close_paren,
610+
},
611+
});
612+
613+
self.parse_pat_range_begin_with(begin.clone(), form)?
614+
}
615+
616+
// (pat) with optional parentheses
617+
_ => PatKind::Paren(pat),
618+
}
595619
} else {
596620
PatKind::Tuple(fields)
597621
})
@@ -727,6 +751,14 @@ impl<'a> Parser<'a> {
727751
begin: P<Expr>,
728752
re: Spanned<RangeEnd>,
729753
) -> PResult<'a, PatKind> {
754+
// recover from `(`
755+
let open_paren = (self.may_recover()
756+
&& self.token.kind == token::OpenDelim(Delimiter::Parenthesis))
757+
.then(|| {
758+
self.bump();
759+
self.prev_token.span
760+
});
761+
730762
let end = if self.is_pat_range_end_start(0) {
731763
// Parsing e.g. `X..=Y`.
732764
Some(self.parse_pat_range_end()?)
@@ -738,6 +770,19 @@ impl<'a> Parser<'a> {
738770
}
739771
None
740772
};
773+
774+
if let Some(span) = open_paren {
775+
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
776+
777+
self.dcx().emit_err(UnexpectedParenInRangePat {
778+
span: MultiSpan::from_spans(vec![span, self.prev_token.span]),
779+
sugg: UnexpectedParenInRangePatSugg {
780+
start_span: span,
781+
end_span: self.prev_token.span,
782+
},
783+
});
784+
}
785+
741786
Ok(PatKind::Range(Some(begin), end, re))
742787
}
743788

@@ -777,11 +822,32 @@ impl<'a> Parser<'a> {
777822
/// The form `...X` is prohibited to reduce confusion with the potential
778823
/// expression syntax `...expr` for splatting in expressions.
779824
fn parse_pat_range_to(&mut self, mut re: Spanned<RangeEnd>) -> PResult<'a, PatKind> {
825+
// recover from `(`
826+
let open_paren = (self.may_recover()
827+
&& self.token.kind == token::OpenDelim(Delimiter::Parenthesis))
828+
.then(|| {
829+
self.bump();
830+
self.prev_token.span
831+
});
832+
780833
let end = self.parse_pat_range_end()?;
781834
if let RangeEnd::Included(syn @ RangeSyntax::DotDotDot) = &mut re.node {
782835
*syn = RangeSyntax::DotDotEq;
783836
self.dcx().emit_err(DotDotDotRangeToPatternNotAllowed { span: re.span });
784837
}
838+
839+
if let Some(span) = open_paren {
840+
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
841+
842+
self.dcx().emit_err(UnexpectedParenInRangePat {
843+
span: MultiSpan::from_spans(vec![span, self.prev_token.span]),
844+
sugg: UnexpectedParenInRangePatSugg {
845+
start_span: span,
846+
end_span: self.prev_token.span,
847+
},
848+
});
849+
}
850+
785851
Ok(PatKind::Range(None, Some(end), re))
786852
}
787853

@@ -794,6 +860,10 @@ impl<'a> Parser<'a> {
794860
|| t.can_begin_literal_maybe_minus() // e.g. `42`.
795861
|| t.is_whole_expr()
796862
|| t.is_lifetime() // recover `'a` instead of `'a'`
863+
|| (self.may_recover() // recover leading `(`
864+
&& t.kind == token::OpenDelim(Delimiter::Parenthesis)
865+
&& self.look_ahead(dist + 1, |t| t.kind != token::OpenDelim(Delimiter::Parenthesis))
866+
&& self.is_pat_range_end_start(dist + 1))
797867
})
798868
}
799869

@@ -942,7 +1012,7 @@ impl<'a> Parser<'a> {
9421012

9431013
if self.isnt_pattern_start() {
9441014
let descr = super::token_descr(&self.token);
945-
self.dcx().emit_err(errors::BoxNotPat {
1015+
self.dcx().emit_err(BoxNotPat {
9461016
span: self.token.span,
9471017
kw: box_span,
9481018
lo: box_span.shrink_to_lo(),

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ fn main() {
88
for x in -9 + 1..=(9 - 2) {
99
match x as i32 {
1010
0..=(5+1) => errors_only.push(x),
11-
//~^ error: inclusive range with no end
12-
//~| error: expected one of `=>`, `if`, or `|`, found `(`
11+
//~^ error: expected `)`, found `+`
1312
1 | -3..0 => first_or.push(x),
1413
y @ (0..5 | 6) => or_two.push(y),
1514
y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
error[E0586]: inclusive range with no end
2-
--> $DIR/range_pat_interactions2.rs:10:14
1+
error: expected `)`, found `+`
2+
--> $DIR/range_pat_interactions2.rs:10:19
33
|
44
LL | 0..=(5+1) => errors_only.push(x),
5-
| ^^^ help: use `..` instead
6-
|
7-
= note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`)
8-
9-
error: expected one of `=>`, `if`, or `|`, found `(`
10-
--> $DIR/range_pat_interactions2.rs:10:17
11-
|
12-
LL | 0..=(5+1) => errors_only.push(x),
13-
| ^ expected one of `=>`, `if`, or `|`
5+
| ^ expected `)`
146

15-
error: aborting due to 2 previous errors
7+
error: aborting due to 1 previous error
168

17-
For more information about this error, try `rustc --explain E0586`.

tests/ui/parser/pat-recover-ranges.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
fn main() {
2+
match -1 {
3+
0..=1 => (),
4+
0..=(1) => (),
5+
//~^ error: range pattern bounds cannot have parentheses
6+
(-12)..=4 => (),
7+
//~^ error: range pattern bounds cannot have parentheses
8+
(0)..=(-4) => (),
9+
//~^ error: range pattern bounds cannot have parentheses
10+
//~| error: range pattern bounds cannot have parentheses
11+
};
12+
}
13+
14+
macro_rules! m {
15+
($pat:pat) => {};
16+
(($s:literal)..($e:literal)) => {};
17+
}
18+
19+
m!((7)..(7));
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
error: range pattern bounds cannot have parentheses
2+
--> $DIR/pat-recover-ranges.rs:4:13
3+
|
4+
LL | 0..=(1) => (),
5+
| ^ ^
6+
|
7+
help: remove these parentheses
8+
|
9+
LL - 0..=(1) => (),
10+
LL + 0..=1 => (),
11+
|
12+
13+
error: range pattern bounds cannot have parentheses
14+
--> $DIR/pat-recover-ranges.rs:6:9
15+
|
16+
LL | (-12)..=4 => (),
17+
| ^ ^
18+
|
19+
help: remove these parentheses
20+
|
21+
LL - (-12)..=4 => (),
22+
LL + -12..=4 => (),
23+
|
24+
25+
error: range pattern bounds cannot have parentheses
26+
--> $DIR/pat-recover-ranges.rs:8:9
27+
|
28+
LL | (0)..=(-4) => (),
29+
| ^ ^
30+
|
31+
help: remove these parentheses
32+
|
33+
LL - (0)..=(-4) => (),
34+
LL + 0..=(-4) => (),
35+
|
36+
37+
error: range pattern bounds cannot have parentheses
38+
--> $DIR/pat-recover-ranges.rs:8:15
39+
|
40+
LL | (0)..=(-4) => (),
41+
| ^ ^
42+
|
43+
help: remove these parentheses
44+
|
45+
LL - (0)..=(-4) => (),
46+
LL + (0)..=-4 => (),
47+
|
48+
49+
error: aborting due to 4 previous errors
50+

0 commit comments

Comments
 (0)