Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dbcc2e8

Browse files
committedFeb 7, 2019
Auto merge of #57944 - estebank:unclosed-delim-the-quickening, r=<try>
Deduplicate mismatched delimiter errors Delay unmatched delimiter errors until after the parser has run to deduplicate them when parsing and attempt recovering intelligently. Second attempt at #54029, follow up to #53949. Fix #31528.
2 parents ad43389 + c54b230 commit dbcc2e8

18 files changed

+331
-156
lines changed
 

‎src/librustc_errors/emitter.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -672,8 +672,8 @@ impl EmitterWriter {
672672
// | | something about `foo`
673673
// | something about `fn foo()`
674674
annotations_position.sort_by(|a, b| {
675-
// Decreasing order
676-
a.1.len().cmp(&b.1.len()).reverse()
675+
// Decreasing order. When `a` and `b` are the same length, prefer `Primary`.
676+
(a.1.len(), !a.1.is_primary).cmp(&(b.1.len(), !b.1.is_primary)).reverse()
677677
});
678678

679679
// Write the underlines.

‎src/librustc_metadata/cstore_impl.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use syntax::attr;
2929
use syntax::source_map;
3030
use syntax::edition::Edition;
3131
use syntax::parse::source_file_to_stream;
32+
use syntax::parse::parser::emit_unclosed_delims;
3233
use syntax::symbol::Symbol;
3334
use syntax_pos::{Span, NO_EXPANSION, FileName};
3435
use rustc_data_structures::bit_set::BitSet;
@@ -436,7 +437,8 @@ impl cstore::CStore {
436437

437438
let source_file = sess.parse_sess.source_map().new_source_file(source_name, def.body);
438439
let local_span = Span::new(source_file.start_pos, source_file.end_pos, NO_EXPANSION);
439-
let body = source_file_to_stream(&sess.parse_sess, source_file, None);
440+
let (body, errors) = source_file_to_stream(&sess.parse_sess, source_file, None);
441+
emit_unclosed_delims(&errors, &sess.diagnostic());
440442

441443
// Mark the attrs as used
442444
let attrs = data.get_item_attrs(id.index, sess);

‎src/libsyntax/parse/lexer/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ impl Default for TokenAndSpan {
3333
}
3434
}
3535

36+
#[derive(Clone, Debug)]
37+
pub struct UnmatchedBrace {
38+
pub expected_delim: token::DelimToken,
39+
pub found_delim: token::DelimToken,
40+
pub found_span: Span,
41+
pub unclosed_span: Option<Span>,
42+
pub candidate_span: Option<Span>,
43+
}
44+
3645
pub struct StringReader<'a> {
3746
pub sess: &'a ParseSess,
3847
/// The absolute offset within the source_map of the next character to read
@@ -58,6 +67,7 @@ pub struct StringReader<'a> {
5867
span_src_raw: Span,
5968
/// Stack of open delimiters and their spans. Used for error message.
6069
open_braces: Vec<(token::DelimToken, Span)>,
70+
crate unmatched_braces: Vec<UnmatchedBrace>,
6171
/// The type and spans for all braces
6272
///
6373
/// Used only for error recovery when arriving to EOF with mismatched braces.
@@ -222,6 +232,7 @@ impl<'a> StringReader<'a> {
222232
span: syntax_pos::DUMMY_SP,
223233
span_src_raw: syntax_pos::DUMMY_SP,
224234
open_braces: Vec::new(),
235+
unmatched_braces: Vec::new(),
225236
matching_delim_spans: Vec::new(),
226237
override_span,
227238
last_unclosed_found_span: None,

‎src/libsyntax/parse/lexer/tokentrees.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::print::pprust::token_to_string;
2-
use crate::parse::lexer::StringReader;
2+
use crate::parse::lexer::{StringReader, UnmatchedBrace};
33
use crate::parse::{token, PResult};
44
use crate::tokenstream::{DelimSpan, IsJoint::*, TokenStream, TokenTree, TreeAndJoint};
55

@@ -101,38 +101,38 @@ impl<'a> StringReader<'a> {
101101
}
102102
// Incorrect delimiter.
103103
token::CloseDelim(other) => {
104-
let token_str = token_to_string(&self.token);
104+
let mut unclosed_delimiter = None;
105+
let mut candidate = None;
105106
if self.last_unclosed_found_span != Some(self.span) {
106107
// do not complain about the same unclosed delimiter multiple times
107108
self.last_unclosed_found_span = Some(self.span);
108-
let msg = format!("incorrect close delimiter: `{}`", token_str);
109-
let mut err = self.sess.span_diagnostic.struct_span_err(
110-
self.span,
111-
&msg,
112-
);
113-
err.span_label(self.span, "incorrect close delimiter");
114109
// This is a conservative error: only report the last unclosed
115110
// delimiter. The previous unclosed delimiters could actually be
116111
// closed! The parser just hasn't gotten to them yet.
117112
if let Some(&(_, sp)) = self.open_braces.last() {
118-
err.span_label(sp, "un-closed delimiter");
113+
unclosed_delimiter = Some(sp);
119114
};
120115
if let Some(current_padding) = sm.span_to_margin(self.span) {
121116
for (brace, brace_span) in &self.open_braces {
122117
if let Some(padding) = sm.span_to_margin(*brace_span) {
123118
// high likelihood of these two corresponding
124119
if current_padding == padding && brace == &other {
125-
err.span_label(
126-
*brace_span,
127-
"close delimiter possibly meant for this",
128-
);
120+
candidate = Some(*brace_span);
129121
}
130122
}
131123
}
132124
}
133-
err.emit();
125+
let (tok, _) = self.open_braces.pop().unwrap();
126+
self.unmatched_braces.push(UnmatchedBrace {
127+
expected_delim: tok,
128+
found_delim: other,
129+
found_span: self.span,
130+
unclosed_span: unclosed_delimiter,
131+
candidate_span: candidate,
132+
});
133+
} else {
134+
self.open_braces.pop();
134135
}
135-
self.open_braces.pop().unwrap();
136136

137137
// If the incorrect delimiter matches an earlier opening
138138
// delimiter, then don't consume it (it can be used to

‎src/libsyntax/parse/mod.rs

+41-17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::parse::parser::Parser;
99
use crate::symbol::Symbol;
1010
use crate::tokenstream::{TokenStream, TokenTree};
1111
use crate::diagnostics::plugin::ErrorMap;
12+
use crate::print::pprust::token_to_string;
1213

1314
use rustc_data_structures::sync::{Lrc, Lock};
1415
use syntax_pos::{Span, SourceFile, FileName, MultiSpan};
@@ -136,15 +137,17 @@ pub fn parse_crate_attrs_from_source_str(name: FileName, source: String, sess: &
136137
new_parser_from_source_str(sess, name, source).parse_inner_attributes()
137138
}
138139

139-
pub fn parse_stream_from_source_str(name: FileName, source: String, sess: &ParseSess,
140-
override_span: Option<Span>)
141-
-> TokenStream {
140+
pub fn parse_stream_from_source_str(
141+
name: FileName,
142+
source: String,
143+
sess: &ParseSess,
144+
override_span: Option<Span>,
145+
) -> (TokenStream, Vec<lexer::UnmatchedBrace>) {
142146
source_file_to_stream(sess, sess.source_map().new_source_file(name, source), override_span)
143147
}
144148

145149
/// Create a new parser from a source string
146-
pub fn new_parser_from_source_str(sess: &ParseSess, name: FileName, source: String)
147-
-> Parser<'_> {
150+
pub fn new_parser_from_source_str(sess: &ParseSess, name: FileName, source: String) -> Parser<'_> {
148151
panictry_buffer!(&sess.span_diagnostic, maybe_new_parser_from_source_str(sess, name, source))
149152
}
150153

@@ -195,12 +198,14 @@ fn source_file_to_parser(sess: &ParseSess, source_file: Lrc<SourceFile>) -> Pars
195198

196199
/// Given a source_file and config, return a parser. Returns any buffered errors from lexing the
197200
/// initial token stream.
198-
fn maybe_source_file_to_parser(sess: &ParseSess, source_file: Lrc<SourceFile>)
199-
-> Result<Parser<'_>, Vec<Diagnostic>>
200-
{
201+
fn maybe_source_file_to_parser(
202+
sess: &ParseSess,
203+
source_file: Lrc<SourceFile>,
204+
) -> Result<Parser<'_>, Vec<Diagnostic>> {
201205
let end_pos = source_file.end_pos;
202-
let mut parser = stream_to_parser(sess, maybe_file_to_stream(sess, source_file, None)?);
203-
206+
let (stream, unclosed_delims) = maybe_file_to_stream(sess, source_file, None)?;
207+
let mut parser = stream_to_parser(sess, stream);
208+
parser.unclosed_delims = unclosed_delims;
204209
if parser.token == token::Eof && parser.span.is_dummy() {
205210
parser.span = Span::new(end_pos, end_pos, parser.span.ctxt());
206211
}
@@ -247,25 +252,44 @@ fn file_to_source_file(sess: &ParseSess, path: &Path, spanopt: Option<Span>)
247252
}
248253

249254
/// Given a source_file, produce a sequence of token-trees
250-
pub fn source_file_to_stream(sess: &ParseSess,
251-
source_file: Lrc<SourceFile>,
252-
override_span: Option<Span>) -> TokenStream {
255+
pub fn source_file_to_stream(
256+
sess: &ParseSess,
257+
source_file: Lrc<SourceFile>,
258+
override_span: Option<Span>,
259+
) -> (TokenStream, Vec<lexer::UnmatchedBrace>) {
253260
panictry_buffer!(&sess.span_diagnostic, maybe_file_to_stream(sess, source_file, override_span))
254261
}
255262

256263
/// Given a source file, produce a sequence of token-trees. Returns any buffered errors from
257264
/// parsing the token tream.
258-
pub fn maybe_file_to_stream(sess: &ParseSess,
259-
source_file: Lrc<SourceFile>,
260-
override_span: Option<Span>) -> Result<TokenStream, Vec<Diagnostic>> {
265+
pub fn maybe_file_to_stream(
266+
sess: &ParseSess,
267+
source_file: Lrc<SourceFile>,
268+
override_span: Option<Span>,
269+
) -> Result<(TokenStream, Vec<lexer::UnmatchedBrace>), Vec<Diagnostic>> {
261270
let mut srdr = lexer::StringReader::new_or_buffered_errs(sess, source_file, override_span)?;
262271
srdr.real_token();
263272

264273
match srdr.parse_all_token_trees() {
265-
Ok(stream) => Ok(stream),
274+
Ok(stream) => Ok((stream, srdr.unmatched_braces)),
266275
Err(err) => {
267276
let mut buffer = Vec::with_capacity(1);
268277
err.buffer(&mut buffer);
278+
// Not using `emit_unclosed_delims` to use `db.buffer`
279+
for unmatched in srdr.unmatched_braces {
280+
let mut db = sess.span_diagnostic.struct_span_err(unmatched.found_span, &format!(
281+
"incorrect close delimiter: `{}`",
282+
token_to_string(&token::Token::CloseDelim(unmatched.found_delim)),
283+
));
284+
db.span_label(unmatched.found_span, "incorrect close delimiter");
285+
if let Some(sp) = unmatched.candidate_span {
286+
db.span_label(sp, "close delimiter possibly meant for this");
287+
}
288+
if let Some(sp) = unmatched.unclosed_span {
289+
db.span_label(sp, "un-closed delimiter");
290+
}
291+
db.buffer(&mut buffer);
292+
}
269293
Err(buffer)
270294
}
271295
}

‎src/libsyntax/parse/parser.rs

+203-64
Large diffs are not rendered by default.

‎src/libsyntax/parse/token.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::print::pprust;
1010
use crate::ptr::P;
1111
use crate::symbol::keywords;
1212
use crate::syntax::parse::parse_stream_from_source_str;
13+
use crate::syntax::parse::parser::emit_unclosed_delims;
1314
use crate::tokenstream::{self, DelimSpan, TokenStream, TokenTree};
1415

1516
use serialize::{Decodable, Decoder, Encodable, Encoder};
@@ -487,8 +488,8 @@ impl Token {
487488
/// Enables better error recovery when the wrong token is found.
488489
crate fn similar_tokens(&self) -> Option<Vec<Token>> {
489490
match *self {
490-
Comma => Some(vec![Dot, Lt]),
491-
Semi => Some(vec![Colon]),
491+
Comma => Some(vec![Dot, Lt, Semi]),
492+
Semi => Some(vec![Colon, Comma]),
492493
_ => None
493494
}
494495
}
@@ -545,7 +546,10 @@ impl Token {
545546
// FIXME(#43081): Avoid this pretty-print + reparse hack
546547
let source = pprust::token_to_string(self);
547548
let filename = FileName::macro_expansion_source_code(&source);
548-
parse_stream_from_source_str(filename, source, sess, Some(span))
549+
let (tokens, errors) = parse_stream_from_source_str(
550+
filename, source, sess, Some(span));
551+
emit_unclosed_delims(&errors, &sess.span_diagnostic);
552+
tokens
549553
});
550554

551555
// During early phases of the compiler the AST could get modified
@@ -786,12 +790,13 @@ fn prepend_attrs(sess: &ParseSess,
786790
let source = pprust::attr_to_string(attr);
787791
let macro_filename = FileName::macro_expansion_source_code(&source);
788792
if attr.is_sugared_doc {
789-
let stream = parse_stream_from_source_str(
793+
let (stream, errors) = parse_stream_from_source_str(
790794
macro_filename,
791795
source,
792796
sess,
793797
Some(span),
794798
);
799+
emit_unclosed_delims(&errors, &sess.span_diagnostic);
795800
builder.push(stream);
796801
continue
797802
}
@@ -808,12 +813,13 @@ fn prepend_attrs(sess: &ParseSess,
808813
// ... and for more complicated paths, fall back to a reparse hack that
809814
// should eventually be removed.
810815
} else {
811-
let stream = parse_stream_from_source_str(
816+
let (stream, errors) = parse_stream_from_source_str(
812817
macro_filename,
813818
source,
814819
sess,
815820
Some(span),
816821
);
822+
emit_unclosed_delims(&errors, &sess.span_diagnostic);
817823
brackets.push(stream);
818824
}
819825

‎src/libsyntax/util/parser_testing.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ use std::path::PathBuf;
1212
/// Map a string to tts, using a made-up filename:
1313
pub fn string_to_stream(source_str: String) -> TokenStream {
1414
let ps = ParseSess::new(FilePathMapping::empty());
15-
source_file_to_stream(&ps, ps.source_map()
16-
.new_source_file(PathBuf::from("bogofile").into(), source_str), None)
15+
source_file_to_stream(
16+
&ps,
17+
ps.source_map().new_source_file(PathBuf::from("bogofile").into(),
18+
source_str,
19+
), None).0
1720
}
1821

1922
/// Map string to parser (via tts)

‎src/libsyntax_ext/proc_macro_server.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use syntax::ast;
1212
use syntax::ext::base::ExtCtxt;
1313
use syntax::parse::lexer::comments;
1414
use syntax::parse::{self, token, ParseSess};
15+
use syntax::parse::parser::emit_unclosed_delims;
1516
use syntax::tokenstream::{self, DelimSpan, IsJoint::*, TokenStream, TreeAndJoint};
1617
use syntax_pos::hygiene::{SyntaxContext, Transparency};
1718
use syntax_pos::symbol::{keywords, Symbol};
@@ -409,12 +410,14 @@ impl server::TokenStream for Rustc<'_> {
409410
stream.is_empty()
410411
}
411412
fn from_str(&mut self, src: &str) -> Self::TokenStream {
412-
parse::parse_stream_from_source_str(
413+
let (tokens, errors) = parse::parse_stream_from_source_str(
413414
FileName::proc_macro_source_code(src.clone()),
414415
src.to_string(),
415416
self.sess,
416417
Some(self.call_site),
417-
)
418+
);
419+
emit_unclosed_delims(&errors, &self.sess.span_diagnostic);
420+
tokens
418421
}
419422
fn to_string(&mut self, stream: &Self::TokenStream) -> String {
420423
stream.to_string()

‎src/test/ui/issues/issue-52891.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ LL | use issue_52891::a;
9090
LL | m,
9191
| ______-
9292
LL | | a}; //~ ERROR `a` is defined multiple times
93-
| | -
93+
| | ^
9494
| | |
9595
| |_____`a` reimported here
9696
| help: remove unnecessary import

‎src/test/ui/parser-recovery-2.stderr

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
error: unexpected token: `;`
2+
--> $DIR/parser-recovery-2.rs:12:15
3+
|
4+
LL | let x = y.; //~ ERROR unexpected token
5+
| ^
6+
17
error: incorrect close delimiter: `)`
28
--> $DIR/parser-recovery-2.rs:8:5
39
|
@@ -7,12 +13,6 @@ LL | let x = foo(); //~ ERROR cannot find function `foo` in this scope
713
LL | ) //~ ERROR incorrect close delimiter: `)`
814
| ^ incorrect close delimiter
915

10-
error: unexpected token: `;`
11-
--> $DIR/parser-recovery-2.rs:12:15
12-
|
13-
LL | let x = y.; //~ ERROR unexpected token
14-
| ^
15-
1616
error[E0425]: cannot find function `foo` in this scope
1717
--> $DIR/parser-recovery-2.rs:7:17
1818
|

‎src/test/ui/parser/issue-10636-2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub fn trace_option(option: Option<isize>) {
55
option.map(|some| 42;
66
//~^ ERROR: expected one of
77

8-
} //~ ERROR: incorrect close delimiter
8+
}
99
//~^ ERROR: expected expression, found `)`
1010

1111
fn main() {}
+6-14
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
1-
error: incorrect close delimiter: `}`
2-
--> $DIR/issue-10636-2.rs:8:1
3-
|
4-
LL | pub fn trace_option(option: Option<isize>) {
5-
| - close delimiter possibly meant for this
6-
LL | option.map(|some| 42;
7-
| - un-closed delimiter
8-
...
9-
LL | } //~ ERROR: incorrect close delimiter
10-
| ^ incorrect close delimiter
11-
121
error: expected one of `)`, `,`, `.`, `?`, or an operator, found `;`
132
--> $DIR/issue-10636-2.rs:5:25
143
|
154
LL | option.map(|some| 42;
16-
| ^ expected one of `)`, `,`, `.`, `?`, or an operator here
5+
| - ^
6+
| | |
7+
| | help: `)` may belong here
8+
| unclosed delimiter
179

1810
error: expected expression, found `)`
1911
--> $DIR/issue-10636-2.rs:8:1
2012
|
21-
LL | } //~ ERROR: incorrect close delimiter
13+
LL | }
2214
| ^ expected expression
2315

24-
error: aborting due to 3 previous errors
16+
error: aborting due to 2 previous errors
2517

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
error: unexpected close delimiter: `}`
2+
--> $DIR/macro-mismatched-delim-paren-brace.rs:5:1
3+
|
4+
LL | } //~ ERROR unexpected close delimiter: `}`
5+
| ^ unexpected close delimiter
6+
17
error: incorrect close delimiter: `}`
28
--> $DIR/macro-mismatched-delim-paren-brace.rs:4:5
39
|
@@ -7,11 +13,5 @@ LL | bar, "baz", 1, 2.0
713
LL | } //~ ERROR incorrect close delimiter
814
| ^ incorrect close delimiter
915

10-
error: unexpected close delimiter: `}`
11-
--> $DIR/macro-mismatched-delim-paren-brace.rs:5:1
12-
|
13-
LL | } //~ ERROR unexpected close delimiter: `}`
14-
| ^ unexpected close delimiter
15-
1616
error: aborting due to 2 previous errors
1717

‎src/test/ui/resolve/token-error-correct-3.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub mod raw {
1717
//~| expected type `()`
1818
//~| found type `std::result::Result<bool, std::io::Error>`
1919
//~| expected one of
20-
} else { //~ ERROR: incorrect close delimiter: `}`
20+
} else {
2121
//~^ ERROR: expected one of
2222
//~| unexpected token
2323
Ok(false);
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
1-
error: incorrect close delimiter: `}`
2-
--> $DIR/token-error-correct-3.rs:20:9
3-
|
4-
LL | if !is_directory(path.as_ref()) { //~ ERROR: cannot find function `is_directory`
5-
| - close delimiter possibly meant for this
6-
LL | callback(path.as_ref(); //~ ERROR expected one of
7-
| - un-closed delimiter
8-
...
9-
LL | } else { //~ ERROR: incorrect close delimiter: `}`
10-
| ^ incorrect close delimiter
11-
121
error: expected one of `)`, `,`, `.`, `?`, or an operator, found `;`
132
--> $DIR/token-error-correct-3.rs:14:35
143
|
154
LL | callback(path.as_ref(); //~ ERROR expected one of
16-
| ^ expected one of `)`, `,`, `.`, `?`, or an operator here
5+
| - ^
6+
| | |
7+
| | help: `)` may belong here
8+
| unclosed delimiter
179

1810
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `)`
1911
--> $DIR/token-error-correct-3.rs:20:9
2012
|
2113
LL | fs::create_dir_all(path.as_ref()).map(|()| true) //~ ERROR: mismatched types
2214
| - expected one of `.`, `;`, `?`, `}`, or an operator here
2315
...
24-
LL | } else { //~ ERROR: incorrect close delimiter: `}`
16+
LL | } else {
2517
| ^ unexpected token
2618

2719
error[E0425]: cannot find function `is_directory` in this scope
@@ -41,7 +33,7 @@ LL | fs::create_dir_all(path.as_ref()).map(|()| true) //~ ERROR: mis
4133
= note: expected type `()`
4234
found type `std::result::Result<bool, std::io::Error>`
4335

44-
error: aborting due to 5 previous errors
36+
error: aborting due to 4 previous errors
4537

4638
Some errors occurred: E0308, E0425.
4739
For more information about an error, try `rustc --explain E0308`.

‎src/test/ui/resolve/token-error-correct.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
fn main() {
44
foo(bar(;
5-
//~^ ERROR: expected expression, found `;`
5+
//~^ ERROR cannot find function `bar` in this scope
66
}
77
//~^ ERROR: incorrect close delimiter: `}`
8+
9+
fn foo(_: usize) {}

‎src/test/ui/resolve/token-error-correct.stderr

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ LL | fn main() {
55
| - close delimiter possibly meant for this
66
LL | foo(bar(;
77
| - un-closed delimiter
8-
LL | //~^ ERROR: expected expression, found `;`
8+
LL | //~^ ERROR cannot find function `bar` in this scope
99
LL | }
1010
| ^ incorrect close delimiter
1111

12-
error: expected expression, found `;`
13-
--> $DIR/token-error-correct.rs:4:13
12+
error[E0425]: cannot find function `bar` in this scope
13+
--> $DIR/token-error-correct.rs:4:9
1414
|
1515
LL | foo(bar(;
16-
| ^ expected expression
16+
| ^^^ not found in this scope
1717

1818
error: aborting due to 2 previous errors
1919

20+
For more information about this error, try `rustc --explain E0425`.

0 commit comments

Comments
 (0)
Please sign in to comment.