Skip to content

Commit

Permalink
Rollup merge of rust-lang#63945 - Centril:recover-mut-pat, r=estebank
Browse files Browse the repository at this point in the history
Recover `mut $pat` and other improvements

- Recover on e.g. `mut Foo(x, y)` and suggest `Foo(mut x, mut y)`. Fixes rust-lang#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.
  • Loading branch information
Centril authored Aug 29, 2019
2 parents eb4ac32 + 42e895d commit 52c3846
Show file tree
Hide file tree
Showing 75 changed files with 546 additions and 171 deletions.
4 changes: 2 additions & 2 deletions src/libsyntax/parse/literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl LitKind {

Ok(match kind {
token::Bool => {
assert!(symbol == kw::True || symbol == kw::False);
assert!(symbol.is_bool_lit());
LitKind::Bool(symbol == kw::True)
}
token::Byte => return unescape_byte(&symbol.as_str())
Expand Down Expand Up @@ -261,7 +261,7 @@ impl Lit {
/// Converts arbitrary token into an AST literal.
crate fn from_token(token: &Token) -> Result<Lit, LitError> {
let lit = match token.kind {
token::Ident(name, false) if name == kw::True || name == kw::False =>
token::Ident(name, false) if name.is_bool_lit() =>
token::Lit::new(token::Bool, name, None),
token::Literal(lit) =>
lit,
Expand Down
155 changes: 126 additions & 29 deletions src/libsyntax/parse/parser/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
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
Expand Down Expand Up @@ -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`;
Expand Down Expand Up @@ -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)
}

/// 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?
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)`.
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/parse/parser/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ impl<'a> Parser<'a> {
// FIXME(const_generics): to distinguish between idents for types and consts,
// we should introduce a GenericArg::Ident in the AST and distinguish when
// lowering to the HIR. For now, idents for const args are not permitted.
if self.token.is_keyword(kw::True) || self.token.is_keyword(kw::False) {
if self.token.is_bool_lit() {
self.parse_literal_maybe_minus()?
} else {
return Err(
Expand Down
43 changes: 20 additions & 23 deletions src/libsyntax/parse/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,18 +409,16 @@ impl Token {
crate fn expect_lit(&self) -> Lit {
match self.kind {
Literal(lit) => lit,
_=> panic!("`expect_lit` called on non-literal"),
_ => panic!("`expect_lit` called on non-literal"),
}
}

/// Returns `true` if the token is any literal, a minus (which can prefix a literal,
/// for example a '-42', or one of the boolean idents).
crate fn can_begin_literal_or_bool(&self) -> bool {
match self.kind {
Literal(..) => true,
BinOp(Minus) => true,
Ident(name, false) if name == kw::True => true,
Ident(name, false) if name == kw::False => true,
Literal(..) | BinOp(Minus) => true,
Ident(name, false) if name.is_bool_lit() => true,
Interpolated(ref nt) => match **nt {
NtLiteral(..) => true,
_ => false,
Expand Down Expand Up @@ -457,6 +455,7 @@ impl Token {
pub fn is_ident(&self) -> bool {
self.ident().is_some()
}

/// Returns `true` if the token is a lifetime.
crate fn is_lifetime(&self) -> bool {
self.lifetime().is_some()
Expand Down Expand Up @@ -508,45 +507,43 @@ impl Token {

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

crate fn is_path_segment_keyword(&self) -> bool {
match self.ident() {
Some((id, false)) => id.is_path_segment_keyword(),
_ => false,
}
self.is_non_raw_ident_where(ast::Ident::is_path_segment_keyword)
}

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

/// Returns `true` if the token is a keyword used in the language.
crate fn is_used_keyword(&self) -> bool {
match self.ident() {
Some((id, false)) => id.is_used_keyword(),
_ => false,
}
self.is_non_raw_ident_where(ast::Ident::is_used_keyword)
}

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

/// Returns `true` if the token is either a special identifier or a keyword.
pub fn is_reserved_ident(&self) -> bool {
self.is_non_raw_ident_where(ast::Ident::is_reserved)
}

/// Returns `true` if the token is the identifier `true` or `false`.
crate fn is_bool_lit(&self) -> bool {
self.is_non_raw_ident_where(|id| id.name.is_bool_lit())
}

/// Returns `true` if the token is a non-raw identifier for which `pred` holds.
fn is_non_raw_ident_where(&self, pred: impl FnOnce(ast::Ident) -> bool) -> bool {
match self.ident() {
Some((id, false)) => id.is_reserved(),
Some((id, false)) => pred(id),
_ => false,
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/libsyntax_pos/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,11 @@ impl Symbol {
self == kw::DollarCrate
}

/// Returns `true` if the symbol is `true` or `false`.
pub fn is_bool_lit(self) -> bool {
self == kw::True || self == kw::False
}

/// This symbol can be a raw identifier.
pub fn can_be_raw(self) -> bool {
self != kw::Invalid && self != kw::Underscore && !self.is_path_segment_keyword()
Expand Down
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

3 changes: 2 additions & 1 deletion src/test/ui/parser/issue-32501.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ fn main() {
let _ = 0;
let mut b = 0;
let mut _b = 0;
let mut _ = 0; //~ ERROR expected identifier, found reserved identifier `_`
let mut _ = 0;
//~^ ERROR `mut` must be followed by a named binding
}
8 changes: 5 additions & 3 deletions src/test/ui/parser/issue-32501.stderr
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

2 changes: 1 addition & 1 deletion src/test/ui/parser/keyword-abstract.rs
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`
}
8 changes: 6 additions & 2 deletions src/test/ui/parser/keyword-abstract.stderr
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

Loading

0 comments on commit 52c3846

Please sign in to comment.