rustc_parse/lexer/
diagnostics.rs

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