Skip to content

Commit e3048c7

Browse files
authored
Rollup merge of #104012 - chenyukang:yukang/fix-103882-deli-indentation, r=petrochenkov
Improve unexpected close and mismatch delimiter hint in TokenTreesReader Fixes #103882 Fixes #68987 Fixes #69259 The inner indentation mismatching will be covered by outer block, the new added function `report_error_prone_delim_block` will find out the error prone candidates for reporting.
2 parents 226b249 + cd23323 commit e3048c7

24 files changed

+408
-106
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use super::UnmatchedBrace;
2+
use rustc_ast::token::Delimiter;
3+
use rustc_errors::Diagnostic;
4+
use rustc_span::source_map::SourceMap;
5+
use rustc_span::Span;
6+
7+
#[derive(Default)]
8+
pub struct TokenTreeDiagInfo {
9+
/// Stack of open delimiters and their spans. Used for error message.
10+
pub open_braces: Vec<(Delimiter, Span)>,
11+
pub unmatched_braces: Vec<UnmatchedBrace>,
12+
13+
/// Used only for error recovery when arriving to EOF with mismatched braces.
14+
pub last_unclosed_found_span: Option<Span>,
15+
16+
/// Collect empty block spans that might have been auto-inserted by editors.
17+
pub empty_block_spans: Vec<Span>,
18+
19+
/// Collect the spans of braces (Open, Close). Used only
20+
/// for detecting if blocks are empty and only braces.
21+
pub matching_block_spans: Vec<(Span, Span)>,
22+
}
23+
24+
pub fn same_identation_level(sm: &SourceMap, open_sp: Span, close_sp: Span) -> bool {
25+
match (sm.span_to_margin(open_sp), sm.span_to_margin(close_sp)) {
26+
(Some(open_padding), Some(close_padding)) => open_padding == close_padding,
27+
_ => false,
28+
}
29+
}
30+
31+
// When we get a `)` or `]` for `{`, we should emit help message here
32+
// it's more friendly compared to report `unmatched error` in later phase
33+
pub fn report_missing_open_delim(
34+
err: &mut Diagnostic,
35+
unmatched_braces: &[UnmatchedBrace],
36+
) -> bool {
37+
let mut reported_missing_open = false;
38+
for unmatch_brace in unmatched_braces.iter() {
39+
if let Some(delim) = unmatch_brace.found_delim
40+
&& matches!(delim, Delimiter::Parenthesis | Delimiter::Bracket)
41+
{
42+
let missed_open = match delim {
43+
Delimiter::Parenthesis => "(",
44+
Delimiter::Bracket => "[",
45+
_ => unreachable!(),
46+
};
47+
err.span_label(
48+
unmatch_brace.found_span.shrink_to_lo(),
49+
format!("missing open `{}` for this delimiter", missed_open),
50+
);
51+
reported_missing_open = true;
52+
}
53+
}
54+
reported_missing_open
55+
}
56+
57+
pub fn report_suspicious_mismatch_block(
58+
err: &mut Diagnostic,
59+
diag_info: &TokenTreeDiagInfo,
60+
sm: &SourceMap,
61+
delim: Delimiter,
62+
) {
63+
if report_missing_open_delim(err, &diag_info.unmatched_braces) {
64+
return;
65+
}
66+
67+
let mut matched_spans: Vec<(Span, bool)> = diag_info
68+
.matching_block_spans
69+
.iter()
70+
.map(|&(open, close)| (open.with_hi(close.lo()), same_identation_level(sm, open, close)))
71+
.collect();
72+
73+
// sort by `lo`, so the large block spans in the front
74+
matched_spans.sort_by(|a, b| a.0.lo().cmp(&b.0.lo()));
75+
76+
// We use larger block whose identation is well to cover those inner mismatched blocks
77+
// O(N^2) here, but we are on error reporting path, so it is fine
78+
for i in 0..matched_spans.len() {
79+
let (block_span, same_ident) = matched_spans[i];
80+
if same_ident {
81+
for j in i + 1..matched_spans.len() {
82+
let (inner_block, inner_same_ident) = matched_spans[j];
83+
if block_span.contains(inner_block) && !inner_same_ident {
84+
matched_spans[j] = (inner_block, true);
85+
}
86+
}
87+
}
88+
}
89+
90+
// Find the inner-most span candidate for final report
91+
let candidate_span =
92+
matched_spans.into_iter().rev().find(|&(_, same_ident)| !same_ident).map(|(span, _)| span);
93+
94+
if let Some(block_span) = candidate_span {
95+
err.span_label(block_span.shrink_to_lo(), "this delimiter might not be properly closed...");
96+
err.span_label(
97+
block_span.shrink_to_hi(),
98+
"...as it matches this but it has different indentation",
99+
);
100+
101+
// If there is a empty block in the mismatched span, note it
102+
if delim == Delimiter::Brace {
103+
for span in diag_info.empty_block_spans.iter() {
104+
if block_span.contains(*span) {
105+
err.span_label(*span, "block is empty, you might have not meant to close it");
106+
break;
107+
}
108+
}
109+
}
110+
} else {
111+
// If there is no suspicious span, give the last properly closed block may help
112+
if let Some(parent) = diag_info.matching_block_spans.last()
113+
&& diag_info.open_braces.last().is_none()
114+
&& diag_info.empty_block_spans.iter().all(|&sp| sp != parent.0.to(parent.1)) {
115+
err.span_label(parent.0, "this opening brace...");
116+
err.span_label(parent.1, "...matches this closing brace");
117+
}
118+
}
119+
}

