Skip to content

Commit f908aa9

Browse files
committed
recover on 'mut ' and improve recovery for keywords.
1 parent e49b958 commit f908aa9

File tree

71 files changed

+449
-147
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+449
-147
lines changed

src/libsyntax/parse/parser/pat.rs

Lines changed: 105 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
44
use crate::ptr::P;
55
use crate::ast::{self, Attribute, Pat, PatKind, FieldPat, RangeEnd, RangeSyntax, Mac};
66
use crate::ast::{BindingMode, Ident, Mutability, Path, QSelf, Expr, ExprKind};
7+
use crate::mut_visit::{noop_visit_pat, MutVisitor};
78
use crate::parse::token::{self};
89
use crate::print::pprust;
910
use crate::source_map::{respan, Span, Spanned};
@@ -273,21 +274,20 @@ impl<'a> Parser<'a> {
273274
// Parse _
274275
PatKind::Wild
275276
} else if self.eat_keyword(kw::Mut) {
276-
self.recover_pat_ident_mut_first()?
277+
self.parse_pat_ident_mut()?
277278
} else if self.eat_keyword(kw::Ref) {
278279
// Parse ref ident @ pat / ref mut ident @ pat
279280
let mutbl = self.parse_mutability();
280281
self.parse_pat_ident(BindingMode::ByRef(mutbl))?
281282
} else if self.eat_keyword(kw::Box) {
282283
// Parse `box pat`
283284
PatKind::Box(self.parse_pat_with_range_pat(false, None)?)
284-
} else if self.token.is_ident() && !self.token.is_reserved_ident() &&
285-
self.parse_as_ident() {
285+
} else if self.can_be_ident_pat() {
286286
// Parse `ident @ pat`
287287
// This can give false positives and parse nullary enums,
288288
// they are dealt with later in resolve.
289289
self.parse_pat_ident(BindingMode::ByValue(Mutability::Immutable))?
290-
} else if self.token.is_path_start() {
290+
} else if self.is_start_of_pat_with_path() {
291291
// Parse pattern starting with a path
292292
let (qself, path) = if self.eat_lt() {
293293
// Parse a qualified path
@@ -384,24 +384,85 @@ impl<'a> Parser<'a> {
384384
})
385385
}
386386

387+
fn parse_pat_ident_mut(&mut self) -> PResult<'a, PatKind> {
388+
let mut_span = self.prev_span;
389+
390+
if self.eat_keyword(kw::Ref) {
391+
return self.recover_mut_ref_ident(mut_span)
392+
}
393+
394+
self.recover_additional_muts();
395+
396+
let mut pat = self.parse_pat(Some("identifier"))?;
397+
398+
// Add `mut` to any binding in the parsed pattern.
399+
struct AddMut;
400+
impl MutVisitor for AddMut {
401+
fn visit_pat(&mut self, pat: &mut P<Pat>) {
402+
if let PatKind::Ident(BindingMode::ByValue(ref mut m), ..) = pat.node {
403+
*m = Mutability::Mutable;
404+
}
405+
noop_visit_pat(pat, self);
406+
}
407+
}
408+
AddMut.visit_pat(&mut pat);
409+
410+
// Unwrap; If we don't have `mut $ident`, error.
411+
let pat = pat.into_inner();
412+
match &pat.node {
413+
PatKind::Ident(..) => {}
414+
_ => self.ban_mut_general_pat(mut_span, &pat),
415+
}
416+
417+
Ok(pat.node)
418+
}
419+
387420
/// Recover on `mut ref? ident @ pat` and suggest
388421
/// that the order of `mut` and `ref` is incorrect.
389-
fn recover_pat_ident_mut_first(&mut self) -> PResult<'a, PatKind> {
390-
let mutref_span = self.prev_span.to(self.token.span);
391-
let binding_mode = if self.eat_keyword(kw::Ref) {
392-
self.struct_span_err(mutref_span, "the order of `mut` and `ref` is incorrect")
393-
.span_suggestion(
394-
mutref_span,
395-
"try switching the order",
396-
"ref mut".into(),
397-
Applicability::MachineApplicable
398-
)
399-
.emit();
400-
BindingMode::ByRef(Mutability::Mutable)
401-
} else {
402-
BindingMode::ByValue(Mutability::Mutable)
403-
};
404-
self.parse_pat_ident(binding_mode)
422+
fn recover_mut_ref_ident(&mut self, lo: Span) -> PResult<'a, PatKind> {
423+
let mutref_span = lo.to(self.prev_span);
424+
self.struct_span_err(mutref_span, "the order of `mut` and `ref` is incorrect")
425+
.span_suggestion(
426+
mutref_span,
427+
"try switching the order",
428+
"ref mut".into(),
429+
Applicability::MachineApplicable
430+
)
431+
.emit();
432+
433+
self.parse_pat_ident(BindingMode::ByRef(Mutability::Mutable))
434+
}
435+
436+
/// Error on `mut $pat` where `$pat` is not an ident.
437+
fn ban_mut_general_pat(&self, lo: Span, pat: &Pat) {
438+
let span = lo.to(pat.span);
439+
self.struct_span_err(span, "`mut` must be attached to each individual binding")
440+
.span_suggestion(
441+
span,
442+
"add `mut` to each binding",
443+
pprust::pat_to_string(&pat),
444+
Applicability::MachineApplicable,
445+
)
446+
.emit();
447+
}
448+
449+
/// Eat any extraneous `mut`s and error + recover if we ate any.
450+
fn recover_additional_muts(&mut self) {
451+
let lo = self.token.span;
452+
while self.eat_keyword(kw::Mut) {}
453+
if lo == self.token.span {
454+
return;
455+
}
456+
457+
let span = lo.to(self.prev_span);
458+
self.struct_span_err(span, "`mut` on a binding may not be repeated")
459+
.span_suggestion(
460+
span,
461+
"remove the additional `mut`s",
462+
String::new(),
463+
Applicability::MachineApplicable,
464+
)
465+
.emit();
405466
}
406467

