Skip to content

Commit

Permalink
TokenTreesReader now can find the correct mismatch delimiter pairs
Browse files Browse the repository at this point in the history
  • Loading branch information
chenyukang committed Nov 7, 2022
1 parent 6718ea1 commit 1a8e69a
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 62 deletions.
115 changes: 69 additions & 46 deletions compiler/rustc_parse/src/lexer/tokentrees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rustc_ast::token::{self, Delimiter, Token};
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
use rustc_ast_pretty::pprust::token_to_string;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{PErr, PResult};
use rustc_errors::{Diagnostic, PErr, PResult};
use rustc_span::Span;

pub(super) struct TokenTreesReader<'a> {
Expand Down Expand Up @@ -104,22 +104,7 @@ impl<'a> TokenTreesReader<'a> {
}

if let Some((delim, _)) = self.open_braces.last() {
if let Some((_, open_sp, close_sp)) =
self.matching_delim_spans.iter().find(|(d, open_sp, close_sp)| {
let sm = self.string_reader.sess.source_map();
if let Some(close_padding) = sm.span_to_margin(*close_sp) {
if let Some(open_padding) = sm.span_to_margin(*open_sp) {
return delim == d && close_padding != open_padding;
}
}
false
})
// these are in reverse order as they get inserted on close, but
{
// we want the last open/first close
err.span_label(*open_sp, "this delimiter might not be properly closed...");
err.span_label(*close_sp, "...as it matches this but it has different indentation");
}
self.report_error_prone_delim_block(*delim, &mut err);
}
err
}
Expand Down Expand Up @@ -157,15 +142,11 @@ impl<'a> TokenTreesReader<'a> {
//only add braces
if let (Delimiter::Brace, Delimiter::Brace) = (open_brace, open_delim) {
self.matching_block_spans.push((open_brace_span, close_brace_span));
}

if self.open_braces.is_empty() {
// Clear up these spans to avoid suggesting them as we've found
// properly matched delimiters so far for an entire block.
self.matching_delim_spans.clear();
} else {
// Add all the matching spans, we will sort by span later
self.matching_delim_spans.push((open_brace, open_brace_span, close_brace_span));
}

// Move past the closing delimiter.
self.token = self.string_reader.next_token().0;
}
Expand All @@ -183,15 +164,12 @@ impl<'a> TokenTreesReader<'a> {
if let Some(&(_, sp)) = self.open_braces.last() {
unclosed_delimiter = Some(sp);
};
let sm = self.string_reader.sess.source_map();
if let Some(current_padding) = sm.span_to_margin(self.token.span) {
for (brace, brace_span) in &self.open_braces {
if let Some(padding) = sm.span_to_margin(*brace_span) {
// high likelihood of these two corresponding
if current_padding == padding && brace == &close_delim {
candidate = Some(*brace_span);
}
}
for (brace, brace_span) in &self.open_braces {
if self.same_identation_level(self.token.span, *brace_span)
&& brace == &close_delim
{
// high likelihood of these two corresponding
candidate = Some(*brace_span);
}
}
let (tok, _) = self.open_braces.pop().unwrap();
Expand Down Expand Up @@ -236,23 +214,68 @@ impl<'a> TokenTreesReader<'a> {
let mut err =
self.string_reader.sess.span_diagnostic.struct_span_err(self.token.span, &msg);

// Braces are added at the end, so the last element is the biggest block
if let Some(parent) = self.matching_block_spans.last() {
if let Some(span) = self.last_delim_empty_block_spans.remove(&delim) {
// Check if the (empty block) is in the last properly closed block
if (parent.0.to(parent.1)).contains(span) {
err.span_label(span, "block is empty, you might have not meant to close it");
} else {
err.span_label(parent.0, "this opening brace...");
err.span_label(parent.1, "...matches this closing brace");
self.report_error_prone_delim_block(delim, &mut err);
err.span_label(self.token.span, "unexpected closing delimiter");
err
}

fn same_identation_level(&self, open_sp: Span, close_sp: Span) -> bool {
let sm = self.string_reader.sess.source_map();
if let (Some(open_padding), Some(close_padding)) =
(sm.span_to_margin(open_sp), sm.span_to_margin(close_sp))
{
open_padding == close_padding
} else {
false
}
}

fn report_error_prone_delim_block(&self, delim: Delimiter, err: &mut Diagnostic) {
let mut matched_spans = vec![];
let mut candidate_span = None;

for &(d, open_sp, close_sp) in &self.matching_delim_spans {
if d == delim {
let block_span = open_sp.with_hi(close_sp.lo());
let same_ident = self.same_identation_level(open_sp, close_sp);
matched_spans.push((block_span, same_ident));
}
}

// sort by `lo`, so the large block spans in the front
matched_spans.sort_by(|a, b| a.0.lo().cmp(&b.0.lo()));

// We use larger block whose identation is well to cover those innert blocks
// O(N^2) here, but we are on error reporting path, so it is fine
for i in 0..matched_spans.len() {
let (block_span, same_ident) = matched_spans[i];
if same_ident {
for j in i + 1..matched_spans.len() {
let (inner_block, innert_same_ident) = matched_spans[j];
if block_span.contains(inner_block) && !innert_same_ident {
matched_spans[j] = (inner_block, true);
}
}
} else {
err.span_label(parent.0, "this opening brace...");
err.span_label(parent.1, "...matches this closing brace");
}
}

err.span_label(self.token.span, "unexpected closing delimiter");
err
// Find the innermost span candidate for final report
for (block_span, same_ident) in matched_spans.into_iter().rev() {
if !same_ident {
candidate_span = Some(block_span);
break;
}
}

if let Some(block_span) = candidate_span {
err.span_label(
block_span.shrink_to_lo(),
"this delimiter might not be properly closed...",
);
err.span_label(
block_span.shrink_to_hi(),
"...as it matches this but it has different indentation",
);
}
}
}
27 changes: 27 additions & 0 deletions src/test/ui/parser/deli-ident-issue-1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ignore-tidy-trailing-newlines
//
// error-pattern: this file contains an unclosed delimiter
#![feature(let_chains)]
trait Demo {}

impl dyn Demo {
pub fn report(&self) -> u32 {
let sum = |a: u32,
b: u32,
c: u32| {
a + b + c
};
sum(1, 2, 3)
}

fn check(&self, val: Option<u32>, num: Option<u32>) {
if let Some(b) = val
&& let Some(c) = num {
&& b == c {
//~^ ERROR expected struct
//~| ERROR mismatched types
}
}
}

fn main() { } //~ ERROR this file contains an unclosed delimiter
37 changes: 37 additions & 0 deletions src/test/ui/parser/deli-ident-issue-1.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
error: this file contains an unclosed delimiter
--> $DIR/deli-ident-issue-1.rs:27:65
|
LL | impl dyn Demo {
| - unclosed delimiter
...
LL | && let Some(c) = num {
| - this delimiter might not be properly closed...
...
LL | }
| - ...as it matches this but it has different indentation
...
LL | fn main() { }
| ^

error[E0574]: expected struct, variant or union type, found local variable `c`
--> $DIR/deli-ident-issue-1.rs:20:17
|
LL | && b == c {
| ^ not a struct, variant or union type

error[E0308]: mismatched types
--> $DIR/deli-ident-issue-1.rs:20:9
|
LL | fn check(&self, val: Option<u32>, num: Option<u32>) {
| - expected `()` because of default return type
...
LL | / && b == c {
LL | |
LL | |
LL | | }
| |_________^ expected `()`, found `bool`

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0308, E0574.
For more information about an error, try `rustc --explain E0308`.
25 changes: 25 additions & 0 deletions src/test/ui/parser/deli-ident-issue-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ignore-tidy-trailing-newlines
//
// error-pattern: this file contains an unclosed delimiter
#![feature(let_chains)]
trait Demo {}

impl dyn Demo {
pub fn report(&self,
a: u32,
b: u32,
c: u32) -> u32 {
return a + b + c;
}

fn check(&self, val: Option<u32>, num: Option<u32>) {
if let Some(b) = val
&& let Some(c) = num {
&& b == c {
//~^ ERROR expected struct
//~| ERROR mismatched types
}
}
}

fn main() { } //~ ERROR this file contains an unclosed delimiter
37 changes: 37 additions & 0 deletions src/test/ui/parser/deli-ident-issue-2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
error: this file contains an unclosed delimiter
--> $DIR/deli-ident-issue-2.rs:25:65
|
LL | impl dyn Demo {
| - unclosed delimiter
...
LL | && let Some(c) = num {
| - this delimiter might not be properly closed...
...
LL | }
| - ...as it matches this but it has different indentation
...
LL | fn main() { }
| ^

error[E0574]: expected struct, variant or union type, found local variable `c`
--> $DIR/deli-ident-issue-2.rs:18:17
|
LL | && b == c {
| ^ not a struct, variant or union type

error[E0308]: mismatched types
--> $DIR/deli-ident-issue-2.rs:18:9
|
LL | fn check(&self, val: Option<u32>, num: Option<u32>) {
| - expected `()` because of default return type
...
LL | / && b == c {
LL | |
LL | |
LL | | }
| |_________^ expected `()`, found `bool`

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0308, E0574.
For more information about an error, try `rustc --explain E0308`.
12 changes: 12 additions & 0 deletions src/test/ui/parser/issue-68987-unmatch-issue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This file has unexpected closing delimiter,

fn func(o: Option<u32>) {
match o {
Some(_x) =>
let _ = if true {};
}
None => {}
}
} //~ ERROR unexpected closing delimiter

fn main() {}
14 changes: 14 additions & 0 deletions src/test/ui/parser/issue-68987-unmatch-issue.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: unexpected closing delimiter: `}`
--> $DIR/issue-68987-unmatch-issue.rs:10:1
|
LL | match o {
| - this delimiter might not be properly closed...
...
LL | }
| - ...as it matches this but it has different indentation
...
LL | }
| ^ unexpected closing delimiter

error: aborting due to previous error

6 changes: 2 additions & 4 deletions src/test/ui/parser/issues/issue-2354.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
fn foo() { //~ NOTE unclosed delimiter
match Some(10) {
//~^ NOTE this delimiter might not be properly closed...
match Some(10) { //~ NOTE this delimiter might not be properly closed
Some(y) => { panic!(); }
None => { panic!(); }
}
//~^ NOTE ...as it matches this but it has different indentation
} //~ NOTE ...as it matches this but it has different indentation

fn bar() {
let mut i = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/parser/issues/issue-2354.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: this file contains an unclosed delimiter
--> $DIR/issue-2354.rs:15:52
--> $DIR/issue-2354.rs:13:52
|
LL | fn foo() {
| - unclosed delimiter
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/parser/issues/issue-70583-block-is-empty-1.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ error: unexpected closing delimiter: `}`
--> $DIR/issue-70583-block-is-empty-1.rs:20:1
|
LL | fn struct_generic(x: Vec<i32>) {
| - this opening brace...
| - this delimiter might not be properly closed...
...
LL | }
| - ...matches this closing brace
| - ...as it matches this but it has different indentation
LL | }
| ^ unexpected closing delimiter

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
error: unexpected closing delimiter: `}`
--> $DIR/issue-70583-block-is-empty-2.rs:14:1
|
LL | match self {
| - this delimiter might not be properly closed...
LL | ErrorHandled::Reported => {}}
| -- block is empty, you might have not meant to close it
| - ...as it matches this but it has different indentation
...
LL | }
| ^ unexpected closing delimiter
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/parser/macro-mismatched-delim-paren-brace.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ error: unexpected closing delimiter: `}`
--> $DIR/macro-mismatched-delim-paren-brace.rs:5:1
|
LL | fn main() {
| - this opening brace...
| - this delimiter might not be properly closed...
...
LL | }
| - ...matches this closing brace
| - ...as it matches this but it has different indentation
LL | }
| ^ unexpected closing delimiter

Expand Down
6 changes: 0 additions & 6 deletions src/test/ui/parser/mismatched-delim-brace-empty-block.stderr
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
error: unexpected closing delimiter: `}`
--> $DIR/mismatched-delim-brace-empty-block.rs:5:1
|
LL | fn main() {
| - this opening brace...
LL |
LL | }
| - ...matches this closing brace
LL | let _ = ();
LL | }
| ^ unexpected closing delimiter

Expand Down

0 comments on commit 1a8e69a

Please sign in to comment.