-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Recover mut $pat
and other improvements
#63945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
5cc1559
e49b958
f908aa9
dbbe336
42e895d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; | |
use crate::ptr::P; | ||
use crate::ast::{self, Attribute, Pat, PatKind, FieldPat, RangeEnd, RangeSyntax, Mac}; | ||
use crate::ast::{BindingMode, Ident, Mutability, Path, QSelf, Expr, ExprKind}; | ||
use crate::mut_visit::{noop_visit_pat, MutVisitor}; | ||
use crate::parse::token::{self}; | ||
use crate::print::pprust; | ||
use crate::source_map::{respan, Span, Spanned}; | ||
|
@@ -273,21 +274,20 @@ impl<'a> Parser<'a> { | |
// Parse _ | ||
PatKind::Wild | ||
} else if self.eat_keyword(kw::Mut) { | ||
self.recover_pat_ident_mut_first()? | ||
self.parse_pat_ident_mut()? | ||
} else if self.eat_keyword(kw::Ref) { | ||
// Parse ref ident @ pat / ref mut ident @ pat | ||
let mutbl = self.parse_mutability(); | ||
self.parse_pat_ident(BindingMode::ByRef(mutbl))? | ||
} else if self.eat_keyword(kw::Box) { | ||
// Parse `box pat` | ||
PatKind::Box(self.parse_pat_with_range_pat(false, None)?) | ||
} else if self.token.is_ident() && !self.token.is_reserved_ident() && | ||
self.parse_as_ident() { | ||
} else if self.can_be_ident_pat() { | ||
// Parse `ident @ pat` | ||
// This can give false positives and parse nullary enums, | ||
// they are dealt with later in resolve. | ||
self.parse_pat_ident(BindingMode::ByValue(Mutability::Immutable))? | ||
} else if self.token.is_path_start() { | ||
} else if self.is_start_of_pat_with_path() { | ||
// Parse pattern starting with a path | ||
let (qself, path) = if self.eat_lt() { | ||
// Parse a qualified path | ||
|
@@ -384,24 +384,94 @@ impl<'a> Parser<'a> { | |
}) | ||
} | ||
|
||
/// Parse a mutable binding with the `mut` token already eaten. | ||
fn parse_pat_ident_mut(&mut self) -> PResult<'a, PatKind> { | ||
let mut_span = self.prev_span; | ||
|
||
if self.eat_keyword(kw::Ref) { | ||
return self.recover_mut_ref_ident(mut_span) | ||
} | ||
|
||
self.recover_additional_muts(); | ||
|
||
// Make sure we don't allow e.g. `let mut $p;` where `$p:pat`. | ||
if let token::Interpolated(ref nt) = self.token.kind { | ||
if let token::NtPat(_) = **nt { | ||
self.expected_ident_found().emit(); | ||
} | ||
} | ||
|
||
// Parse the pattern we hope to be an identifier. | ||
let mut pat = self.parse_pat(Some("identifier"))?; | ||
|
||
// Add `mut` to any binding in the parsed pattern. | ||
struct AddMut; | ||
impl MutVisitor for AddMut { | ||
fn visit_pat(&mut self, pat: &mut P<Pat>) { | ||
if let PatKind::Ident(BindingMode::ByValue(ref mut m), ..) = pat.node { | ||
*m = Mutability::Mutable; | ||
} | ||
noop_visit_pat(pat, self); | ||
} | ||
} | ||
AddMut.visit_pat(&mut pat); | ||
|
||
// Unwrap; If we don't have `mut $ident`, error. | ||
let pat = pat.into_inner(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could avoid this allocation by extracting a new function |
||
match &pat.node { | ||
PatKind::Ident(..) => {} | ||
_ => self.ban_mut_general_pat(mut_span, &pat), | ||
} | ||
|
||
Ok(pat.node) | ||
} | ||
|
||
/// Recover on `mut ref? ident @ pat` and suggest | ||
/// that the order of `mut` and `ref` is incorrect. | ||
fn recover_pat_ident_mut_first(&mut self) -> PResult<'a, PatKind> { | ||
let mutref_span = self.prev_span.to(self.token.span); | ||
let binding_mode = if self.eat_keyword(kw::Ref) { | ||
self.struct_span_err(mutref_span, "the order of `mut` and `ref` is incorrect") | ||
.span_suggestion( | ||
mutref_span, | ||
"try switching the order", | ||
"ref mut".into(), | ||
Applicability::MachineApplicable | ||
) | ||
.emit(); | ||
BindingMode::ByRef(Mutability::Mutable) | ||
} else { | ||
BindingMode::ByValue(Mutability::Mutable) | ||
}; | ||
self.parse_pat_ident(binding_mode) | ||
fn recover_mut_ref_ident(&mut self, lo: Span) -> PResult<'a, PatKind> { | ||
let mutref_span = lo.to(self.prev_span); | ||
self.struct_span_err(mutref_span, "the order of `mut` and `ref` is incorrect") | ||
.span_suggestion( | ||
mutref_span, | ||
"try switching the order", | ||
"ref mut".into(), | ||
Applicability::MachineApplicable | ||
) | ||
.emit(); | ||
|
||
self.parse_pat_ident(BindingMode::ByRef(Mutability::Mutable)) | ||
} | ||
|
||
/// Error on `mut $pat` where `$pat` is not an ident. | ||
fn ban_mut_general_pat(&self, lo: Span, pat: &Pat) { | ||
let span = lo.to(pat.span); | ||
self.struct_span_err(span, "`mut` must be attached to each individual binding") | ||
.span_suggestion( | ||
span, | ||
"add `mut` to each binding", | ||
pprust::pat_to_string(&pat), | ||
Applicability::MachineApplicable, | ||
) | ||
.emit(); | ||
} | ||
|
||
/// Eat any extraneous `mut`s and error + recover if we ate any. | ||
fn recover_additional_muts(&mut self) { | ||
let lo = self.token.span; | ||
while self.eat_keyword(kw::Mut) {} | ||
if lo == self.token.span { | ||
return; | ||
} | ||
|
||
let span = lo.to(self.prev_span); | ||
self.struct_span_err(span, "`mut` on a binding may not be repeated") | ||
.span_suggestion( | ||
span, | ||
"remove the additional `mut`s", | ||
String::new(), | ||
Applicability::MachineApplicable, | ||
) | ||
.emit(); | ||
} | ||
|
||
/// Parse macro invocation | ||
|
@@ -479,17 +549,6 @@ impl<'a> Parser<'a> { | |
Err(err) | ||
} | ||
|
||
// Helper function to decide whether to parse as ident binding | ||
// or to try to do something more complex like range patterns. | ||
fn parse_as_ident(&mut self) -> bool { | ||
self.look_ahead(1, |t| match t.kind { | ||
token::OpenDelim(token::Paren) | token::OpenDelim(token::Brace) | | ||
token::DotDotDot | token::DotDotEq | token::DotDot | | ||
token::ModSep | token::Not => false, | ||
_ => true, | ||
}) | ||
} | ||
|
||
/// Is the current token suitable as the start of a range patterns end? | ||
fn is_pat_range_end_start(&self) -> bool { | ||
self.token.is_path_start() // e.g. `MY_CONST`; | ||
|
@@ -563,6 +622,30 @@ impl<'a> Parser<'a> { | |
} | ||
} | ||
|
||
/// Is this the start of a pattern beginning with a path? | ||
fn is_start_of_pat_with_path(&mut self) -> bool { | ||
self.check_path() | ||
// Just for recovery (see `can_be_ident`). | ||
|| self.token.is_ident() && !self.token.is_bool_lit() && !self.token.is_keyword(kw::In) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this made me realize that we should have some check for "expected There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be a concrete example of a pitfall here? I'd like to avoid complicating things until someone reports a an issue. |
||
|
||
/// Would `parse_pat_ident` be appropriate here? | ||
fn can_be_ident_pat(&mut self) -> bool { | ||
self.check_ident() | ||
&& !self.token.is_bool_lit() // Avoid `true` or `false` as a binding as it is a literal. | ||
&& !self.token.is_path_segment_keyword() // Avoid e.g. `Self` as it is a path. | ||
// Avoid `in`. Due to recovery in the list parser this messes with `for ( $pat in $expr )`. | ||
&& !self.token.is_keyword(kw::In) | ||
&& self.look_ahead(1, |t| match t.kind { // Try to do something more complex? | ||
Centril marked this conversation as resolved.
Show resolved
Hide resolved
|
||
token::OpenDelim(token::Paren) // A tuple struct pattern. | ||
| token::OpenDelim(token::Brace) // A struct pattern. | ||
| token::DotDotDot | token::DotDotEq | token::DotDot // A range pattern. | ||
| token::ModSep // A tuple / struct variant pattern. | ||
| token::Not => false, // A macro expanding to a pattern. | ||
_ => true, | ||
}) | ||
} | ||
|
||
/// Parses `ident` or `ident @ pat`. | ||
/// Used by the copy foo and ref foo patterns to give a good | ||
/// error message when parsing mistakes like `ref foo(a, b)`. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
fn main() { | ||
let extern = 0; //~ ERROR expected pattern, found keyword `extern` | ||
let extern = 0; //~ ERROR expected identifier, found keyword `extern` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
error: expected pattern, found keyword `extern` | ||
error: expected identifier, found keyword `extern` | ||
--> $DIR/keyword-extern-as-identifier-pat.rs:2:9 | ||
| | ||
LL | let extern = 0; | ||
| ^^^^^^ expected pattern | ||
| ^^^^^^ expected identifier, found keyword | ||
help: you can escape reserved keywords to use them as identifiers | ||
| | ||
LL | let r#extern = 0; | ||
| ^^^^^^^^ | ||
|
||
error: aborting due to previous error | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
error: expected identifier, found reserved identifier `_` | ||
--> $DIR/issue-32501.rs:7:13 | ||
error: `mut` must be attached to each individual binding | ||
--> $DIR/issue-32501.rs:7:9 | ||
| | ||
LL | let mut _ = 0; | ||
| ^ expected identifier, found reserved identifier | ||
| ^^^^^ help: add `mut` to each binding: `_` | ||
Centril marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
error: aborting due to previous error | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
fn main() { | ||
let abstract = (); //~ ERROR expected pattern, found reserved keyword `abstract` | ||
let abstract = (); //~ ERROR expected identifier, found reserved keyword `abstract` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
error: expected pattern, found reserved keyword `abstract` | ||
error: expected identifier, found reserved keyword `abstract` | ||
--> $DIR/keyword-abstract.rs:2:9 | ||
| | ||
LL | let abstract = (); | ||
| ^^^^^^^^ expected pattern | ||
| ^^^^^^^^ expected identifier, found reserved keyword | ||
help: you can escape reserved keywords to use them as identifiers | ||
| | ||
LL | let r#abstract = (); | ||
| ^^^^^^^^^^ | ||
|
||
error: aborting due to previous error | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
// This file was auto-generated using 'src/etc/generate-keyword-tests.py as' | ||
|
||
fn main() { | ||
let as = "foo"; //~ error: expected pattern, found keyword `as` | ||
let as = "foo"; //~ error: expected identifier, found keyword `as` | ||
} |
Uh oh!
There was an error while loading. Please reload this page.