compiler/rustc_parse/src/lexer/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustc_session::parse::ParseSess;
1717
use rustc_span::symbol::{sym, Symbol};
1818
use rustc_span::{edition::Edition, BytePos, Pos, Span};
1919

20+
mod diagnostics;
2021
mod tokentrees;
2122
mod unescape_error_reporting;
2223
mod unicode_chars;

compiler/rustc_parse/src/lexer/tokentrees.rs

+42-87
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
1+
use super::diagnostics::report_suspicious_mismatch_block;
2+
use super::diagnostics::same_identation_level;
3+
use super::diagnostics::TokenTreeDiagInfo;
14
use super::{StringReader, UnmatchedBrace};
25
use rustc_ast::token::{self, Delimiter, Token};
36
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
47
use rustc_ast_pretty::pprust::token_to_string;
5-
use rustc_data_structures::fx::FxHashMap;
68
use rustc_errors::{PErr, PResult};
7-
use rustc_span::Span;
89

910
pub(super) struct TokenTreesReader<'a> {
1011
string_reader: StringReader<'a>,
1112
/// The "next" token, which has been obtained from the `StringReader` but
1213
/// not yet handled by the `TokenTreesReader`.
1314
token: Token,
14-
/// Stack of open delimiters and their spans. Used for error message.
15-
open_braces: Vec<(Delimiter, Span)>,
16-
unmatched_braces: Vec<UnmatchedBrace>,
17-
/// The type and spans for all braces
18-
///
19-
/// Used only for error recovery when arriving to EOF with mismatched braces.
20-
matching_delim_spans: Vec<(Delimiter, Span, Span)>,
21-
last_unclosed_found_span: Option<Span>,
22-
/// Collect empty block spans that might have been auto-inserted by editors.
23-
last_delim_empty_block_spans: FxHashMap<Delimiter, Span>,
24-
/// Collect the spans of braces (Open, Close). Used only
25-
/// for detecting if blocks are empty and only braces.
26-
matching_block_spans: Vec<(Span, Span)>,
15+
diag_info: TokenTreeDiagInfo,
2716
}
2817

