Skip to content

Commit de48274

Browse files
committed
auto merge of #8418 : pnkfelix/rust/fsk-issue3192-improve-parse-error-for-empty-struct-init, r=pcwalton,me
Fix #3192. r? anyone There are 4 different new tests, to check some different scenarios for what the parse context is at the time of recovery, becasue our compile-fail infrastructure does not appear to handle verifying error-recovery situations. Differentiate between unit-like struct definition item and unit-like struct construction in the error message. ---- More generally, outlines a more generic strategy for parse error recovery: By committing to an expression/statement at set points in the parser, we can then do some look-ahead to catch common mistakes and skip over them. One detail about this strategy is that you want to avoid emitting the "helpful" message unless the input is reasonably close to the case of interest. (E.g. do not warn about a potential unit struct for an input of the form `let hmm = do foo { } { };`) To accomplish this, I added (partial) last_token tracking; used for `commit_stmt` support. The check_for_erroneous_unit_struct_expecting fn returns bool to signal whether it "made progress"; currently unused; this is meant for use to compose several such recovery checks together in a loop.
2 parents 1785841 + eee0447 commit de48274

File tree

6 files changed

+187
-32
lines changed

6 files changed

+187
-32
lines changed

src/libsyntax/parse/parser.rs

+114-31
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ pub fn Parser(sess: @mut ParseSess,
283283
token: @mut tok0.tok,
284284
span: @mut span,
285285
last_span: @mut span,
286+
last_token: @mut None,
286287
buffer: @mut ([
287288
placeholder.clone(),
288289
placeholder.clone(),
@@ -309,6 +310,8 @@ pub struct Parser {
309310
span: @mut span,
310311
// the span of the prior token:
311312
last_span: @mut span,
313+
// the previous token or None (only stashed sometimes).
314+
last_token: @mut Option<~token::Token>,
312315
buffer: @mut [TokenAndSpan, ..4],
313316
buffer_start: @mut int,
314317
buffer_end: @mut int,
@@ -376,6 +379,89 @@ impl Parser {
376379
}
377380
}
378381

382+
// Expect next token to be edible or inedible token. If edible,
383+
// then consume it; if inedible, then return without consuming
384+
// anything. Signal a fatal error if next token is unexpected.
385+
pub fn expect_one_of(&self, edible: &[token::Token], inedible: &[token::Token]) {
386+
fn tokens_to_str(p:&Parser, tokens: &[token::Token]) -> ~str {
387+
let mut i = tokens.iter();
388+
// This might be a sign we need a connect method on Iterator.
389+
let b = i.next().map_default(~"", |t| p.token_to_str(*t));
390+
i.fold(b, |b,a| b + " " + p.token_to_str(a))
391+
}
392+
if edible.contains(self.token) {
393+
self.bump();
394+
} else if inedible.contains(self.token) {
395+
// leave it in the input
396+
} else {
397+
let expected = vec::append(edible.to_owned(), inedible);
398+
let expect = tokens_to_str(self, expected);
399+
let actual = self.this_token_to_str();
400+
self.fatal(
401+
if expected.len() != 1 {
402+
fmt!("expected one of `%s` but found `%s`", expect, actual)
403+
} else {
404+
fmt!("expected `%s` but found `%s`", expect, actual)
405+
}
406+
)
407+
}
408+
}
409+
410+
// Check for erroneous `ident { }`; if matches, signal error and
411+
// recover (without consuming any expected input token). Returns
412+
// true if and only if input was consumed for recovery.
413+
pub fn check_for_erroneous_unit_struct_expecting(&self, expected: &[token::Token]) -> bool {
414+
if *self.token == token::LBRACE
415+
&& expected.iter().all(|t| *t != token::LBRACE)
416+
&& self.look_ahead(1, |t| *t == token::RBRACE) {
417+
// matched; signal non-fatal error and recover.
418+
self.span_err(*self.span,
419+
"Unit-like struct construction is written with no trailing `{ }`");
420+
self.eat(&token::LBRACE);
421+
self.eat(&token::RBRACE);
422+
true
423+
} else {
424+
false
425+
}
426+
}
427+
428+
// Commit to parsing a complete expression `e` expected to be
429+
// followed by some token from the set edible + inedible. Recover
430+
// from anticipated input errors, discarding erroneous characters.
431+
pub fn commit_expr(&self, e: @expr, edible: &[token::Token], inedible: &[token::Token]) {
432+
debug!("commit_expr %?", e);
433+
match e.node {
434+
expr_path(*) => {
435+
// might be unit-struct construction; check for recoverableinput error.
436+
let expected = vec::append(edible.to_owned(), inedible);
437+
self.check_for_erroneous_unit_struct_expecting(expected);
438+
}
439+
_ => {}
440+
}
441+
self.expect_one_of(edible, inedible)
442+
}
443+
444+
pub fn commit_expr_expecting(&self, e: @expr, edible: token::Token) {
445+
self.commit_expr(e, &[edible], &[])
446+
}
447+
448+
// Commit to parsing a complete statement `s`, which expects to be
449+
// followed by some token from the set edible + inedible. Check
450+
// for recoverable input errors, discarding erroneous characters.
451+
pub fn commit_stmt(&self, s: @stmt, edible: &[token::Token], inedible: &[token::Token]) {
452+
debug!("commit_stmt %?", s);
453+
let _s = s; // unused, but future checks might want to inspect `s`.
454+
if self.last_token.map_default(false, |t|is_ident_or_path(*t)) {
455+
let expected = vec::append(edible.to_owned(), inedible);
456+
self.check_for_erroneous_unit_struct_expecting(expected);
457+
}
458+
self.expect_one_of(edible, inedible)
459+
}
460+
461+
pub fn commit_stmt_expecting(&self, s: @stmt, edible: token::Token) {
462+
self.commit_stmt(s, &[edible], &[])
463+
}
464+
379465
pub fn parse_ident(&self) -> ast::ident {
380466
self.check_strict_keywords();
381467
self.check_reserved_keywords();
@@ -578,6 +664,12 @@ impl Parser {
578664
// advance the parser by one token
579665
pub fn bump(&self) {
580666
*self.last_span = *self.span;
667+
// Stash token for error recovery (sometimes; clone is not necessarily cheap).
668+
*self.last_token = if is_ident_or_path(self.token) {
669+
Some(~(*self.token).clone())
670+
} else {
671+
None
672+
};
581673
let next = if *self.buffer_start == *self.buffer_end {
582674
self.reader.next_token()
583675
} else {
@@ -1595,17 +1687,19 @@ impl Parser {
15951687
return self.mk_expr(lo, hi, expr_lit(lit));
15961688
}
15971689
let mut es = ~[self.parse_expr()];
1690+
self.commit_expr(*es.last(), &[], &[token::COMMA, token::RPAREN]);
15981691
while *self.token == token::COMMA {
15991692
self.bump();
16001693
if *self.token != token::RPAREN {
16011694
es.push(self.parse_expr());
1695+
self.commit_expr(*es.last(), &[], &[token::COMMA, token::RPAREN]);
16021696
}
16031697
else {
16041698
trailing_comma = true;
16051699
}
16061700
}
16071701
hi = self.span.hi;
1608-
self.expect(&token::RPAREN);
1702+
self.commit_expr_expecting(*es.last(), token::RPAREN);
16091703

16101704
return if es.len() == 1 && !trailing_comma {
16111705
self.mk_expr(lo, self.span.hi, expr_paren(es[0]))
@@ -1745,7 +1839,7 @@ impl Parser {
17451839
break;
17461840
}
17471841

1748-
self.expect(&token::COMMA);
1842+
self.commit_expr(fields.last().expr, &[token::COMMA], &[token::RBRACE]);
17491843

17501844
if self.eat(&token::DOTDOT) {
17511845
base = Some(self.parse_expr());
@@ -1760,7 +1854,7 @@ impl Parser {
17601854
}
17611855

17621856
hi = pth.span.hi;
1763-
self.expect(&token::RBRACE);
1857+
self.commit_expr_expecting(fields.last().expr, token::RBRACE);
17641858
ex = expr_struct(pth, fields, base);
17651859
return self.mk_expr(lo, hi, ex);
17661860
}
@@ -1854,7 +1948,7 @@ impl Parser {
18541948
self.bump();
18551949
let ix = self.parse_expr();
18561950
hi = ix.span.hi;
1857-
self.expect(&token::RBRACKET);
1951+
self.commit_expr_expecting(ix, token::RBRACKET);
18581952
e = self.mk_expr(lo, hi, self.mk_index(e, ix));
18591953
}
18601954

@@ -2463,7 +2557,7 @@ impl Parser {
24632557
fn parse_match_expr(&self) -> @expr {
24642558
let lo = self.last_span.lo;
24652559
let discriminant = self.parse_expr();
2466-
self.expect(&token::LBRACE);
2560+
self.commit_expr_expecting(discriminant, token::LBRACE);
24672561
let mut arms: ~[arm] = ~[];
24682562
while *self.token != token::RBRACE {
24692563
let pats = self.parse_pats();
@@ -2479,7 +2573,7 @@ impl Parser {
24792573
&& *self.token != token::RBRACE;
24802574

24812575
if require_comma {
2482-
self.expect(&token::COMMA);
2576+
self.commit_expr(expr, &[token::COMMA], &[token::RBRACE]);
24832577
} else {
24842578
self.eat(&token::COMMA);
24852579
}
@@ -3179,37 +3273,26 @@ impl Parser {
31793273
match stmt.node {
31803274
stmt_expr(e, stmt_id) => {
31813275
// expression without semicolon
3182-
let has_semi;
3276+
if classify::stmt_ends_with_semi(stmt) {
3277+
// Just check for errors and recover; do not eat semicolon yet.
3278+
self.commit_stmt(stmt, &[], &[token::SEMI, token::RBRACE]);
3279+
}
3280+
31833281
match *self.token {
31843282
token::SEMI => {
3185-
has_semi = true;
3283+
self.bump();
3284+
stmts.push(@codemap::spanned {
3285+
node: stmt_semi(e, stmt_id),
3286+
span: stmt.span,
3287+
});
31863288
}
31873289
token::RBRACE => {
3188-
has_semi = false;
31893290
expr = Some(e);
31903291
}
3191-
ref t => {
3192-
has_semi = false;
3193-
if classify::stmt_ends_with_semi(stmt) {
3194-
self.fatal(
3195-
fmt!(
3196-
"expected `;` or `}` after \
3197-
expression but found `%s`",
3198-
self.token_to_str(t)
3199-
)
3200-
);
3201-
}
3292+
_ => {
32023293
stmts.push(stmt);
32033294
}
32043295
}
3205-
3206-
if has_semi {
3207-
self.bump();
3208-
stmts.push(@codemap::spanned {
3209-
node: stmt_semi(e, stmt_id),
3210-
span: stmt.span,
3211-
});
3212-
}
32133296
}
32143297
stmt_mac(ref m, _) => {
32153298
// statement macro; might be an expr
@@ -3245,7 +3328,7 @@ impl Parser {
32453328
stmts.push(stmt);
32463329

32473330
if classify::stmt_ends_with_semi(stmt) {
3248-
self.expect(&token::SEMI);
3331+
self.commit_stmt_expecting(stmt, token::SEMI);
32493332
}
32503333
}
32513334
}
@@ -3760,7 +3843,7 @@ impl Parser {
37603843
}
37613844
}
37623845
if fields.len() == 0 {
3763-
self.fatal(fmt!("Unit-like struct should be written as `struct %s;`",
3846+
self.fatal(fmt!("Unit-like struct definition should be written as `struct %s;`",
37643847
get_ident_interner().get(class_name.name)));
37653848
}
37663849
self.bump();
@@ -3952,7 +4035,7 @@ impl Parser {
39524035
let ty = self.parse_ty(false);
39534036
self.expect(&token::EQ);
39544037
let e = self.parse_expr();
3955-
self.expect(&token::SEMI);
4038+
self.commit_expr_expecting(e, token::SEMI);
39564039
(id, item_static(ty, m, e), None)
39574040
}
39584041

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// error-pattern: Unit-like struct construction is written with no trailing `{ }`
12+
struct Foo;
13+
14+
fn f2() {
15+
let _end_stmt = Foo { };
16+
}
17+
18+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// error-pattern: Unit-like struct construction is written with no trailing `{ }`
12+
struct Foo;
13+
14+
fn g3() {
15+
let _mid_tuple = (Foo { }, 2);
16+
}
17+
18+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// error-pattern: Unit-like struct construction is written with no trailing `{ }`
12+
struct Foo;
13+
14+
fn h4() {
15+
let _end_of_tuple = (3, Foo { });
16+
}
17+
18+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// error-pattern: Unit-like struct construction is written with no trailing `{ }`
12+
struct Foo;
13+
14+
fn i5() {
15+
let _end_of_block = { Foo { } };
16+
}
17+
18+
fn main() {}

src/test/compile-fail/struct-no-fields.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// error-pattern: Unit-like struct should be written as `struct Foo;`
11+
// error-pattern: Unit-like struct definition should be written as `struct Foo;`
1212
struct Foo {}
1313

1414
fn main() {}

0 commit comments

Comments
 (0)