Skip to content

Commit 7451cd8

Browse files
committed
Deduplicate mismatched delimiter errors
Delay unmatched delimiter errors until after the parser has run to deduplicate them when parsing and attempt recovering intelligently.
1 parent 825f355 commit 7451cd8

17 files changed

+335
-158
lines changed

src/librustc_errors/emitter.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -674,8 +674,8 @@ impl EmitterWriter {
674674
// | | something about `foo`
675675
// | something about `fn foo()`
676676
annotations_position.sort_by(|a, b| {
677-
// Decreasing order
678-
a.1.len().cmp(&b.1.len()).reverse()
677+
// Decreasing order. When `a` and `b` are the same length, prefer `Primary`.
678+
(a.1.len(), !a.1.is_primary).cmp(&(b.1.len(), !b.1.is_primary)).reverse()
679679
});
680680

681681
// Write the underlines.

src/librustc_metadata/cstore_impl.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,13 @@ impl cstore::CStore {
436436

437437
let source_file = sess.parse_sess.source_map().new_source_file(source_name, def.body);
438438
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);
439+
let (body, errors) = source_file_to_stream(&sess.parse_sess, source_file, None);
440+
for err in errors {
441+
sess.struct_span_err(
442+
err.found_span,
443+
"unclosed delimiter cstore",
444+
).emit();
445+
}
440446

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

src/libsyntax/parse/lexer/mod.rs

Lines changed: 11 additions & 0 deletions
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

Lines changed: 15 additions & 15 deletions
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

Lines changed: 40 additions & 17 deletions
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,43 @@ 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+
for unmatched in srdr.unmatched_braces {
279+
let mut db = sess.span_diagnostic.struct_span_err(unmatched.found_span, &format!(
280+
"incorrect close delimiter: `{}`",
281+
token_to_string(&token::Token::CloseDelim(unmatched.found_delim)),
282+
));
283+
db.span_label(unmatched.found_span, "incorrect close delimiter");
284+
if let Some(sp) = unmatched.candidate_span {
285+
db.span_label(sp, "close delimiter possibly meant for this");
286+
}
287+
if let Some(sp) = unmatched.unclosed_span {
288+
db.span_label(sp, "un-closed delimiter");
289+
}
290+
db.buffer(&mut buffer);
291+
}
269292
Err(buffer)
270293
}
271294
}

0 commit comments

Comments
 (0)