blob: 947f3df179ffa9dac9132f49fad0f67bb8fb167a [file] [log] [blame]
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()
}