2918
impl<'a> TokenTreesReader<'a> {
@@ -33,15 +22,10 @@ impl<'a> TokenTreesReader<'a> {
3322
let mut tt_reader = TokenTreesReader {
3423
string_reader,
3524
token: Token::dummy(),
36-
open_braces: Vec::new(),
37-
unmatched_braces: Vec::new(),
38-
matching_delim_spans: Vec::new(),
39-
last_unclosed_found_span: None,
40-
last_delim_empty_block_spans: FxHashMap::default(),
41-
matching_block_spans: Vec::new(),
25+
diag_info: TokenTreeDiagInfo::default(),
4226
};
4327
let res = tt_reader.parse_token_trees(/* is_delimited */ false);
44-
(res, tt_reader.unmatched_braces)
28+
(res, tt_reader.diag_info.unmatched_braces)
4529
}
4630

4731
// Parse a stream of tokens into a list of `TokenTree`s.
@@ -92,9 +76,9 @@ impl<'a> TokenTreesReader<'a> {
9276
fn eof_err(&mut self) -> PErr<'a> {
9377
let msg = "this file contains an unclosed delimiter";
9478
let mut err = self.string_reader.sess.span_diagnostic.struct_span_err(self.token.span, msg);
95-
for &(_, sp) in &self.open_braces {
79+
for &(_, sp) in &self.diag_info.open_braces {
9680
err.span_label(sp, "unclosed delimiter");
97-
self.unmatched_braces.push(UnmatchedBrace {
81+
self.diag_info.unmatched_braces.push(UnmatchedBrace {
9882
expected_delim: Delimiter::Brace,
9983
found_delim: None,
10084
found_span: self.token.span,
@@ -103,23 +87,13 @@ impl<'a> TokenTreesReader<'a> {
10387
});
10488
}
10589

106-
if let Some((delim, _)) = self.open_braces.last() {
107-
if let Some((_, open_sp, close_sp)) =
108-
self.matching_delim_spans.iter().find(|(d, open_sp, close_sp)| {
109-
let sm = self.string_reader.sess.source_map();
110-
if let Some(close_padding) = sm.span_to_margin(*close_sp) {
111-
if let Some(open_padding) = sm.span_to_margin(*open_sp) {
112-
return delim == d && close_padding != open_padding;
113-
}
114-
}
115-
false
116-
})
117-
// these are in reverse order as they get inserted on close, but
118-
{
119-
// we want the last open/first close
120-
err.span_label(*open_sp, "this delimiter might not be properly closed...");
121-
err.span_label(*close_sp, "...as it matches this but it has different indentation");
122-
}
90+
if let Some((delim, _)) = self.diag_info.open_braces.last() {
91+
report_suspicious_mismatch_block(
92+
&mut err,
93+
&self.diag_info,
94+
&self.string_reader.sess.source_map(),
95+
*delim,
96+
)
12397
}
12498
err
12599
}
@@ -128,7 +102,7 @@ impl<'a> TokenTreesReader<'a> {
128102
// The span for beginning of the delimited section
129103
let pre_span = self.token.span;
130104

131-
self.open_braces.push((open_delim, self.token.span));
105+
self.diag_info.open_braces.push((open_delim, self.token.span));
132106

133107
// Parse the token trees within the delimiters.
134108
// We stop at any delimiter so we can try to recover if the user
@@ -137,35 +111,29 @@ impl<'a> TokenTreesReader<'a> {
137111

138112
// Expand to cover the entire delimited token tree
139113
let delim_span = DelimSpan::from_pair(pre_span, self.token.span);
114+
let sm = self.string_reader.sess.source_map();
140115

141116
match self.token.kind {
142117
// Correct delimiter.
143118
token::CloseDelim(close_delim) if close_delim == open_delim => {
144-
let (open_brace, open_brace_span) = self.open_braces.pop().unwrap();
119+
let (open_brace, open_brace_span) = self.diag_info.open_braces.pop().unwrap();
145120
let close_brace_span = self.token.span;
146121

147-
if tts.is_empty() {
122+
if tts.is_empty() && close_delim == Delimiter::Brace {
148123
let empty_block_span = open_brace_span.to(close_brace_span);
149-
let sm = self.string_reader.sess.source_map();
150124
if !sm.is_multiline(empty_block_span) {
151125
// Only track if the block is in the form of `{}`, otherwise it is
152126
// likely that it was written on purpose.
153-
self.last_delim_empty_block_spans.insert(open_delim, empty_block_span);
127+
self.diag_info.empty_block_spans.push(empty_block_span);
154128
}
155129
}
156130

157-
//only add braces
131+
// only add braces
158132
if let (Delimiter::Brace, Delimiter::Brace) = (open_brace, open_delim) {
159-
self.matching_block_spans.push((open_brace_span, close_brace_span));
133+
// Add all the matching spans, we will sort by span later
134+
self.diag_info.matching_block_spans.push((open_brace_span, close_brace_span));
160135
}
161136

162-
if self.open_braces.is_empty() {
163-
// Clear up these spans to avoid suggesting them as we've found
164-
// properly matched delimiters so far for an entire block.
165-
self.matching_delim_spans.clear();
166-
} else {
167-
self.matching_delim_spans.push((open_brace, open_brace_span, close_brace_span));
168-
}
169137
// Move past the closing delimiter.
170138
self.token = self.string_reader.next_token().0;
171139
}
@@ -174,36 +142,33 @@ impl<'a> TokenTreesReader<'a> {
174142
let mut unclosed_delimiter = None;
175143
let mut candidate = None;
176144

177-
if self.last_unclosed_found_span != Some(self.token.span) {
145+
if self.diag_info.last_unclosed_found_span != Some(self.token.span) {
178146
// do not complain about the same unclosed delimiter multiple times
179-
self.last_unclosed_found_span = Some(self.token.span);
147+
self.diag_info.last_unclosed_found_span = Some(self.token.span);
180148
// This is a conservative error: only report the last unclosed
181149
// delimiter. The previous unclosed delimiters could actually be
182150
// closed! The parser just hasn't gotten to them yet.
183-
if let Some(&(_, sp)) = self.open_braces.last() {
151+
if let Some(&(_, sp)) = self.diag_info.open_braces.last() {
184152
unclosed_delimiter = Some(sp);
185153
};
186-
let sm = self.string_reader.sess.source_map();
187-
if let Some(current_padding) = sm.span_to_margin(self.token.span) {
188-
for (brace, brace_span) in &self.open_braces {
189-
if let Some(padding) = sm.span_to_margin(*brace_span) {
190-
// high likelihood of these two corresponding
191-
if current_padding == padding && brace == &close_delim {
192-
candidate = Some(*brace_span);
193-
}
194-
}
154+
for (brace, brace_span) in &self.diag_info.open_braces {
155+
if same_identation_level(&sm, self.token.span, *brace_span)
156+
&& brace == &close_delim
157+
{
158+
// high likelihood of these two corresponding
159+
candidate = Some(*brace_span);
195160
}
196161
}
197-
let (tok, _) = self.open_braces.pop().unwrap();
198-
self.unmatched_braces.push(UnmatchedBrace {
162+
let (tok, _) = self.diag_info.open_braces.pop().unwrap();
163+
self.diag_info.unmatched_braces.push(UnmatchedBrace {
199164
expected_delim: tok,
200165
found_delim: Some(close_delim),
201166
found_span: self.token.span,
202167
unclosed_span: unclosed_delimiter,
203168
candidate_span: candidate,
204169
});
205170
} else {
206-
self.open_braces.pop();
171+
self.diag_info.open_braces.pop();
207172
}
208173

209174
// If the incorrect delimiter matches an earlier opening
@@ -213,7 +178,7 @@ impl<'a> TokenTreesReader<'a> {
213178
// fn foo() {
214179
// bar(baz(
215180
// } // Incorrect delimiter but matches the earlier `{`
216-
if !self.open_braces.iter().any(|&(b, _)| b == close_delim) {
181+
if !self.diag_info.open_braces.iter().any(|&(b, _)| b == close_delim) {
217182
self.token = self.string_reader.next_token().0;
218183
}
219184
}
@@ -236,22 +201,12 @@ impl<'a> TokenTreesReader<'a> {
236201
let mut err =
237202
self.string_reader.sess.span_diagnostic.struct_span_err(self.token.span, &msg);
238203

239-
// Braces are added at the end, so the last element is the biggest block
240-
if let Some(parent) = self.matching_block_spans.last() {
241-
if let Some(span) = self.last_delim_empty_block_spans.remove(&delim) {
242-
// Check if the (empty block) is in the last properly closed block
243-
if (parent.0.to(parent.1)).contains(span) {
244-
err.span_label(span, "block is empty, you might have not meant to close it");
245-
} else {
246-
err.span_label(parent.0, "this opening brace...");
247-
err.span_label(parent.1, "...matches this closing brace");
248-
}
249-
} else {
250-
err.span_label(parent.0, "this opening brace...");
251-
err.span_label(parent.1, "...matches this closing brace");
252-
}
253-
}
254-
204+
report_suspicious_mismatch_block(
205+
&mut err,
206+
&self.diag_info,
207+
&self.string_reader.sess.source_map(),
208+
delim,
209+
);
255210
err.span_label(self.token.span, "unexpected closing delimiter");
256211
err
257212
}

tests/ui/parser/deli-ident-issue-1.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![feature(let_chains)]
2+
trait Demo {}
3+
4+
impl dyn Demo {
5+
pub fn report(&self) -> u32 {
6+
let sum = |a: u32,
7+
b: u32,
8+
c: u32| {
9+
a + b + c
10+
};
11+
sum(1, 2, 3)
12+
}
13+
14+
fn check(&self, val: Option<u32>, num: Option<u32>) {
15+
if let Some(b) = val
16+
&& let Some(c) = num {
17+
&& b == c {
18+
//~^ ERROR expected struct
19+
//~| ERROR mismatched types
20+
}
21+
}
22+
}
23+
24+
fn main() { } //~ ERROR this file contains an unclosed delimiter

0 commit comments

Comments
 (0)