Skip to main content

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