Skip to content
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

Recover mut $pat and other improvements #63945

Merged
merged 5 commits into from
Aug 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"))?;
Centril marked this conversation as resolved.
Show resolved Hide resolved

// 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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could avoid this allocation by extracting a new function fn parse_pat_with_range_pat_kind. This would also avoid the need for the NtPat logic above but it would also not recover on let mut $p where $p:pat.

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)
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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 Fn() -> _, found bool and expr is foo\n|| bar" to detect a missing ; on the first pattern and returning a closure was intended 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)`.
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 @@ -1063,6 +1063,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