Skip to content

Commit 52c3846

Browse files
authored
Rollup merge of #63945 - Centril:recover-mut-pat, r=estebank
Recover `mut $pat` and other improvements - Recover on e.g. `mut Foo(x, y)` and suggest `Foo(mut x, mut y)`. Fixes #63764. - Recover on e.g. `let mut mut x;` - Recover on e.g. `let keyword` and `let keyword(...)`. - Cleanups in `token.rs` with `fn is_non_raw_ident_where` and friends.
2 parents eb4ac32 + 42e895d commit 52c3846

File tree

75 files changed

+546
-171
lines changed

Some content is hidden

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

75 files changed

+546
-171
lines changed

src/libsyntax/parse/literal.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl LitKind {
104104

105105
Ok(match kind {
106106
token::Bool => {
107-
assert!(symbol == kw::True || symbol == kw::False);
107+
assert!(symbol.is_bool_lit());
108108
LitKind::Bool(symbol == kw::True)
109109
}
110110
token::Byte => return unescape_byte(&symbol.as_str())
@@ -261,7 +261,7 @@ impl Lit {
261261
/// Converts arbitrary token into an AST literal.
262262
crate fn from_token(token: &Token) -> Result<Lit, LitError> {
263263
let lit = match token.kind {
264-
token::Ident(name, false) if name == kw::True || name == kw::False =>
264+
token::Ident(name, false) if name.is_bool_lit() =>
265265
token::Lit::new(token::Bool, name, None),
266266
token::Literal(lit) =>
267267
lit,

src/libsyntax/parse/parser/pat.rs

+126-29
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,108 @@ impl<'a> Parser<'a> {
384384
})
385385
}
386386

387+
/// Parse a mutable binding with the `mut` token already eaten.
388+
fn parse_pat_ident_mut(&mut self) -> PResult<'a, PatKind> {
389+
let mut_span = self.prev_span;
390+
391+
if self.eat_keyword(kw::Ref) {
392+
return self.recover_mut_ref_ident(mut_span)
393+
}
394+
395+
self.recover_additional_muts();
396+
397+
// Make sure we don't allow e.g. `let mut $p;` where `$p:pat`.
398+
if let token::Interpolated(ref nt) = self.token.kind {
399+
if let token::NtPat(_) = **nt {
400+
self.expected_ident_found().emit();
401+
}
402+
}
403+
404+
// Parse the pattern we hope to be an identifier.
405+
let mut pat = self.parse_pat(Some("identifier"))?;
406+
407+
// Add `mut` to any binding in the parsed pattern.
408+
let changed_any_binding = Self::make_all_value_bindings_mutable(&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, changed_any_binding),
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)
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+
/// Turn all by-value immutable bindings in a pattern into mutable bindings.
437+
/// Returns `true` if any change was made.
438+
fn make_all_value_bindings_mutable(pat: &mut P<Pat>) -> bool {
439+
struct AddMut(bool);
440+
impl MutVisitor for AddMut {
441+
fn visit_pat(&mut self, pat: &mut P<Pat>) {
442+
if let PatKind::Ident(BindingMode::ByValue(ref mut m @ Mutability::Immutable), ..)
443+
= pat.node
444+
{
445+
*m = Mutability::Mutable;
446+
self.0 = true;
447+
}
448+
noop_visit_pat(pat, self);
449+
}
450+
}
451+
452+
let mut add_mut = AddMut(false);
453+
add_mut.visit_pat(pat);
454+
add_mut.0
455+
}
456+
457+
/// Error on `mut $pat` where `$pat` is not an ident.
458+
fn ban_mut_general_pat(&self, lo: Span, pat: &Pat, changed_any_binding: bool) {
459+
let span = lo.to(pat.span);
460+
let fix = pprust::pat_to_string(&pat);
461+
let (problem, suggestion) = if changed_any_binding {
462+
("`mut` must be attached to each individual binding", "add `mut` to each binding")
401463
} else {
402-
BindingMode::ByValue(Mutability::Mutable)
464+
("`mut` must be followed by a named binding", "remove the `mut` prefix")
403465
};
404-
self.parse_pat_ident(binding_mode)
466+
self.struct_span_err(span, problem)
467+
.span_suggestion(span, suggestion, fix, Applicability::MachineApplicable)
468+
.note("`mut` may be followed by `variable` and `variable @ pattern`")
469+
.emit()
470+
}
471+
472+
/// Eat any extraneous `mut`s and error + recover if we ate any.
473+
fn recover_additional_muts(&mut self) {
474+
let lo = self.token.span;
475+
while self.eat_keyword(kw::Mut) {}
476+
if lo == self.token.span {
477+
return;
478+
}
479+
480+
let span = lo.to(self.prev_span);
481+
self.struct_span_err(span, "`mut` on a binding may not be repeated")
482+
.span_suggestion(
483+
span,
484+
"remove the additional `mut`s",
485+
String::new(),
486+
Applicability::MachineApplicable,
487+
)
488+
.emit();
405489
}
406490

407491
/// Parse macro invocation
@@ -479,17 +563,6 @@ impl<'a> Parser<'a> {
479563
Err(err)
480564
}
481565

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-
493566
/// Is the current token suitable as the start of a range patterns end?
494567
fn is_pat_range_end_start(&self) -> bool {
495568
self.token.is_path_start() // e.g. `MY_CONST`;
@@ -563,6 +636,30 @@ impl<'a> Parser<'a> {
563636
}
564637
}
565638

639+
/// Is this the start of a pattern beginning with a path?
640+
fn is_start_of_pat_with_path(&mut self) -> bool {
641+
self.check_path()
642+
// Just for recovery (see `can_be_ident`).
643+
|| self.token.is_ident() && !self.token.is_bool_lit() && !self.token.is_keyword(kw::In)
644+
}
645+
646+
/// Would `parse_pat_ident` be appropriate here?
647+
fn can_be_ident_pat(&mut self) -> bool {
648+
self.check_ident()
649+
&& !self.token.is_bool_lit() // Avoid `true` or `false` as a binding as it is a literal.
650+
&& !self.token.is_path_segment_keyword() // Avoid e.g. `Self` as it is a path.
651+
// Avoid `in`. Due to recovery in the list parser this messes with `for ( $pat in $expr )`.
652+
&& !self.token.is_keyword(kw::In)
653+
&& self.look_ahead(1, |t| match t.kind { // Try to do something more complex?
654+
token::OpenDelim(token::Paren) // A tuple struct pattern.
655+
| token::OpenDelim(token::Brace) // A struct pattern.
656+
| token::DotDotDot | token::DotDotEq | token::DotDot // A range pattern.
657+
| token::ModSep // A tuple / struct variant pattern.
658+
| token::Not => false, // A macro expanding to a pattern.
659+
_ => true,
660+
})
661+
}
662+
566663
/// Parses `ident` or `ident @ pat`.
567664
/// Used by the copy foo and ref foo patterns to give a good
568665
/// error message when parsing mistakes like `ref foo(a, b)`.

src/libsyntax/parse/parser/path.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ impl<'a> Parser<'a> {
423423
// FIXME(const_generics): to distinguish between idents for types and consts,
424424
// we should introduce a GenericArg::Ident in the AST and distinguish when
425425
// lowering to the HIR. For now, idents for const args are not permitted.
426-
if self.token.is_keyword(kw::True) || self.token.is_keyword(kw::False) {
426+
if self.token.is_bool_lit() {
427427
self.parse_literal_maybe_minus()?
428428
} else {
429429
return Err(

src/libsyntax/parse/token.rs

+20-23
Original file line numberDiff line numberDiff line change
@@ -409,18 +409,16 @@ impl Token {
409409
crate fn expect_lit(&self) -> Lit {
410410
match self.kind {
411411
Literal(lit) => lit,
412-
_=> panic!("`expect_lit` called on non-literal"),
412+
_ => panic!("`expect_lit` called on non-literal"),
413413
}
414414
}
415415

416416
/// Returns `true` if the token is any literal, a minus (which can prefix a literal,
417417
/// for example a '-42', or one of the boolean idents).
418418
crate fn can_begin_literal_or_bool(&self) -> bool {
419419
match self.kind {
420-
Literal(..) => true,
421-
BinOp(Minus) => true,
422-
Ident(name, false) if name == kw::True => true,
423-
Ident(name, false) if name == kw::False => true,
420+
Literal(..) | BinOp(Minus) => true,
421+
Ident(name, false) if name.is_bool_lit() => true,
424422
Interpolated(ref nt) => match **nt {
425423
NtLiteral(..) => true,
426424
_ => false,
@@ -457,6 +455,7 @@ impl Token {
457455
pub fn is_ident(&self) -> bool {
458456
self.ident().is_some()
459457
}
458+
460459
/// Returns `true` if the token is a lifetime.
461460
crate fn is_lifetime(&self) -> bool {
462461
self.lifetime().is_some()
@@ -508,45 +507,43 @@ impl Token {
508507

509508
/// Returns `true` if the token is a given keyword, `kw`.
510509
pub fn is_keyword(&self, kw: Symbol) -> bool {
511-
self.ident().map(|(id, is_raw)| id.name == kw && !is_raw).unwrap_or(false)
510+
self.is_non_raw_ident_where(|id| id.name == kw)
512511
}
513512

514513
crate fn is_path_segment_keyword(&self) -> bool {
515-
match self.ident() {
516-
Some((id, false)) => id.is_path_segment_keyword(),
517-
_ => false,
518-
}
514+
self.is_non_raw_ident_where(ast::Ident::is_path_segment_keyword)
519515
}
520516

521517
// Returns true for reserved identifiers used internally for elided lifetimes,
522518
// unnamed method parameters, crate root module, error recovery etc.
523519
crate fn is_special_ident(&self) -> bool {
524-
match self.ident() {
525-
Some((id, false)) => id.is_special(),
526-
_ => false,
527-
}
520+
self.is_non_raw_ident_where(ast::Ident::is_special)
528521
}
529522

530523
/// Returns `true` if the token is a keyword used in the language.
531524
crate fn is_used_keyword(&self) -> bool {
532-
match self.ident() {
533-
Some((id, false)) => id.is_used_keyword(),
534-
_ => false,
535-
}
525+
self.is_non_raw_ident_where(ast::Ident::is_used_keyword)
536526
}
537527

538528
/// Returns `true` if the token is a keyword reserved for possible future use.
539529
crate fn is_unused_keyword(&self) -> bool {
540-
match self.ident() {
541-
Some((id, false)) => id.is_unused_keyword(),
542-
_ => false,
543-
}
530+
self.is_non_raw_ident_where(ast::Ident::is_unused_keyword)
544531
}
545532

546533
/// Returns `true` if the token is either a special identifier or a keyword.
547534
pub fn is_reserved_ident(&self) -> bool {
535+
self.is_non_raw_ident_where(ast::Ident::is_reserved)
536+
}
537+
538+
/// Returns `true` if the token is the identifier `true` or `false`.
539+
crate fn is_bool_lit(&self) -> bool {
540+
self.is_non_raw_ident_where(|id| id.name.is_bool_lit())
541+
}
542+
543+
/// Returns `true` if the token is a non-raw identifier for which `pred` holds.
544+
fn is_non_raw_ident_where(&self, pred: impl FnOnce(ast::Ident) -> bool) -> bool {
548545
match self.ident() {
549-
Some((id, false)) => id.is_reserved(),
546+
Some((id, false)) => pred(id),
550547
_ => false,
551548
}
552549
}

src/libsyntax_pos/symbol.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,11 @@ impl Symbol {
10831083
self == kw::DollarCrate
10841084
}
10851085

1086+
/// Returns `true` if the symbol is `true` or `false`.
1087+
pub fn is_bool_lit(self) -> bool {
1088+
self == kw::True || self == kw::False
1089+
}
1090+
10861091
/// This symbol can be a raw identifier.
10871092
pub fn can_be_raw(self) -> bool {
10881093
self != kw::Invalid && self != kw::Underscore && !self.is_path_segment_keyword()
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
}
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

+2-1
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 followed by a named binding
89
}

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
error: expected identifier, found reserved identifier `_`
2-
--> $DIR/issue-32501.rs:7:13
1+
error: `mut` must be followed by a named binding
2+
--> $DIR/issue-32501.rs:7:9
33
|
44
LL | let mut _ = 0;
5-
| ^ expected identifier, found reserved identifier
5+
| ^^^^^ help: remove the `mut` prefix: `_`
6+
|
7+
= note: `mut` may be followed by `variable` and `variable @ pattern`
68

79
error: aborting due to previous error
810

+1-1
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
}
+6-2
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

0 commit comments

Comments
 (0)