-
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 all 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,108 @@ 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. | ||
let changed_any_binding = Self::make_all_value_bindings_mutable(&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, changed_any_binding), | ||
} | ||
|
||
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) | ||
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)) | ||
} | ||
|
||
/// Turn all by-value immutable bindings in a pattern into mutable bindings. | ||
/// Returns `true` if any change was made. | ||
fn make_all_value_bindings_mutable(pat: &mut P<Pat>) -> bool { | ||
struct AddMut(bool); | ||
impl MutVisitor for AddMut { | ||
fn visit_pat(&mut self, pat: &mut P<Pat>) { | ||
if let PatKind::Ident(BindingMode::ByValue(ref mut m @ Mutability::Immutable), ..) | ||
= pat.node | ||
{ | ||
*m = Mutability::Mutable; | ||
self.0 = true; | ||
} | ||
noop_visit_pat(pat, self); | ||
} | ||
} | ||
|
||
let mut add_mut = AddMut(false); | ||
add_mut.visit_pat(pat); | ||
add_mut.0 | ||
} | ||
|
||
/// Error on `mut $pat` where `$pat` is not an ident. | ||
fn ban_mut_general_pat(&self, lo: Span, pat: &Pat, changed_any_binding: bool) { | ||
let span = lo.to(pat.span); | ||
let fix = pprust::pat_to_string(&pat); | ||
let (problem, suggestion) = if changed_any_binding { | ||
("`mut` must be attached to each individual binding", "add `mut` to each binding") | ||
} else { | ||
BindingMode::ByValue(Mutability::Mutable) | ||
("`mut` must be followed by a named binding", "remove the `mut` prefix") | ||
}; | ||
self.parse_pat_ident(binding_mode) | ||
self.struct_span_err(span, problem) | ||
.span_suggestion(span, suggestion, fix, Applicability::MachineApplicable) | ||
.note("`mut` may be followed by `variable` and `variable @ pattern`") | ||
.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 +563,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 +636,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,10 @@ | ||
error: expected identifier, found reserved identifier `_` | ||
--> $DIR/issue-32501.rs:7:13 | ||
error: `mut` must be followed by a named binding | ||
--> $DIR/issue-32501.rs:7:9 | ||
| | ||
LL | let mut _ = 0; | ||
| ^ expected identifier, found reserved identifier | ||
| ^^^^^ help: remove the `mut` prefix: `_` | ||
| | ||
= note: `mut` may be followed by `variable` and `variable @ pattern` | ||
|
||
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 | ||
|
Uh oh!
There was an error while loading. Please reload this page.