rustc_parse/lexer/
diagnostics.rs

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