407468
/// Parse macro invocation
@@ -479,17 +540,6 @@ impl<'a> Parser<'a> {
479540
Err(err)
480541
}
481542

482-
// Helper function to decide whether to parse as ident binding
483-
// or to try to do something more complex like range patterns.
484-
fn parse_as_ident(&mut self) -> bool {
485-
self.look_ahead(1, |t| match t.kind {
486-
token::OpenDelim(token::Paren) | token::OpenDelim(token::Brace) |
487-
token::DotDotDot | token::DotDotEq | token::DotDot |
488-
token::ModSep | token::Not => false,
489-
_ => true,
490-
})
491-
}
492-
493543
/// Is the current token suitable as the start of a range patterns end?
494544
fn is_pat_range_end_start(&self) -> bool {
495545
self.token.is_path_start() // e.g. `MY_CONST`;
@@ -563,6 +613,30 @@ impl<'a> Parser<'a> {
563613
}
564614
}
565615

616+
/// Is this the start of a pattern beginning with a path?
617+
fn is_start_of_pat_with_path(&mut self) -> bool {
618+
self.check_path()
619+
// Just for recovery (see `can_be_ident`).
620+
|| self.token.is_ident() && !self.token.is_bool_lit() && !self.token.is_keyword(kw::In)
621+
}
622+
623+
/// Would `parse_pat_ident` be appropriate here?
624+
fn can_be_ident_pat(&mut self) -> bool {
625+
self.check_ident()
626+
&& !self.token.is_bool_lit() // Avoid `true` or `false` as a binding as it is a literal.
627+
&& !self.token.is_path_segment_keyword() // Avoid e.g. `Self` as it is a path.
628+
// Avoid `in`. Due to recovery in the list parser this messes with `for ( $pat in $expr )`.
629+
&& !self.token.is_keyword(kw::In)
630+
&& self.look_ahead(1, |t| match t.kind { // Try to do something more complex?
631+
token::OpenDelim(token::Paren) // A tuple struct pattern.
632+
| token::OpenDelim(token::Brace) // A struct pattern.
633+
| token::DotDotDot | token::DotDotEq | token::DotDot // A range pattern.
634+
| token::ModSep // A tuple / struct variant pattern.
635+
| token::Not => false, // A macro expanding to a pattern.
636+
_ => true,
637+
})
638+
}
639+
566640
/// Parses `ident` or `ident @ pat`.
567641
/// Used by the copy foo and ref foo patterns to give a good
568642
/// error message when parsing mistakes like `ref foo(a, b)`.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
fn main() {
2-
let extern = 0; //~ ERROR expected pattern, found keyword `extern`
2+
let extern = 0; //~ ERROR expected identifier, found keyword `extern`
33
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found keyword `extern`
1+
error: expected identifier, found keyword `extern`
22
--> $DIR/keyword-extern-as-identifier-pat.rs:2:9
33
|
44
LL | let extern = 0;
5-
| ^^^^^^ expected pattern
5+
| ^^^^^^ expected identifier, found keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#extern = 0;
9+
| ^^^^^^^^
610

711
error: aborting due to previous error
812

src/test/ui/parser/issue-32501.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ fn main() {
44
let _ = 0;
55
let mut b = 0;
66
let mut _b = 0;
7-
let mut _ = 0; //~ ERROR expected identifier, found reserved identifier `_`
7+
let mut _ = 0;
8+
//~^ ERROR `mut` must be attached to each individual binding
89
}

src/test/ui/parser/issue-32501.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected identifier, found reserved identifier `_`
2-
--> $DIR/issue-32501.rs:7:13
1+
error: `mut` must be attached to each individual binding
2+
--> $DIR/issue-32501.rs:7:9
33
|
44
LL | let mut _ = 0;
5-
| ^ expected identifier, found reserved identifier
5+
| ^^^^^ help: add `mut` to each binding: `_`
66

77
error: aborting due to previous error
88

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
fn main() {
2-
let abstract = (); //~ ERROR expected pattern, found reserved keyword `abstract`
2+
let abstract = (); //~ ERROR expected identifier, found reserved keyword `abstract`
33
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found reserved keyword `abstract`
1+
error: expected identifier, found reserved keyword `abstract`
22
--> $DIR/keyword-abstract.rs:2:9
33
|
44
LL | let abstract = ();
5-
| ^^^^^^^^ expected pattern
5+
| ^^^^^^^^ expected identifier, found reserved keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#abstract = ();
9+
| ^^^^^^^^^^
610

711
error: aborting due to previous error
812

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file was auto-generated using 'src/etc/generate-keyword-tests.py as'
22

33
fn main() {
4-
let as = "foo"; //~ error: expected pattern, found keyword `as`
4+
let as = "foo"; //~ error: expected identifier, found keyword `as`
55
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found keyword `as`
1+
error: expected identifier, found keyword `as`
22
--> $DIR/keyword-as-as-identifier.rs:4:9
33
|
44
LL | let as = "foo";
5-
| ^^ expected pattern
5+
| ^^ expected identifier, found keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#as = "foo";
9+
| ^^^^
610

711
error: aborting due to previous error
812

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file was auto-generated using 'src/etc/generate-keyword-tests.py break'
22

33
fn main() {
4-
let break = "foo"; //~ error: expected pattern, found keyword `break`
4+
let break = "foo"; //~ error: expected identifier, found keyword `break`
55
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found keyword `break`
1+
error: expected identifier, found keyword `break`
22
--> $DIR/keyword-break-as-identifier.rs:4:9
33
|
44
LL | let break = "foo";
5-
| ^^^^^ expected pattern
5+
| ^^^^^ expected identifier, found keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#break = "foo";
9+
| ^^^^^^^
610

711
error: aborting due to previous error
812

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file was auto-generated using 'src/etc/generate-keyword-tests.py const'
22

33
fn main() {
4-
let const = "foo"; //~ error: expected pattern, found keyword `const`
4+
let const = "foo"; //~ error: expected identifier, found keyword `const`
55
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found keyword `const`
1+
error: expected identifier, found keyword `const`
22
--> $DIR/keyword-const-as-identifier.rs:4:9
33
|
44
LL | let const = "foo";
5-
| ^^^^^ expected pattern
5+
| ^^^^^ expected identifier, found keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#const = "foo";
9+
| ^^^^^^^
610

711
error: aborting due to previous error
812

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file was auto-generated using 'src/etc/generate-keyword-tests.py continue'
22

33
fn main() {
4-
let continue = "foo"; //~ error: expected pattern, found keyword `continue`
4+
let continue = "foo"; //~ error: expected identifier, found keyword `continue`
55
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found keyword `continue`
1+
error: expected identifier, found keyword `continue`
22
--> $DIR/keyword-continue-as-identifier.rs:4:9
33
|
44
LL | let continue = "foo";
5-
| ^^^^^^^^ expected pattern
5+
| ^^^^^^^^ expected identifier, found keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#continue = "foo";
9+
| ^^^^^^^^^^
610

711
error: aborting due to previous error
812

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file was auto-generated using 'src/etc/generate-keyword-tests.py else'
22

33
fn main() {
4-
let else = "foo"; //~ error: expected pattern, found keyword `else`
4+
let else = "foo"; //~ error: expected identifier, found keyword `else`
55
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found keyword `else`
1+
error: expected identifier, found keyword `else`
22
--> $DIR/keyword-else-as-identifier.rs:4:9
33
|
44
LL | let else = "foo";
5-
| ^^^^ expected pattern
5+
| ^^^^ expected identifier, found keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#else = "foo";
9+
| ^^^^^^
610

711
error: aborting due to previous error
812

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file was auto-generated using 'src/etc/generate-keyword-tests.py enum'
22

33
fn main() {
4-
let enum = "foo"; //~ error: expected pattern, found keyword `enum`
4+
let enum = "foo"; //~ error: expected identifier, found keyword `enum`
55
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found keyword `enum`
1+
error: expected identifier, found keyword `enum`
22
--> $DIR/keyword-enum-as-identifier.rs:4:9
33
|
44
LL | let enum = "foo";
5-
| ^^^^ expected pattern
5+
| ^^^^ expected identifier, found keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#enum = "foo";
9+
| ^^^^^^
610

711
error: aborting due to previous error
812

src/test/ui/parser/keyword-final.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
fn main() {
2-
let final = (); //~ ERROR expected pattern, found reserved keyword `final`
2+
let final = (); //~ ERROR expected identifier, found reserved keyword `final`
33
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: expected pattern, found reserved keyword `final`
1+
error: expected identifier, found reserved keyword `final`
22
--> $DIR/keyword-final.rs:2:9
33
|
44
LL | let final = ();
5-
| ^^^^^ expected pattern
5+
| ^^^^^ expected identifier, found reserved keyword
6+
help: you can escape reserved keywords to use them as identifiers
7+
|
8+
LL | let r#final = ();
9+
| ^^^^^^^
610

711
error: aborting due to previous error
812

0 commit comments

Comments
 (0)