-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Add parse-error recovery for erroneous struct_id { }
form.
#8418
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Changes from all commits
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 |
---|---|---|
|
@@ -281,6 +281,7 @@ pub fn Parser(sess: @mut ParseSess, | |
token: @mut tok0.tok, | ||
span: @mut span, | ||
last_span: @mut span, | ||
last_token: @mut None, | ||
buffer: @mut ([ | ||
placeholder.clone(), | ||
placeholder.clone(), | ||
|
@@ -307,6 +308,8 @@ pub struct Parser { | |
span: @mut span, | ||
// the span of the prior token: | ||
last_span: @mut span, | ||
// the previous token or None (only stashed sometimes). | ||
last_token: @mut Option<~token::Token>, | ||
buffer: @mut [TokenAndSpan, ..4], | ||
buffer_start: @mut int, | ||
buffer_end: @mut int, | ||
|
@@ -374,6 +377,89 @@ impl Parser { | |
} | ||
} | ||
|
||
// Expect next token to be edible or inedible token. If edible, | ||
// then consume it; if inedible, then return without consuming | ||
// anything. Signal a fatal error if next token is unexpected. | ||
pub fn expect_one_of(&self, edible: &[token::Token], inedible: &[token::Token]) { | ||
fn tokens_to_str(p:&Parser, tokens: &[token::Token]) -> ~str { | ||
let mut i = tokens.iter(); | ||
// This might be a sign we need a connect method on Iterator. | ||
let b = i.next().map_default(~"", |t| p.token_to_str(*t)); | ||
i.fold(b, |b,a| b + " " + p.token_to_str(a)) | ||
} | ||
if edible.contains(self.token) { | ||
self.bump(); | ||
} else if inedible.contains(self.token) { | ||
// leave it in the input | ||
} else { | ||
let expected = vec::append(edible.to_owned(), inedible); | ||
let expect = tokens_to_str(self, expected); | ||
let actual = self.this_token_to_str(); | ||
self.fatal( | ||
if expected.len() != 1 { | ||
fmt!("expected one of `%s` but found `%s`", expect, actual) | ||
} else { | ||
fmt!("expected `%s` but found `%s`", expect, actual) | ||
} | ||
) | ||
} | ||
} | ||
|
||
// Check for erroneous `ident { }`; if matches, signal error and | ||
// recover (without consuming any expected input token). Returns | ||
// true if and only if input was consumed for recovery. | ||
pub fn check_for_erroneous_unit_struct_expecting(&self, expected: &[token::Token]) -> bool { | ||
if *self.token == token::LBRACE | ||
&& expected.iter().all(|t| *t != token::LBRACE) | ||
&& self.look_ahead(1, |t| *t == token::RBRACE) { | ||
// matched; signal non-fatal error and recover. | ||
self.span_err(*self.span, | ||
"Unit-like struct construction is written with no trailing `{ }`"); | ||
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. nit: lowercase initial letter of error message |
||
self.eat(&token::LBRACE); | ||
self.eat(&token::RBRACE); | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
// Commit to parsing a complete expression `e` expected to be | ||
// followed by some token from the set edible + inedible. Recover | ||
// from anticipated input errors, discarding erroneous characters. | ||
pub fn commit_expr(&self, e: @expr, edible: &[token::Token], inedible: &[token::Token]) { | ||
debug!("commit_expr %?", e); | ||
match e.node { | ||
expr_path(*) => { | ||
// might be unit-struct construction; check for recoverableinput error. | ||
let expected = vec::append(edible.to_owned(), inedible); | ||
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. Do we still not have an 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. nope, only |
||
self.check_for_erroneous_unit_struct_expecting(expected); | ||
} | ||
_ => {} | ||
} | ||
self.expect_one_of(edible, inedible) | ||
} | ||
|
||
pub fn commit_expr_expecting(&self, e: @expr, edible: token::Token) { | ||
self.commit_expr(e, &[edible], &[]) | ||
} | ||
|
||
// Commit to parsing a complete statement `s`, which expects to be | ||
// followed by some token from the set edible + inedible. Check | ||
// for recoverable input errors, discarding erroneous characters. | ||
pub fn commit_stmt(&self, s: @stmt, edible: &[token::Token], inedible: &[token::Token]) { | ||
debug!("commit_stmt %?", s); | ||
let _s = s; // unused, but future checks might want to inspect `s`. | ||
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. Are you trying to suppress the unused variable warning? I might just do |
||
if self.last_token.map_default(false, |t|is_ident_or_path(*t)) { | ||
let expected = vec::append(edible.to_owned(), inedible); | ||
self.check_for_erroneous_unit_struct_expecting(expected); | ||
} | ||
self.expect_one_of(edible, inedible) | ||
} | ||
|
||
pub fn commit_stmt_expecting(&self, s: @stmt, edible: token::Token) { | ||
self.commit_stmt(s, &[edible], &[]) | ||
} | ||
|
||
pub fn parse_ident(&self) -> ast::ident { | ||
self.check_strict_keywords(); | ||
self.check_reserved_keywords(); | ||
|
@@ -576,6 +662,12 @@ impl Parser { | |
// advance the parser by one token | ||
pub fn bump(&self) { | ||
*self.last_span = *self.span; | ||
// Stash token for error recovery (sometimes; clone is not necessarily cheap). | ||
*self.last_token = if is_ident_or_path(self.token) { | ||
Some(~(*self.token).clone()) | ||
} else { | ||
None | ||
}; | ||
let next = if *self.buffer_start == *self.buffer_end { | ||
self.reader.next_token() | ||
} else { | ||
|
@@ -1593,17 +1685,19 @@ impl Parser { | |
return self.mk_expr(lo, hi, expr_lit(lit)); | ||
} | ||
let mut es = ~[self.parse_expr()]; | ||
self.commit_expr(*es.last(), &[], &[token::COMMA, token::RPAREN]); | ||
while *self.token == token::COMMA { | ||
self.bump(); | ||
if *self.token != token::RPAREN { | ||
es.push(self.parse_expr()); | ||
self.commit_expr(*es.last(), &[], &[token::COMMA, token::RPAREN]); | ||
} | ||
else { | ||
trailing_comma = true; | ||
} | ||
} | ||
hi = self.span.hi; | ||
self.expect(&token::RPAREN); | ||
self.commit_expr_expecting(*es.last(), token::RPAREN); | ||
|
||
return if es.len() == 1 && !trailing_comma { | ||
self.mk_expr(lo, self.span.hi, expr_paren(es[0])) | ||
|
@@ -1743,7 +1837,7 @@ impl Parser { | |
break; | ||
} | ||
|
||
self.expect(&token::COMMA); | ||
self.commit_expr(fields.last().expr, &[token::COMMA], &[token::RBRACE]); | ||
|
||
if self.eat(&token::DOTDOT) { | ||
base = Some(self.parse_expr()); | ||
|
@@ -1758,7 +1852,7 @@ impl Parser { | |
} | ||
|
||
hi = pth.span.hi; | ||
self.expect(&token::RBRACE); | ||
self.commit_expr_expecting(fields.last().expr, token::RBRACE); | ||
ex = expr_struct(pth, fields, base); | ||
return self.mk_expr(lo, hi, ex); | ||
} | ||
|
@@ -1852,7 +1946,7 @@ impl Parser { | |
self.bump(); | ||
let ix = self.parse_expr(); | ||
hi = ix.span.hi; | ||
self.expect(&token::RBRACKET); | ||
self.commit_expr_expecting(ix, token::RBRACKET); | ||
e = self.mk_expr(lo, hi, self.mk_index(e, ix)); | ||
} | ||
|
||
|
@@ -2461,7 +2555,7 @@ impl Parser { | |
fn parse_match_expr(&self) -> @expr { | ||
let lo = self.last_span.lo; | ||
let discriminant = self.parse_expr(); | ||
self.expect(&token::LBRACE); | ||
self.commit_expr_expecting(discriminant, token::LBRACE); | ||
let mut arms: ~[arm] = ~[]; | ||
while *self.token != token::RBRACE { | ||
let pats = self.parse_pats(); | ||
|
@@ -2477,7 +2571,7 @@ impl Parser { | |
&& *self.token != token::RBRACE; | ||
|
||
if require_comma { | ||
self.expect(&token::COMMA); | ||
self.commit_expr(expr, &[token::COMMA], &[token::RBRACE]); | ||
} else { | ||
self.eat(&token::COMMA); | ||
} | ||
|
@@ -3177,37 +3271,26 @@ impl Parser { | |
match stmt.node { | ||
stmt_expr(e, stmt_id) => { | ||
// expression without semicolon | ||
let has_semi; | ||
if classify::stmt_ends_with_semi(stmt) { | ||
// Just check for errors and recover; do not eat semicolon yet. | ||
self.commit_stmt(stmt, &[], &[token::SEMI, token::RBRACE]); | ||
} | ||
|
||
match *self.token { | ||
token::SEMI => { | ||
has_semi = true; | ||
self.bump(); | ||
stmts.push(@codemap::spanned { | ||
node: stmt_semi(e, stmt_id), | ||
span: stmt.span, | ||
}); | ||
} | ||
token::RBRACE => { | ||
has_semi = false; | ||
expr = Some(e); | ||
} | ||
ref t => { | ||
has_semi = false; | ||
if classify::stmt_ends_with_semi(stmt) { | ||
self.fatal( | ||
fmt!( | ||
"expected `;` or `}` after \ | ||
expression but found `%s`", | ||
self.token_to_str(t) | ||
) | ||
); | ||
} | ||
_ => { | ||
stmts.push(stmt); | ||
} | ||
} | ||
|
||
if has_semi { | ||
self.bump(); | ||
stmts.push(@codemap::spanned { | ||
node: stmt_semi(e, stmt_id), | ||
span: stmt.span, | ||
}); | ||
} | ||
} | ||
stmt_mac(ref m, _) => { | ||
// statement macro; might be an expr | ||
|
@@ -3243,7 +3326,7 @@ impl Parser { | |
stmts.push(stmt); | ||
|
||
if classify::stmt_ends_with_semi(stmt) { | ||
self.expect(&token::SEMI); | ||
self.commit_stmt_expecting(stmt, token::SEMI); | ||
} | ||
} | ||
} | ||
|
@@ -3758,7 +3841,7 @@ impl Parser { | |
} | ||
} | ||
if fields.len() == 0 { | ||
self.fatal(fmt!("Unit-like struct should be written as `struct %s;`", | ||
self.fatal(fmt!("Unit-like struct definition should be written as `struct %s;`", | ||
get_ident_interner().get(class_name.name))); | ||
} | ||
self.bump(); | ||
|
@@ -3938,7 +4021,7 @@ impl Parser { | |
let ty = self.parse_ty(false); | ||
self.expect(&token::EQ); | ||
let e = self.parse_expr(); | ||
self.expect(&token::SEMI); | ||
self.commit_expr_expecting(e, token::SEMI); | ||
(id, item_static(ty, m, e), None) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// error-pattern: Unit-like struct construction is written with no trailing `{ }` | ||
struct Foo; | ||
|
||
fn f2() { | ||
let _end_stmt = Foo { }; | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// error-pattern: Unit-like struct construction is written with no trailing `{ }` | ||
struct Foo; | ||
|
||
fn g3() { | ||
let _mid_tuple = (Foo { }, 2); | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// error-pattern: Unit-like struct construction is written with no trailing `{ }` | ||
struct Foo; | ||
|
||
fn h4() { | ||
let _end_of_tuple = (3, Foo { }); | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// error-pattern: Unit-like struct construction is written with no trailing `{ }` | ||
struct Foo; | ||
|
||
fn i5() { | ||
let _end_of_block = { Foo { } }; | ||
} | ||
|
||
fn main() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we prefer "expected foo, found bar" to "expected foo but found bar"