]>
Commit | Line | Data |
---|---|---|
9ffffee4 FG |
1 | use super::UnmatchedDelim; |
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_delims: Vec<UnmatchedDelim>, | |
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 | ||
353b0b11 | 24 | pub fn same_indentation_level(sm: &SourceMap, open_sp: Span, close_sp: Span) -> bool { |
9ffffee4 FG |
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_delims: &[UnmatchedDelim], | |
36 | ) -> bool { | |
37 | let mut reported_missing_open = false; | |
38 | for unmatch_brace in unmatched_delims.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_delims) { | |
64 | return; | |
65 | } | |
66 | ||
67 | let mut matched_spans: Vec<(Span, bool)> = diag_info | |
68 | .matching_block_spans | |
69 | .iter() | |
353b0b11 | 70 | .map(|&(open, close)| (open.with_hi(close.lo()), same_indentation_level(sm, open, close))) |
9ffffee4 FG |
71 | .collect(); |
72 | ||
73 | // sort by `lo`, so the large block spans in the front | |
353b0b11 | 74 | matched_spans.sort_by_key(|(span, _)| span.lo()); |
9ffffee4 | 75 | |
353b0b11 | 76 | // We use larger block whose indentation is well to cover those inner mismatched blocks |
9ffffee4 FG |
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 | } |