| use rustc_ast::token::Delimiter; | |
| use rustc_errors::Diag; | |
| use rustc_session::parse::ParseSess; | |
| use rustc_span::Span; | |
| use rustc_span::source_map::SourceMap; | |
| use super::UnmatchedDelim; | |
| use crate::errors::MismatchedClosingDelimiter; | |
| use crate::pprust; | |
| #[derive(Default)] | |
| pub(super) struct TokenTreeDiagInfo { | |
| /// Stack of open delimiters and their spans. Used for error message. | |
| pub open_delimiters: Vec<(Delimiter, Span)>, | |
| pub unmatched_delims: Vec<UnmatchedDelim>, | |
| /// Used only for error recovery when arriving to EOF with mismatched braces. | |
| pub last_unclosed_found_span: Option<Span>, | |
| /// Collect empty block spans that might have been auto-inserted by editors. | |
| pub empty_block_spans: Vec<Span>, | |
| /// Collect the spans of braces (Open, Close). Used only | |
| /// for detecting if blocks are empty and only braces. | |
| pub matching_block_spans: Vec<(Span, Span)>, | |
| } | |
| pub(super) fn same_indentation_level(sm: &SourceMap, open_sp: Span, close_sp: Span) -> bool { | |
| match (sm.span_to_margin(open_sp), sm.span_to_margin(close_sp)) { | |
| (Some(open_padding), Some(close_padding)) => open_padding == close_padding, | |
| _ => false, | |
| } | |
| } | |
| // When we get a `)` or `]` for `{`, we should emit help message here | |
| // it's more friendly compared to report `unmatched error` in later phase | |
| pub(super) fn report_missing_open_delim( | |
| err: &mut Diag<'_>, | |
| unmatched_delims: &mut Vec<UnmatchedDelim>, | |
| ) -> bool { | |
| let mut reported_missing_open = false; | |
| unmatched_delims.retain(|unmatch_brace| { | |
| if let Some(delim) = unmatch_brace.found_delim | |
| && matches!(delim, Delimiter::Parenthesis | Delimiter::Bracket) | |
| { | |
| let missed_open = match delim { | |
| Delimiter::Parenthesis => "(", | |
| Delimiter::Bracket => "[", | |
| _ => unreachable!(), | |
| }; | |
| if let Some(unclosed_span) = unmatch_brace.unclosed_span { | |
| err.span_label(unclosed_span, "the nearest open delimiter"); | |
| } | |
| err.span_label( | |
| unmatch_brace.found_span.shrink_to_lo(), | |
| format!("missing open `{missed_open}` for this delimiter"), | |
| ); | |
| reported_missing_open = true; | |
| false | |
| } else { | |
| true | |
| } | |
| }); | |
| reported_missing_open | |
| } | |
| pub(super) fn report_suspicious_mismatch_block( | |
| err: &mut Diag<'_>, | |
| diag_info: &TokenTreeDiagInfo, | |
| sm: &SourceMap, | |
| delim: Delimiter, | |
| ) { | |
| let mut matched_spans: Vec<(Span, bool)> = diag_info | |
| .matching_block_spans | |
| .iter() | |
| .map(|&(open, close)| (open.with_hi(close.lo()), same_indentation_level(sm, open, close))) | |
| .collect(); | |
| // sort by `lo`, so the large block spans in the front | |
| matched_spans.sort_by_key(|(span, _)| span.lo()); | |
| // We use larger block whose indentation is well to cover those inner mismatched 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, inner_same_ident) = matched_spans[j]; | |
| if block_span.contains(inner_block) && !inner_same_ident { | |
| matched_spans[j] = (inner_block, true); | |
| } | |
| } | |
| } | |
| } | |
| // Find the innermost span candidate for final report | |
| let candidate_span = | |
| matched_spans.into_iter().rev().find(|&(_, same_ident)| !same_ident).map(|(span, _)| span); | |
| 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", | |
| ); | |
| // If there is a empty block in the mismatched span, note it | |
| if delim == Delimiter::Brace { | |
| for span in diag_info.empty_block_spans.iter() { | |
| if block_span.contains(*span) { | |
| err.span_label(*span, "block is empty, you might have not meant to close it"); | |
| break; | |
| } | |
| } | |
| } | |
| } else { | |
| // If there is no suspicious span, give the last properly closed block may help | |
| if let Some(parent) = diag_info.matching_block_spans.last() | |
| && diag_info.open_delimiters.last().is_none() | |
| && diag_info.empty_block_spans.iter().all(|&sp| sp != parent.0.to(parent.1)) | |
| { | |
| err.span_label(parent.0, "this opening brace..."); | |
| err.span_label(parent.1, "...matches this closing brace"); | |
| } | |
| } | |
| } | |
| pub(crate) fn make_errors_for_mismatched_closing_delims<'psess>( | |
| unmatcheds: &[UnmatchedDelim], | |
| psess: &'psess ParseSess, | |
| ) -> Vec<Diag<'psess>> { | |
| unmatcheds | |
| .iter() | |
| .filter_map(|unmatched| { | |
| // `None` here means an `Eof` was found. We already emit those errors elsewhere, we add them to | |
| // `unmatched_delims` only for error recovery in the `Parser`. | |
| let found_delim = unmatched.found_delim?; | |
| let mut spans = vec![unmatched.found_span]; | |
| if let Some(sp) = unmatched.unclosed_span { | |
| spans.push(sp); | |
| }; | |
| let err = psess.dcx().create_err(MismatchedClosingDelimiter { | |
| spans, | |
| delimiter: pprust::token_kind_to_string(&found_delim.as_close_token_kind()) | |
| .to_string(), | |
| unmatched: unmatched.found_span, | |
| opening_candidate: unmatched.candidate_span, | |
| unclosed: unmatched.unclosed_span, | |
| }); | |
| Some(err) | |
| }) | |
| .collect() | |
| } |