use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use alloc::borrow::Cow;
use core::iter;
use eko::path::Path;
use crate::rustc_data_structures::fx::FxIndexSet;
use crate::rustc_error_messages::DiagArgMap;
use crate::rustc_span::hygiene::{ExpnKind, MacroKind};
use crate::rustc_span::source_map::SourceMap;
use crate::rustc_span::{FileName, SourceFile, Span};
use tracing::{debug, warn};
use crate::rustc_errors::formatting::format_diag_message;
use crate::rustc_errors::timings::TimingRecord;
use crate::rustc_errors::{
CodeSuggestion, DiagInner, DiagMessage, Level, MultiSpan, Style, Subdiag, SuggestionStyle,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HumanReadableErrorType {
pub short: bool,
pub unicode: bool,
}
impl HumanReadableErrorType {
pub fn short(&self) -> bool {
self.short
}
}
pub enum TimingEvent {
Start,
End,
}
pub type DynEmitter = dyn Emitter;
pub trait Emitter {
fn emit_diagnostic(&mut self, diag: DiagInner);
fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>) {}
fn emit_unused_externs(
&mut self,
_lint_level: crate::rustc_lint_defs::Level,
_unused_externs: &[&str],
) {
}
fn should_show_explain(&self) -> bool {
true
}
fn supports_color(&self) -> bool {
false
}
fn source_map(&self) -> Option<&SourceMap>;
fn primary_span_formatted(
&self,
primary_span: &mut MultiSpan,
suggestions: &mut Vec<CodeSuggestion>,
args: &DiagArgMap,
) {
if let Some((sugg, rest)) = suggestions.split_first() {
let msg = format_diag_message(&sugg.msg, args);
if rest.is_empty()
&& let [substitution] = sugg.substitutions.as_slice()
&& let [part] = substitution.parts.as_slice()
&& msg.split_whitespace().count() < 10
&& !part.snippet.contains('\n')
&& ![
SuggestionStyle::HideCodeAlways,
SuggestionStyle::CompletelyHidden,
SuggestionStyle::ShowAlways,
].contains(&sugg.style)
{
let snippet = part.snippet.trim();
let msg = if snippet.is_empty() || sugg.style.hide_inline() {
format!("help: {msg}")
} else {
let confusion_type = self
.source_map()
.map(|sm| detect_confusion_type(sm, snippet, part.span))
.unwrap_or(ConfusionType::None);
format!("help: {}{}: `{}`", msg, confusion_type.label_text(), snippet,)
};
primary_span.push_span_label(part.span, msg);
suggestions.clear();
} else {
}
} else {
}
}
fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
&self,
span: &mut MultiSpan,
children: &mut Vec<Subdiag>,
level: &Level,
backtrace: bool,
) {
let has_macro_spans: Vec<_> = iter::once(&*span)
.chain(children.iter().map(|child| &child.span))
.flat_map(|span| span.primary_spans())
.flat_map(|sp| sp.macro_backtrace())
.filter_map(|expn_data| {
match expn_data.kind {
ExpnKind::Root => None,
ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
ExpnKind::Macro(macro_kind, name) => {
Some((macro_kind, name, expn_data.diagnostic_opaque))
}
}
})
.collect();
if !backtrace {
self.fix_multispans_in_extern_macros(span, children);
}
self.render_multispans_macro_backtrace(span, children, backtrace);
if !backtrace {
if let Some((macro_kind, name, _)) = has_macro_spans.first()
&& let Some((_, _, false)) = has_macro_spans.last()
{
let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
&& last_name != name
{
let descr = macro_kind.descr();
format!(" which comes from the expansion of the {descr} `{last_name}`")
} else {
"".to_string()
};
let descr = macro_kind.descr();
let msg = format!(
"this {level} originates in the {descr} `{name}`{and_then} \
(in Nightly builds, run with -Z macro-backtrace for more info)",
);
children.push(Subdiag {
level: Level::Note,
messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
span: MultiSpan::new(),
});
}
}
}
fn render_multispans_macro_backtrace(
&self,
span: &mut MultiSpan,
children: &mut Vec<Subdiag>,
backtrace: bool,
) {
for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
self.render_multispan_macro_backtrace(span, backtrace);
}
}
fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
let mut new_labels = FxIndexSet::default();
for &sp in span.primary_spans() {
if sp.is_dummy() {
continue;
}
let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
for (i, trace) in macro_backtrace.iter().rev().enumerate() {
if trace.def_site.is_dummy() {
continue;
}
if always_backtrace {
new_labels.insert((
trace.def_site,
format!(
"in this expansion of `{}`{}",
trace.kind.descr(),
if macro_backtrace.len() > 1 {
format!(" (#{})", i + 1)
} else {
String::new()
},
),
));
}
let redundant_span = trace.call_site.contains(sp);
if !redundant_span || always_backtrace {
let msg: Cow<'static, _> = match trace.kind {
ExpnKind::Macro(MacroKind::Attr, _) => {
"this attribute macro expansion".into()
}
ExpnKind::Macro(MacroKind::Derive, _) => {
"this derive macro expansion".into()
}
ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
ExpnKind::Root => "the crate root".into(),
ExpnKind::AstPass(kind) => kind.descr().into(),
ExpnKind::Desugaring(kind) => {
format!("this {} desugaring", kind.descr()).into()
}
};
new_labels.insert((
trace.call_site,
format!(
"in {}{}",
msg,
if macro_backtrace.len() > 1 && always_backtrace {
format!(" (#{})", i + 1)
} else {
String::new()
},
),
));
}
if !always_backtrace {
break;
}
}
}
for (label_span, label_text) in new_labels {
span.push_span_label(label_span, label_text);
}
}
fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
self.fix_multispan_in_extern_macros(span);
for child in children.iter_mut() {
self.fix_multispan_in_extern_macros(&mut child.span);
}
debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
}
fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
let Some(source_map) = self.source_map() else { return };
let should_hide = |span| {
source_map.is_imported(span) || {
let expn = span.data().ctxt.outer_expn_data();
expn.diagnostic_opaque && matches!(expn.kind, ExpnKind::Macro(MacroKind::Bang, _))
}
};
let replacements: Vec<(Span, Span)> = span
.primary_spans()
.iter()
.copied()
.chain(span.span_labels().iter().map(|sp_label| sp_label.span))
.filter_map(|sp| {
if !sp.is_dummy() && should_hide(sp) {
let mut span = sp;
while let Some(callsite) = span.parent_callsite() {
span = callsite;
if !should_hide(span) {
return Some((sp, span));
}
}
}
None
})
.collect();
for (from, to) in replacements {
span.replace(from, to);
}
}
}
pub struct EmitterWithNote {
pub emitter: Box<dyn Emitter>,
pub note: String,
}
impl Emitter for EmitterWithNote {
fn source_map(&self) -> Option<&SourceMap> {
None
}
fn emit_diagnostic(&mut self, mut diag: DiagInner) {
diag.sub(Level::Note, self.note.clone(), MultiSpan::new());
self.emitter.emit_diagnostic(diag);
}
}
pub struct SilentEmitter;
impl Emitter for SilentEmitter {
fn source_map(&self) -> Option<&SourceMap> {
None
}
fn emit_diagnostic(&mut self, _diag: DiagInner) {}
}
pub const MAX_SUGGESTIONS: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorConfig {
Auto,
Always,
Never,
}
impl ColorConfig {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputTheme {
Ascii,
Unicode,
}
const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
('\0', "␀"),
('\u{0001}', "␁"),
('\u{0002}', "␂"),
('\u{0003}', "␃"),
('\u{0004}', "␄"),
('\u{0005}', "␅"),
('\u{0006}', "␆"),
('\u{0007}', "␇"),
('\u{0008}', "␈"),
('\t', " "), ('\u{000b}', "␋"),
('\u{000c}', "␌"),
('\u{000d}', "␍"),
('\u{000e}', "␎"),
('\u{000f}', "␏"),
('\u{0010}', "␐"),
('\u{0011}', "␑"),
('\u{0012}', "␒"),
('\u{0013}', "␓"),
('\u{0014}', "␔"),
('\u{0015}', "␕"),
('\u{0016}', "␖"),
('\u{0017}', "␗"),
('\u{0018}', "␘"),
('\u{0019}', "␙"),
('\u{001a}', "␚"),
('\u{001b}', "␛"),
('\u{001c}', "␜"),
('\u{001d}', "␝"),
('\u{001e}', "␞"),
('\u{001f}', "␟"),
('\u{007f}', "␡"),
('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
('\u{202e}', "�"),
('\u{2066}', "�"),
('\u{2067}', "�"),
('\u{2068}', "�"),
('\u{2069}', "�"),
];
pub(crate) fn normalize_whitespace(s: &str) -> String {
const {
let mut i = 1;
while i < OUTPUT_REPLACEMENTS.len() {
assert!(
OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
"The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
and must contain no duplicate entries"
);
i += 1;
}
}
s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
_ => s.push(c),
}
s
})
}
impl Style {
}
pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
let found = match sm.span_to_snippet(sp) {
Ok(snippet) => snippet,
Err(e) => {
warn!(error = ?e, "Invalid span {:?}", sp);
return true;
}
};
found != suggested
}
pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
let found = match sm.span_to_snippet(sp) {
Ok(snippet) => snippet,
Err(e) => {
warn!(error = ?e, "Invalid span {:?}", sp);
return ConfusionType::None;
}
};
let mut has_case_confusion = false;
let mut has_digit_letter_confusion = false;
if found.len() == suggested.len() {
let mut has_case_diff = false;
let mut has_digit_letter_confusable = false;
let mut has_other_diff = false;
let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
for (f, s) in iter::zip(found.chars(), suggested.chars()) {
if f != s {
if f.eq_ignore_ascii_case(&s) {
if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
has_case_diff = true;
} else {
has_other_diff = true;
}
} else if digit_letter_confusables.contains(&(f, s))
|| digit_letter_confusables.contains(&(s, f))
{
has_digit_letter_confusable = true;
} else {
has_other_diff = true;
}
}
}
if has_case_diff && !has_other_diff && found != suggested {
has_case_confusion = true;
}
if has_digit_letter_confusable && !has_other_diff && found != suggested {
has_digit_letter_confusion = true;
}
}
match (has_case_confusion, has_digit_letter_confusion) {
(true, true) => ConfusionType::Both,
(true, false) => ConfusionType::Case,
(false, true) => ConfusionType::DigitLetter,
(false, false) => ConfusionType::None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfusionType {
None,
Case,
DigitLetter,
Both,
}
impl ConfusionType {
pub fn label_text(&self) -> &'static str {
match self {
ConfusionType::None => "",
ConfusionType::Case => " (notice the capitalization)",
ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
}
}
pub fn combine(self, other: ConfusionType) -> ConfusionType {
match (self, other) {
(ConfusionType::None, other) => other,
(this, ConfusionType::None) => this,
(ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
(ConfusionType::Case, ConfusionType::DigitLetter)
| (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
(ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
(ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
}
}
pub fn has_confusion(&self) -> bool {
*self != ConfusionType::None
}
}
pub(crate) fn should_show_source_code(
ignored_directories: &[String],
sm: &SourceMap,
file: &SourceFile,
) -> bool {
if !sm.ensure_source_file_source_present(file) {
return false;
}
let FileName::Real(name) = &file.name else { return true };
name.local_path()
.map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
.unwrap_or(true)
}