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

Some fixes for error recovery in the compiler #32435

Merged
merged 3 commits into from
Mar 26, 2016
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
95 changes: 71 additions & 24 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ pub struct Parser<'a> {
/// Used to determine the path to externally loaded source files
pub filename: Option<String>,
pub mod_path_stack: Vec<InternedString>,
/// Stack of spans of open delimiters. Used for error message.
pub open_braces: Vec<Span>,
/// Stack of open delimiters and their spans. Used for error message.
pub open_braces: Vec<(token::DelimToken, Span)>,
/// Flag if this parser "owns" the directory that it is currently parsing
/// in. This will affect how nested files are looked up.
pub owns_directory: bool,
Expand Down Expand Up @@ -895,7 +895,7 @@ impl<'a> Parser<'a> {
sep: SeqSep,
f: F)
-> Vec<T>
where F: FnMut(&mut Parser<'a>) -> PResult<'a, T>,
where F: FnMut(&mut Parser<'a>) -> PResult<'a, T>
{
self.parse_seq_to_before_tokens(&[ket], sep, f, |mut e| e.emit())
}
Expand Down Expand Up @@ -2755,8 +2755,8 @@ impl<'a> Parser<'a> {
let mut err: DiagnosticBuilder<'a> =
self.diagnostic().struct_span_err(self.span,
"this file contains an un-closed delimiter");
for sp in &self.open_braces {
err.span_help(*sp, "did you mean to close this delimiter?");
for &(_, sp) in &self.open_braces {
err.span_help(sp, "did you mean to close this delimiter?");
}

Err(err)
Expand All @@ -2766,23 +2766,66 @@ impl<'a> Parser<'a> {
let pre_span = self.span;

// Parse the open delimiter.
self.open_braces.push(self.span);
self.open_braces.push((delim, self.span));
let open_span = self.span;
self.bump();

// Parse the token trees within the delimiters
let tts = self.parse_seq_to_before_end(&token::CloseDelim(delim),
SeqSep::none(),
|p| p.parse_token_tree());
// Parse the token trees within the delimiters.
// We stop at any delimiter so we can try to recover if the user
// uses an incorrect delimiter.
let tts = self.parse_seq_to_before_tokens(&[&token::CloseDelim(token::Brace),
&token::CloseDelim(token::Paren),
&token::CloseDelim(token::Bracket)],
SeqSep::none(),
|p| p.parse_token_tree(),
|mut e| e.emit());

// Parse the close delimiter.
let close_span = self.span;
self.bump();
self.open_braces.pop().unwrap();

// Expand to cover the entire delimited token tree
let span = Span { hi: close_span.hi, ..pre_span };

match self.token {
Copy link
Contributor

Choose a reason for hiding this comment

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

is this code specific to macros? if so, I don't see any tests below that would exercise it.

// Correct delmiter.
token::CloseDelim(d) if d == delim => {
self.open_braces.pop().unwrap();

// Parse the close delimiter.
self.bump();
}
// Incorect delimiter.
token::CloseDelim(other) => {
let token_str = self.this_token_to_string();
let mut err = self.diagnostic().struct_span_err(self.span,
&format!("incorrect close delimiter: `{}`", token_str));
// This is a conservative error: only report the last unclosed delimiter.
// The previous unclosed delimiters could actually be closed! The parser
// just hasn't gotten to them yet.
if let Some(&(_, sp)) = self.open_braces.last() {
err.span_note(sp, "unclosed delimiter");
};
err.emit();
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like once we emit one unclosed delimeter error, we should try to avoid reporting any further ones, since all further delimeters are pretty suspect.


self.open_braces.pop().unwrap();

// If the incorrect delimter matches an earlier opening
// delimiter, then don't consume it (it can be used to
// close the earlier one)Otherwise, consume it.
// E.g., we try to recover from:
// fn foo() {
// bar(baz(
// } // Incorrect delimiter but matches the earlier `{`
Copy link
Contributor

Choose a reason for hiding this comment

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

should this example be bar!(baz(? like, will parse_token_tree be invoked outside of a macro invocation?

if !self.open_braces.iter().any(|&(b, _)| b == other) {
self.bump();
}
}
token::Eof => {
// Silently recover, the EOF token will be seen again
// and an error emitted then. Thus we don't pop from
// self.open_braces here.
},
_ => unreachable!(),
}

Ok(TokenTree::Delimited(span, Rc::new(Delimited {
delim: delim,
open_span: open_span,
Expand All @@ -2798,16 +2841,11 @@ impl<'a> Parser<'a> {
maybe_whole!(deref self, NtTT);
match self.token {
token::CloseDelim(_) => {
// An unexpected closing delimiter (i.e., there is no
// matching opening delimiter).
let token_str = self.this_token_to_string();
let mut err = self.diagnostic().struct_span_err(self.span,
&format!("incorrect close delimiter: `{}`", token_str));
// This is a conservative error: only report the last unclosed delimiter.
// The previous unclosed delimiters could actually be closed! The parser
// just hasn't gotten to them yet.
if let Some(&sp) = self.open_braces.last() {
err.span_note(sp, "unclosed delimiter");
};

let err = self.diagnostic().struct_span_err(self.span,
&format!("unexpected close delimiter: `{}`", token_str));
Err(err)
},
/* we ought to allow different depths of unquotation */
Expand Down Expand Up @@ -3824,7 +3862,9 @@ impl<'a> Parser<'a> {
fn recover_stmt_(&mut self, break_on_semi: SemiColonMode) {
let mut brace_depth = 0;
let mut bracket_depth = 0;
debug!("recover_stmt_ enter loop");
loop {
debug!("recover_stmt_ loop {:?}", self.token);
match self.token {
token::OpenDelim(token::DelimToken::Brace) => {
brace_depth += 1;
Expand All @@ -3836,6 +3876,7 @@ impl<'a> Parser<'a> {
}
token::CloseDelim(token::DelimToken::Brace) => {
if brace_depth == 0 {
debug!("recover_stmt_ return - close delim {:?}", self.token);
return;
}
brace_depth -= 1;
Expand All @@ -3848,12 +3889,16 @@ impl<'a> Parser<'a> {
}
self.bump();
}
token::Eof => return,
token::Eof => {
debug!("recover_stmt_ return - Eof");
return;
}
token::Semi => {
self.bump();
if break_on_semi == SemiColonMode::Break &&
brace_depth == 0 &&
bracket_depth == 0 {
debug!("recover_stmt_ return - Semi");
return;
}
}
Expand Down Expand Up @@ -4042,6 +4087,8 @@ impl<'a> Parser<'a> {
while !self.eat(&token::CloseDelim(token::Brace)) {
let Spanned {node, span} = if let Some(s) = self.parse_stmt_() {
s
} else if self.token == token::Eof {
break;
} else {
// Found only `;` or `}`.
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// Copyright 2013-2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
Expand All @@ -11,11 +11,9 @@
// FIXME(31528) we emit a bunch of silly errors here due to continuing past the
// first one. This would be easy-ish to address by better recovery in tokenisation.

// compile-flags: -Z parse-only

pub fn trace_option(option: Option<isize>) { //~ HELP did you mean to close this delimiter?
pub fn trace_option(option: Option<isize>) {
option.map(|some| 42; //~ NOTE: unclosed delimiter
//~^ ERROR: expected one of
//~^^ ERROR: mismatched types
} //~ ERROR: incorrect close delimiter
//~^ ERROR: expected one of
//~ ERROR: this file contains an un-closed delimiter
16 changes: 16 additions & 0 deletions src/test/compile-fail/issue-31804.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2016 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.

// Test that error recovery in the parser to an EOF does not give an infinite
// spew of errors.

fn main() {
let
} //~ ERROR unexpected token: `}`
17 changes: 17 additions & 0 deletions src/test/compile-fail/token-error-correct-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2016 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.

// Test that we do some basic error correcton in the tokeniser (and don't ICE).

fn main() {
if foo { //~ NOTE: unclosed delimiter
//~^ ERROR: unresolved name `foo`
) //~ ERROR: incorrect close delimiter: `)`
}
33 changes: 33 additions & 0 deletions src/test/compile-fail/token-error-correct-3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2016 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.

// Test that we do some basic error correcton in the tokeniser (and don't spew
// too many bogus errors).

pub mod raw {
use std::{io, fs};
use std::path::Path;

pub fn ensure_dir_exists<P: AsRef<Path>, F: FnOnce(&Path)>(path: P,
callback: F)
-> io::Result<bool> {
if !is_directory(path.as_ref()) { //~ ERROR: unresolved name `is_directory`
callback(path.as_ref(); //~ NOTE: unclosed delimiter
//~^ ERROR: expected one of
fs::create_dir_all(path.as_ref()).map(|()| true) //~ ERROR: expected one of
} else { //~ ERROR: incorrect close delimiter: `}`
Ok(false);
}

panic!();
}
}

fn main() {}
20 changes: 20 additions & 0 deletions src/test/compile-fail/token-error-correct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2016 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.

// Test that we do some basic error correcton in the tokeniser.

fn main() {
foo(bar(; //~ NOTE: unclosed delimiter
Copy link
Contributor

Choose a reason for hiding this comment

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

I am a bit surprised to see so many errors here. I feel like after the ; we should "reset" and not consider the } to be an error.

//~^ NOTE: unclosed delimiter
//~^^ ERROR: unexpected token: `;`
//~^^^ ERROR: unresolved name `bar`
//~^^^^ ERROR: unresolved name `foo`
} //~ ERROR: incorrect close delimiter: `}`
//~^ ERROR: incorrect close delimiter: `}`
2 changes: 1 addition & 1 deletion src/test/parse-fail/issue-2354-1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@

// compile-flags: -Z parse-only

static foo: isize = 2; } //~ ERROR incorrect close delimiter:
static foo: isize = 2; } //~ ERROR unexpected close delimiter:
2 changes: 1 addition & 1 deletion src/test/parse-fail/macro-mismatched-delim-paren-brace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ fn main() {
foo! (
bar, "baz", 1, 2.0
} //~ ERROR incorrect close delimiter
}
} //~ ERROR unexpected close delimiter: `}`
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this does exercise the macro logic, my mistake.