use Destination::*;
use rustc_span::source_map::SourceMap;
use rustc_span::{MultiSpan, SourceFile, Span};
use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString};
use crate::styled_buffer::StyledBuffer;
use crate::{
pluralize, CodeSuggestion, Diagnostic, DiagnosticId, Level, SubDiagnostic, SuggestionStyle,
};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sync::Lrc;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use std::borrow::Cow;
use std::cmp::{max, min, Reverse};
use std::io;
use std::io::prelude::*;
use std::iter;
use std::path::Path;
use termcolor::{Ansi, BufferWriter, ColorChoice, ColorSpec, StandardStream};
use termcolor::{Buffer, Color, WriteColor};
use tracing::*;
const DEFAULT_COLUMN_WIDTH: usize = 140;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HumanReadableErrorType {
Default(ColorConfig),
AnnotateSnippet(ColorConfig),
Short(ColorConfig),
}
impl HumanReadableErrorType {
pub fn unzip(self) -> (bool, ColorConfig) {
match self {
HumanReadableErrorType::Default(cc) => (false, cc),
HumanReadableErrorType::Short(cc) => (true, cc),
HumanReadableErrorType::AnnotateSnippet(cc) => (false, cc),
}
}
pub fn new_emitter(
self,
dst: Box<dyn Write + Send>,
source_map: Option<Lrc<SourceMap>>,
teach: bool,
terminal_width: Option<usize>,
macro_backtrace: bool,
) -> EmitterWriter {
let (short, color_config) = self.unzip();
let color = color_config.suggests_using_colors();
EmitterWriter::new(dst, source_map, short, teach, color, terminal_width, macro_backtrace)
}
}
#[derive(Clone, Copy, Debug)]
struct Margin {
pub whitespace_left: usize,
pub span_left: usize,
pub span_right: usize,
pub computed_left: usize,
pub computed_right: usize,
pub column_width: usize,
pub label_right: usize,
}
impl Margin {
fn new(
whitespace_left: usize,
span_left: usize,
span_right: usize,
label_right: usize,
column_width: usize,
max_line_len: usize,
) -> Self {
let mut m = Margin {
whitespace_left: whitespace_left.saturating_sub(6),
span_left: span_left.saturating_sub(6),
span_right: span_right + 6,
computed_left: 0,
computed_right: 0,
column_width,
label_right: label_right + 6,
};
m.compute(max_line_len);
m
}
fn was_cut_left(&self) -> bool {
self.computed_left > 0
}
fn was_cut_right(&self, line_len: usize) -> bool {
let right =
if self.computed_right == self.span_right || self.computed_right == self.label_right {
self.computed_right - 6
} else {
self.computed_right
};
right < line_len && self.computed_left + self.column_width < line_len
}
fn compute(&mut self, max_line_len: usize) {
self.computed_left = if self.whitespace_left > 20 {
self.whitespace_left - 16
} else {
0
};
self.computed_right = max(max_line_len, self.computed_left);
if self.computed_right - self.computed_left > self.column_width {
if self.label_right - self.whitespace_left <= self.column_width {
self.computed_left = self.whitespace_left;
self.computed_right = self.computed_left + self.column_width;
} else if self.label_right - self.span_left <= self.column_width {
let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
self.computed_left = self.span_left.saturating_sub(padding_left);
self.computed_right = self.computed_left + self.column_width;
} else if self.span_right - self.span_left <= self.column_width {
let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
self.computed_left = self.span_left.saturating_sub(padding_left);
self.computed_right = self.computed_left + self.column_width;
} else {
self.computed_left = self.span_left;
self.computed_right = self.span_right;
}
}
}
fn left(&self, line_len: usize) -> usize {
min(self.computed_left, line_len)
}
fn right(&self, line_len: usize) -> usize {
if line_len.saturating_sub(self.computed_left) <= self.column_width {
line_len
} else {
min(line_len, self.computed_right)
}
}
}
const ANONYMIZED_LINE_NUM: &str = "LL";
pub trait Emitter {
fn emit_diagnostic(&mut self, diag: &Diagnostic);
fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
fn should_show_explain(&self) -> bool {
true
}
fn source_map(&self) -> Option<&Lrc<SourceMap>>;
fn primary_span_formatted<'a>(
&mut self,
diag: &'a Diagnostic,
) -> (MultiSpan, &'a [CodeSuggestion]) {
let mut primary_span = diag.span.clone();
if let Some((sugg, rest)) = diag.suggestions.split_first() {
if rest.is_empty() &&
sugg.substitutions.len() == 1 &&
sugg.substitutions[0].parts.len() == 1 &&
sugg.msg.split_whitespace().count() < 10 &&
!sugg.substitutions[0].parts[0].snippet.contains('\n') &&
![
SuggestionStyle::HideCodeAlways,
SuggestionStyle::CompletelyHidden,
SuggestionStyle::ShowAlways,
].contains(&sugg.style)
{
let substitution = &sugg.substitutions[0].parts[0].snippet.trim();
let msg = if substitution.is_empty() || sugg.style.hide_inline() {
format!("help: {}", sugg.msg)
} else {
format!(
"help: {}{}: `{}`",
sugg.msg,
if self
.source_map()
.map(|sm| is_case_difference(
&**sm,
substitution,
sugg.substitutions[0].parts[0].span,
))
.unwrap_or(false)
{
" (notice the capitalization)"
} else {
""
},
substitution,
)
};
primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg);
(primary_span, &[])
} else {
(primary_span, &diag.suggestions)
}
} else {
(primary_span, &diag.suggestions)
}
}
fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
&self,
source_map: &Option<Lrc<SourceMap>>,
span: &mut MultiSpan,
children: &mut Vec<SubDiagnostic>,
level: &Level,
backtrace: bool,
) {
let has_macro_spans = iter::once(&*span)
.chain(children.iter().map(|child| &child.span))
.flat_map(|span| span.primary_spans())
.flat_map(|sp| sp.macro_backtrace())
.find_map(|expn_data| {
match expn_data.kind {
ExpnKind::Root => None,
ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
ExpnKind::Macro(macro_kind, _) => Some(macro_kind),
}
});
if !backtrace {
self.fix_multispans_in_extern_macros(source_map, span, children);
}
self.render_multispans_macro_backtrace(span, children, backtrace);
if !backtrace {
if let Some(macro_kind) = has_macro_spans {
let msg = format!(
"this {} originates in {} {} \
(in Nightly builds, run with -Z macro-backtrace for more info)",
level,
macro_kind.article(),
macro_kind.descr(),
);
children.push(SubDiagnostic {
level: Level::Note,
message: vec![(msg, Style::NoStyle)],
span: MultiSpan::new(),
render_span: None,
});
}
}
}
fn render_multispans_macro_backtrace(
&self,
span: &mut MultiSpan,
children: &mut Vec<SubDiagnostic>,
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: Vec<(Span, String)> = vec![];
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.push((
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 && matches!(trace.kind, ExpnKind::Macro(MacroKind::Bang, _))
|| always_backtrace
{
new_labels.push((
trace.call_site,
format!(
"in this macro invocation{}",
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,
source_map: &Option<Lrc<SourceMap>>,
span: &mut MultiSpan,
children: &mut Vec<SubDiagnostic>,
) {
debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
for span in iter::once(&mut *span).chain(children.iter_mut().map(|child| &mut child.span)) {
self.fix_multispan_in_extern_macros(source_map, span);
}
debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
}
fn fix_multispan_in_extern_macros(
&self,
source_map: &Option<Lrc<SourceMap>>,
span: &mut MultiSpan,
) {
let sm = match source_map {
Some(ref sm) => sm,
None => return,
};
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() && sm.is_imported(sp) {
let maybe_callsite = sp.source_callsite();
if sp != maybe_callsite {
return Some((sp, maybe_callsite));
}
}
None
})
.collect();
for (from, to) in replacements {
span.replace(from, to);
}
}
}
impl Emitter for EmitterWriter {
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
self.sm.as_ref()
}
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
let mut children = diag.children.clone();
let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
debug!("emit_diagnostic: suggestions={:?}", suggestions);
self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
&self.sm,
&mut primary_span,
&mut children,
&diag.level,
self.macro_backtrace,
);
self.emit_messages_default(
&diag.level,
&diag.styled_message(),
&diag.code,
&primary_span,
&children,
&suggestions,
);
}
fn should_show_explain(&self) -> bool {
!self.short_message
}
}
pub struct SilentEmitter;
impl Emitter for SilentEmitter {
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
None
}
fn emit_diagnostic(&mut self, _: &Diagnostic) {}
}
pub const MAX_HIGHLIGHT_LINES: usize = 6;
pub const MAX_SUGGESTION_HIGHLIGHT_LINES: usize = 6;
pub const MAX_SUGGESTIONS: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorConfig {
Auto,
Always,
Never,
}
impl ColorConfig {
fn to_color_choice(self) -> ColorChoice {
match self {
ColorConfig::Always => {
if atty::is(atty::Stream::Stderr) {
ColorChoice::Always
} else {
ColorChoice::AlwaysAnsi
}
}
ColorConfig::Never => ColorChoice::Never,
ColorConfig::Auto if atty::is(atty::Stream::Stderr) => ColorChoice::Auto,
ColorConfig::Auto => ColorChoice::Never,
}
}
fn suggests_using_colors(self) -> bool {
match self {
ColorConfig::Always | ColorConfig::Auto => true,
ColorConfig::Never => false,
}
}
}
pub struct EmitterWriter {
dst: Destination,
sm: Option<Lrc<SourceMap>>,
short_message: bool,
teach: bool,
ui_testing: bool,
terminal_width: Option<usize>,
macro_backtrace: bool,
}
#[derive(Debug)]
pub struct FileWithAnnotatedLines {
pub file: Lrc<SourceFile>,
pub lines: Vec<Line>,
multiline_depth: usize,
}
impl EmitterWriter {
pub fn stderr(
color_config: ColorConfig,
source_map: Option<Lrc<SourceMap>>,
short_message: bool,
teach: bool,
terminal_width: Option<usize>,
macro_backtrace: bool,
) -> EmitterWriter {
let dst = Destination::from_stderr(color_config);
EmitterWriter {
dst,
sm: source_map,
short_message,
teach,
ui_testing: false,
terminal_width,
macro_backtrace,
}
}
pub fn new(
dst: Box<dyn Write + Send>,
source_map: Option<Lrc<SourceMap>>,
short_message: bool,
teach: bool,
colored: bool,
terminal_width: Option<usize>,
macro_backtrace: bool,
) -> EmitterWriter {
EmitterWriter {
dst: Raw(dst, colored),
sm: source_map,
short_message,
teach,
ui_testing: false,
terminal_width,
macro_backtrace,
}
}
pub fn ui_testing(mut self, ui_testing: bool) -> Self {
self.ui_testing = ui_testing;
self
}
fn maybe_anonymized(&self, line_num: usize) -> String {
if self.ui_testing { ANONYMIZED_LINE_NUM.to_string() } else { line_num.to_string() }
}
fn draw_line(
&self,
buffer: &mut StyledBuffer,
source_string: &str,
line_index: usize,
line_offset: usize,
width_offset: usize,
code_offset: usize,
margin: Margin,
) {
let line_len = source_string.len();
let left = margin.left(line_len);
let right = margin.right(line_len);
let mut taken = 0;
let code: String = source_string
.chars()
.skip(left)
.take_while(|ch| {
let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
if taken + next > right - left {
return false;
}
taken += next;
true
})
.collect();
buffer.puts(line_offset, code_offset, &code, Style::Quotation);
if margin.was_cut_left() {
buffer.puts(line_offset, code_offset, "...", Style::LineNumber);
}
if margin.was_cut_right(line_len) {
buffer.puts(line_offset, code_offset + taken - 3, "...", Style::LineNumber);
}
buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber);
draw_col_separator(buffer, line_offset, width_offset - 2);
}
fn render_source_line(
&self,
buffer: &mut StyledBuffer,
file: Lrc<SourceFile>,
line: &Line,
width_offset: usize,
code_offset: usize,
margin: Margin,
) -> Vec<(usize, Style)> {
if line.line_index == 0 {
return Vec::new();
}
let source_string = match file.get_line(line.line_index - 1) {
Some(s) => s,
None => return Vec::new(),
};
let line_offset = buffer.num_lines();
let left = margin.left(source_string.len());
let left = source_string
.chars()
.take(left)
.map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
.sum();
self.draw_line(
buffer,
&source_string,
line.line_index,
line_offset,
width_offset,
code_offset,
margin,
);
if let [ann] = &line.annotations[..] {
if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
if source_string.chars().take(ann.start_col).all(|c| c.is_whitespace()) {
let style = if ann.is_primary {
Style::UnderlinePrimary
} else {
Style::UnderlineSecondary
};
buffer.putc(line_offset, width_offset + depth - 1, '/', style);
return vec![(depth, style)];
}
}
}
let mut annotations = line.annotations.clone();
annotations.sort_by_key(|a| Reverse(a.start_col));
let mut annotations_position = vec![];
let mut line_len = 0;
let mut p = 0;
for (i, annotation) in annotations.iter().enumerate() {
for (j, next) in annotations.iter().enumerate() {
if overlaps(next, annotation, 0)
&& annotation.has_label()
&& j > i
&& p == 0
{
if next.start_col == annotation.start_col
&& next.end_col == annotation.end_col
&& !next.has_label()
{
continue;
}
p += 1;
break;
}
}
annotations_position.push((p, annotation));
for (j, next) in annotations.iter().enumerate() {
if j > i {
let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
if (overlaps(next, annotation, l)
&& annotation.has_label()
&& next.has_label())
|| (annotation.takes_space()
&& next.has_label())
|| (annotation.has_label()
&& next.takes_space())
|| (annotation.takes_space() && next.takes_space())
|| (overlaps(next, annotation, l)
&& next.end_col <= annotation.end_col
&& next.has_label()
&& p == 0)
{
p += 1;
break;
}
}
}
line_len = max(line_len, p);
}
if line_len != 0 {
line_len += 1;
}
if line.annotations.iter().all(|a| a.is_line()) {
return vec![];
}
for pos in 0..=line_len {
draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2);
buffer.putc(line_offset + pos + 1, width_offset - 2, '|', Style::LineNumber);
}
for &(pos, annotation) in &annotations_position {
let style = if annotation.is_primary {
Style::UnderlinePrimary
} else {
Style::UnderlineSecondary
};
let pos = pos + 1;
match annotation.annotation_type {
AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
draw_range(
buffer,
'_',
line_offset + pos,
width_offset + depth,
code_offset + annotation.start_col - left,
style,
);
}
_ if self.teach => {
buffer.set_style_range(
line_offset,
code_offset + annotation.start_col - left,
code_offset + annotation.end_col - left,
style,
annotation.is_primary,
);
}
_ => {}
}
}
for &(pos, annotation) in &annotations_position {
let style = if annotation.is_primary {
Style::UnderlinePrimary
} else {
Style::UnderlineSecondary
};
let pos = pos + 1;
if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
for p in line_offset + 1..=line_offset + pos {
buffer.putc(
p,
(code_offset + annotation.start_col).saturating_sub(left),
'|',
style,
);
}
}
match annotation.annotation_type {
AnnotationType::MultilineStart(depth) => {
for p in line_offset + pos + 1..line_offset + line_len + 2 {
buffer.putc(p, width_offset + depth - 1, '|', style);
}
}
AnnotationType::MultilineEnd(depth) => {
for p in line_offset..=line_offset + pos {
buffer.putc(p, width_offset + depth - 1, '|', style);
}
}
_ => (),
}
}
for &(pos, annotation) in &annotations_position {
let style =
if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
let (pos, col) = if pos == 0 {
(pos + 1, (annotation.end_col + 1).saturating_sub(left))
} else {
(pos + 2, annotation.start_col.saturating_sub(left))
};
if let Some(ref label) = annotation.label {
buffer.puts(line_offset + pos, code_offset + col, &label, style);
}
}
annotations_position.sort_by_key(|(_, ann)| {
(Reverse(ann.len()), ann.is_primary)
});
for &(_, annotation) in &annotations_position {
let (underline, style) = if annotation.is_primary {
('^', Style::UnderlinePrimary)
} else {
('-', Style::UnderlineSecondary)
};
for p in annotation.start_col..annotation.end_col {
buffer.putc(
line_offset + 1,
(code_offset + p).saturating_sub(left),
underline,
style,
);
}
}
annotations_position
.iter()
.filter_map(|&(_, annotation)| match annotation.annotation_type {
AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
let style = if annotation.is_primary {
Style::LabelPrimary
} else {
Style::LabelSecondary
};
Some((p, style))
}
_ => None,
})
.collect::<Vec<_>>()
}
fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
let sm = match self.sm {
Some(ref sm) => sm,
None => return 0,
};
let mut max = 0;
for primary_span in msp.primary_spans() {
if !primary_span.is_dummy() {
let hi = sm.lookup_char_pos(primary_span.hi());
max = (hi.line).max(max);
}
}
if !self.short_message {
for span_label in msp.span_labels() {
if !span_label.span.is_dummy() {
let hi = sm.lookup_char_pos(span_label.span.hi());
max = (hi.line).max(max);
}
}
}
max
}
fn get_max_line_num(&mut self, span: &MultiSpan, children: &[SubDiagnostic]) -> usize {
let primary = self.get_multispan_max_line_num(span);
children
.iter()
.map(|sub| self.get_multispan_max_line_num(&sub.span))
.max()
.unwrap_or(0)
.max(primary)
}
fn msg_to_buffer(
&self,
buffer: &mut StyledBuffer,
msg: &[(String, Style)],
padding: usize,
label: &str,
override_style: Option<Style>,
) {
let padding = " ".repeat(padding + label.len() + 5);
fn style_or_override(style: Style, override_: Option<Style>) -> Style {
match (style, override_) {
(Style::NoStyle, Some(override_)) => override_,
_ => style,
}
}
let mut line_number = 0;
for &(ref text, ref style) in msg.iter() {
let lines = text.split('\n').collect::<Vec<_>>();
if lines.len() > 1 {
for (i, line) in lines.iter().enumerate() {
if i != 0 {
line_number += 1;
buffer.append(line_number, &padding, Style::NoStyle);
}
buffer.append(line_number, line, style_or_override(*style, override_style));
}
} else {
buffer.append(line_number, text, style_or_override(*style, override_style));
}
}
}
fn emit_message_default(
&mut self,
msp: &MultiSpan,
msg: &[(String, Style)],
code: &Option<DiagnosticId>,
level: &Level,
max_line_num_len: usize,
is_secondary: bool,
) -> io::Result<()> {
let mut buffer = StyledBuffer::new();
let header_style = if is_secondary { Style::HeaderMsg } else { Style::MainHeaderMsg };
if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
{
for _ in 0..max_line_num_len {
buffer.prepend(0, " ", Style::NoStyle);
}
draw_note_separator(&mut buffer, 0, max_line_num_len + 1);
if *level != Level::FailureNote {
let level_str = level.to_string();
if !level_str.is_empty() {
buffer.append(0, &level_str, Style::MainHeaderMsg);
buffer.append(0, ": ", Style::NoStyle);
}
}
self.msg_to_buffer(&mut buffer, msg, max_line_num_len, "note", None);
} else {
let level_str = level.to_string();
if *level != Level::FailureNote && !level_str.is_empty() {
buffer.append(0, &level_str, Style::Level(*level));
}
if let Some(DiagnosticId::Error(ref code)) = *code {
buffer.append(0, "[", Style::Level(*level));
buffer.append(0, &code, Style::Level(*level));
buffer.append(0, "]", Style::Level(*level));
}
if *level != Level::FailureNote && !level_str.is_empty() {
buffer.append(0, ": ", header_style);
}
for &(ref text, _) in msg.iter() {
buffer.append(0, text, header_style);
}
}
let mut annotated_files = FileWithAnnotatedLines::collect_annotations(msp, &self.sm);
let (primary_lo, sm) = if let (Some(sm), Some(ref primary_span)) =
(self.sm.as_ref(), msp.primary_span().as_ref())
{
if !primary_span.is_dummy() {
(sm.lookup_char_pos(primary_span.lo()), sm)
} else {
emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
return Ok(());
}
} else {
emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
return Ok(());
};
if let Ok(pos) =
annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
{
annotated_files.swap(0, pos);
}
for annotated_file in annotated_files {
if !sm.ensure_source_file_source_present(annotated_file.file.clone()) {
continue;
}
let is_primary = primary_lo.file.name == annotated_file.file.name;
if is_primary {
let loc = primary_lo.clone();
if !self.short_message {
let buffer_msg_line_offset = buffer.num_lines();
buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber);
buffer.append(
buffer_msg_line_offset,
&format!(
"{}:{}:{}",
loc.file.name,
sm.doctest_offset_line(&loc.file.name, loc.line),
loc.col.0 + 1,
),
Style::LineAndColumn,
);
for _ in 0..max_line_num_len {
buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
}
} else {
buffer.prepend(
0,
&format!(
"{}:{}:{}: ",
loc.file.name,
sm.doctest_offset_line(&loc.file.name, loc.line),
loc.col.0 + 1,
),
Style::LineAndColumn,
);
}
} else if !self.short_message {
let buffer_msg_line_offset = buffer.num_lines();
draw_col_separator(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1);
buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber);
let loc = if let Some(first_line) = annotated_file.lines.first() {
let col = if let Some(first_annotation) = first_line.annotations.first() {
format!(":{}", first_annotation.start_col + 1)
} else {
String::new()
};
format!(
"{}:{}{}",
annotated_file.file.name,
sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
col
)
} else {
annotated_file.file.name.to_string()
};
buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
for _ in 0..max_line_num_len {
buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
}
}
if !self.short_message {
let buffer_msg_line_offset = buffer.num_lines();
draw_col_separator_no_space(
&mut buffer,
buffer_msg_line_offset,
max_line_num_len + 1,
);
let mut multilines = FxHashMap::default();
let mut whitespace_margin = usize::MAX;
for line_idx in 0..annotated_file.lines.len() {
let file = annotated_file.file.clone();
let line = &annotated_file.lines[line_idx];
if let Some(source_string) = file.get_line(line.line_index - 1) {
let leading_whitespace =
source_string.chars().take_while(|c| c.is_whitespace()).count();
if source_string.chars().any(|c| !c.is_whitespace()) {
whitespace_margin = min(whitespace_margin, leading_whitespace);
}
}
}
if whitespace_margin == usize::MAX {
whitespace_margin = 0;
}
let mut span_left_margin = usize::MAX;
for line in &annotated_file.lines {
for ann in &line.annotations {
span_left_margin = min(span_left_margin, ann.start_col);
span_left_margin = min(span_left_margin, ann.end_col);
}
}
if span_left_margin == usize::MAX {
span_left_margin = 0;
}
let mut span_right_margin = 0;
let mut label_right_margin = 0;
let mut max_line_len = 0;
for line in &annotated_file.lines {
max_line_len = max(
max_line_len,
annotated_file.file.get_line(line.line_index - 1).map_or(0, |s| s.len()),
);
for ann in &line.annotations {
span_right_margin = max(span_right_margin, ann.start_col);
span_right_margin = max(span_right_margin, ann.end_col);
let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
label_right_margin = max(label_right_margin, ann.end_col + label_right);
}
}
let width_offset = 3 + max_line_num_len;
let code_offset = if annotated_file.multiline_depth == 0 {
width_offset
} else {
width_offset + annotated_file.multiline_depth + 1
};
let column_width = if let Some(width) = self.terminal_width {
width.saturating_sub(code_offset)
} else if self.ui_testing {
DEFAULT_COLUMN_WIDTH
} else {
termize::dimensions()
.map(|(w, _)| w.saturating_sub(code_offset))
.unwrap_or(DEFAULT_COLUMN_WIDTH)
};
let margin = Margin::new(
whitespace_margin,
span_left_margin,
span_right_margin,
label_right_margin,
column_width,
max_line_len,
);
for line_idx in 0..annotated_file.lines.len() {
let previous_buffer_line = buffer.num_lines();
let depths = self.render_source_line(
&mut buffer,
annotated_file.file.clone(),
&annotated_file.lines[line_idx],
width_offset,
code_offset,
margin,
);
let mut to_add = FxHashMap::default();
for (depth, style) in depths {
if multilines.get(&depth).is_some() {
multilines.remove(&depth);
} else {
to_add.insert(depth, style);
}
}
for (depth, style) in &multilines {
for line in previous_buffer_line..buffer.num_lines() {
draw_multiline_line(&mut buffer, line, width_offset, *depth, *style);
}
}
if line_idx < (annotated_file.lines.len() - 1) {
let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
- annotated_file.lines[line_idx].line_index;
if line_idx_delta > 2 {
let last_buffer_line_num = buffer.num_lines();
buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber);
for (depth, style) in &multilines {
draw_multiline_line(
&mut buffer,
last_buffer_line_num,
width_offset,
*depth,
*style,
);
}
} else if line_idx_delta == 2 {
let unannotated_line = annotated_file
.file
.get_line(annotated_file.lines[line_idx].line_index)
.unwrap_or_else(|| Cow::from(""));
let last_buffer_line_num = buffer.num_lines();
self.draw_line(
&mut buffer,
&unannotated_line,
annotated_file.lines[line_idx + 1].line_index - 1,
last_buffer_line_num,
width_offset,
code_offset,
margin,
);
for (depth, style) in &multilines {
draw_multiline_line(
&mut buffer,
last_buffer_line_num,
width_offset,
*depth,
*style,
);
}
}
}
multilines.extend(&to_add);
}
}
}
emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
Ok(())
}
fn emit_suggestion_default(
&mut self,
suggestion: &CodeSuggestion,
level: &Level,
max_line_num_len: usize,
) -> io::Result<()> {
let sm = match self.sm {
Some(ref sm) => sm,
None => return Ok(()),
};
let suggestions = suggestion.splice_lines(&**sm);
debug!("emit_suggestion_default: suggestions={:?}", suggestions);
if suggestions.is_empty() {
return Ok(());
}
let mut buffer = StyledBuffer::new();
let level_str = level.to_string();
if !level_str.is_empty() {
buffer.append(0, &level_str, Style::Level(*level));
buffer.append(0, ": ", Style::HeaderMsg);
}
self.msg_to_buffer(
&mut buffer,
&[(suggestion.msg.to_owned(), Style::NoStyle)],
max_line_num_len,
"suggestion",
Some(Style::HeaderMsg),
);
let mut row_num = 2;
let mut notice_capitalization = false;
for (complete, parts, only_capitalization) in suggestions.iter().take(MAX_SUGGESTIONS) {
notice_capitalization |= only_capitalization;
let show_underline = !(parts.len() == 1 && parts[0].snippet.trim() == complete.trim())
&& complete.lines().count() == 1;
let lines = sm
.span_to_lines(parts[0].span)
.expect("span_to_lines failed when emitting suggestion");
assert!(!lines.lines.is_empty() || parts[0].span.is_dummy());
let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
let mut lines = complete.lines();
for (line_pos, line) in lines.by_ref().take(MAX_SUGGESTION_HIGHLIGHT_LINES).enumerate()
{
buffer.puts(
row_num,
0,
&self.maybe_anonymized(line_start + line_pos),
Style::LineNumber,
);
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
buffer.append(row_num, line, Style::NoStyle);
row_num += 1;
}
let mut offsets: Vec<(usize, isize)> = Vec::new();
if show_underline {
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
for part in parts {
let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
let start = part.snippet.len().saturating_sub(part.snippet.trim_start().len());
let sub_len: usize = part
.snippet
.trim()
.chars()
.map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
.sum();
let offset: isize = offsets
.iter()
.filter_map(
|(start, v)| if span_start_pos <= *start { None } else { Some(v) },
)
.sum();
let underline_start = (span_start_pos + start) as isize + offset;
let underline_end = (span_start_pos + start + sub_len) as isize + offset;
assert!(underline_start >= 0 && underline_end >= 0);
for p in underline_start..underline_end {
buffer.putc(
row_num,
((max_line_num_len + 3) as isize + p) as usize,
'^',
Style::UnderlinePrimary,
);
}
if underline_start == underline_end {
for p in underline_start - 1..underline_start + 1 {
buffer.putc(
row_num,
((max_line_num_len + 3) as isize + p) as usize,
'-',
Style::UnderlineSecondary,
);
}
}
let full_sub_len = part
.snippet
.chars()
.map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
.sum::<usize>() as isize;
let snippet_len = span_end_pos as isize - span_start_pos as isize;
offsets.push((span_end_pos, full_sub_len - snippet_len));
}
row_num += 1;
}
if lines.next().is_some() {
buffer.puts(row_num, max_line_num_len - 1, "...", Style::LineNumber);
} else if !show_underline {
draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
row_num += 1;
}
}
if suggestions.len() > MAX_SUGGESTIONS {
let others = suggestions.len() - MAX_SUGGESTIONS;
let msg = format!("and {} other candidate{}", others, pluralize!(others));
buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
} else if notice_capitalization {
let msg = "notice the capitalization difference";
buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
}
emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
Ok(())
}
fn emit_messages_default(
&mut self,
level: &Level,
message: &[(String, Style)],
code: &Option<DiagnosticId>,
span: &MultiSpan,
children: &[SubDiagnostic],
suggestions: &[CodeSuggestion],
) {
let max_line_num_len = if self.ui_testing {
ANONYMIZED_LINE_NUM.len()
} else {
self.get_max_line_num(span, children).to_string().len()
};
match self.emit_message_default(span, message, code, level, max_line_num_len, false) {
Ok(()) => {
if !children.is_empty()
|| suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
{
let mut buffer = StyledBuffer::new();
if !self.short_message {
draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
}
match emit_to_destination(
&buffer.render(),
level,
&mut self.dst,
self.short_message,
) {
Ok(()) => (),
Err(e) => panic!("failed to emit error: {}", e),
}
}
if !self.short_message {
for child in children {
let span = child.render_span.as_ref().unwrap_or(&child.span);
if let Err(err) = self.emit_message_default(
&span,
&child.styled_message(),
&None,
&child.level,
max_line_num_len,
true,
) {
panic!("failed to emit error: {}", err);
}
}
for sugg in suggestions {
if sugg.style == SuggestionStyle::CompletelyHidden {
} else if sugg.style == SuggestionStyle::HideCodeAlways {
if let Err(e) = self.emit_message_default(
&MultiSpan::new(),
&[(sugg.msg.to_owned(), Style::HeaderMsg)],
&None,
&Level::Help,
max_line_num_len,
true,
) {
panic!("failed to emit error: {}", e);
}
} else if let Err(e) =
self.emit_suggestion_default(sugg, &Level::Help, max_line_num_len)
{
panic!("failed to emit error: {}", e);
};
}
}
}
Err(e) => panic!("failed to emit error: {}", e),
}
let mut dst = self.dst.writable();
match writeln!(dst) {
Err(e) => panic!("failed to emit error: {}", e),
_ => {
if let Err(e) = dst.flush() {
panic!("failed to emit error: {}", e)
}
}
}
}
}
impl FileWithAnnotatedLines {
pub fn collect_annotations(
msp: &MultiSpan,
source_map: &Option<Lrc<SourceMap>>,
) -> Vec<FileWithAnnotatedLines> {
fn add_annotation_to_file(
file_vec: &mut Vec<FileWithAnnotatedLines>,
file: Lrc<SourceFile>,
line_index: usize,
ann: Annotation,
) {
for slot in file_vec.iter_mut() {
if slot.file.name == file.name {
for line_slot in &mut slot.lines {
if line_slot.line_index == line_index {
line_slot.annotations.push(ann);
return;
}
}
slot.lines.push(Line { line_index, annotations: vec![ann] });
slot.lines.sort();
return;
}
}
file_vec.push(FileWithAnnotatedLines {
file,
lines: vec![Line { line_index, annotations: vec![ann] }],
multiline_depth: 0,
});
}
let mut output = vec![];
let mut multiline_annotations = vec![];
if let Some(ref sm) = source_map {
for span_label in msp.span_labels() {
if span_label.span.is_dummy() {
continue;
}
let lo = sm.lookup_char_pos(span_label.span.lo());
let mut hi = sm.lookup_char_pos(span_label.span.hi());
if lo.col_display == hi.col_display && lo.line == hi.line {
hi.col_display += 1;
}
if lo.line != hi.line {
let ml = MultilineAnnotation {
depth: 1,
line_start: lo.line,
line_end: hi.line,
start_col: lo.col_display,
end_col: hi.col_display,
is_primary: span_label.is_primary,
label: span_label.label,
overlaps_exactly: false,
};
multiline_annotations.push((lo.file, ml));
} else {
let ann = Annotation {
start_col: lo.col_display,
end_col: hi.col_display,
is_primary: span_label.is_primary,
label: span_label.label,
annotation_type: AnnotationType::Singleline,
};
add_annotation_to_file(&mut output, lo.file, lo.line, ann);
};
}
}
multiline_annotations.sort_by_key(|&(_, ref ml)| (ml.line_start, ml.line_end));
for (_, ann) in multiline_annotations.clone() {
for (_, a) in multiline_annotations.iter_mut() {
if !(ann.same_span(a))
&& num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
{
a.increase_depth();
} else if ann.same_span(a) && &ann != a {
a.overlaps_exactly = true;
} else {
break;
}
}
}
let mut max_depth = 0;
for (file, ann) in multiline_annotations {
max_depth = max(max_depth, ann.depth);
let mut end_ann = ann.as_end();
if !ann.overlaps_exactly {
add_annotation_to_file(&mut output, file.clone(), ann.line_start, ann.as_start());
let middle = min(ann.line_start + 4, ann.line_end);
for line in ann.line_start + 1..middle {
add_annotation_to_file(&mut output, file.clone(), line, ann.as_line());
}
let line_end = ann.line_end - 1;
if middle < line_end {
add_annotation_to_file(&mut output, file.clone(), line_end, ann.as_line());
}
} else {
end_ann.annotation_type = AnnotationType::Singleline;
}
add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
}
for file_vec in output.iter_mut() {
file_vec.multiline_depth = max_depth;
}
output
}
}
fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
buffer.puts(line, col, "| ", Style::LineNumber);
}
fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) {
draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber);
}
fn draw_col_separator_no_space_with_style(
buffer: &mut StyledBuffer,
line: usize,
col: usize,
style: Style,
) {
buffer.putc(line, col, '|', style);
}
fn draw_range(
buffer: &mut StyledBuffer,
symbol: char,
line: usize,
col_from: usize,
col_to: usize,
style: Style,
) {
for col in col_from..col_to {
buffer.putc(line, col, symbol, style);
}
}
fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
buffer.puts(line, col, "= ", Style::LineNumber);
}
fn draw_multiline_line(
buffer: &mut StyledBuffer,
line: usize,
offset: usize,
depth: usize,
style: Style,
) {
buffer.putc(line, offset + depth - 1, '|', style);
}
fn num_overlap(
a_start: usize,
a_end: usize,
b_start: usize,
b_end: usize,
inclusive: bool,
) -> bool {
let extra = if inclusive { 1 } else { 0 };
(b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
}
fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
num_overlap(a1.start_col, a1.end_col + padding, a2.start_col, a2.end_col, false)
}
fn emit_to_destination(
rendered_buffer: &[Vec<StyledString>],
lvl: &Level,
dst: &mut Destination,
short_message: bool,
) -> io::Result<()> {
use crate::lock;
let mut dst = dst.writable();
let _buffer_lock = lock::acquire_global_lock("rustc_errors");
for (pos, line) in rendered_buffer.iter().enumerate() {
for part in line {
dst.apply_style(*lvl, part.style)?;
write!(dst, "{}", part.text)?;
dst.reset()?;
}
if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
writeln!(dst)?;
}
}
dst.flush()?;
Ok(())
}
pub enum Destination {
Terminal(StandardStream),
Buffered(BufferWriter),
Raw(Box<(dyn Write + Send)>, bool),
}
pub enum WritableDst<'a> {
Terminal(&'a mut StandardStream),
Buffered(&'a mut BufferWriter, Buffer),
Raw(&'a mut (dyn Write + Send)),
ColoredRaw(Ansi<&'a mut (dyn Write + Send)>),
}
impl Destination {
fn from_stderr(color: ColorConfig) -> Destination {
let choice = color.to_color_choice();
if cfg!(windows) {
Terminal(StandardStream::stderr(choice))
} else {
Buffered(BufferWriter::stderr(choice))
}
}
fn writable(&mut self) -> WritableDst<'_> {
match *self {
Destination::Terminal(ref mut t) => WritableDst::Terminal(t),
Destination::Buffered(ref mut t) => {
let buf = t.buffer();
WritableDst::Buffered(t, buf)
}
Destination::Raw(ref mut t, false) => WritableDst::Raw(t),
Destination::Raw(ref mut t, true) => WritableDst::ColoredRaw(Ansi::new(t)),
}
}
}
impl<'a> WritableDst<'a> {
fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> {
let mut spec = ColorSpec::new();
match style {
Style::LineAndColumn => {}
Style::LineNumber => {
spec.set_bold(true);
spec.set_intense(true);
if cfg!(windows) {
spec.set_fg(Some(Color::Cyan));
} else {
spec.set_fg(Some(Color::Blue));
}
}
Style::Quotation => {}
Style::MainHeaderMsg => {
spec.set_bold(true);
if cfg!(windows) {
spec.set_intense(true).set_fg(Some(Color::White));
}
}
Style::UnderlinePrimary | Style::LabelPrimary => {
spec = lvl.color();
spec.set_bold(true);
}
Style::UnderlineSecondary | Style::LabelSecondary => {
spec.set_bold(true).set_intense(true);
if cfg!(windows) {
spec.set_fg(Some(Color::Cyan));
} else {
spec.set_fg(Some(Color::Blue));
}
}
Style::HeaderMsg | Style::NoStyle => {}
Style::Level(lvl) => {
spec = lvl.color();
spec.set_bold(true);
}
Style::Highlight => {
spec.set_bold(true);
}
}
self.set_color(&spec)
}
fn set_color(&mut self, color: &ColorSpec) -> io::Result<()> {
match *self {
WritableDst::Terminal(ref mut t) => t.set_color(color),
WritableDst::Buffered(_, ref mut t) => t.set_color(color),
WritableDst::ColoredRaw(ref mut t) => t.set_color(color),
WritableDst::Raw(_) => Ok(()),
}
}
fn reset(&mut self) -> io::Result<()> {
match *self {
WritableDst::Terminal(ref mut t) => t.reset(),
WritableDst::Buffered(_, ref mut t) => t.reset(),
WritableDst::ColoredRaw(ref mut t) => t.reset(),
WritableDst::Raw(_) => Ok(()),
}
}
}
impl<'a> Write for WritableDst<'a> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
match *self {
WritableDst::Terminal(ref mut t) => t.write(bytes),
WritableDst::Buffered(_, ref mut buf) => buf.write(bytes),
WritableDst::Raw(ref mut w) => w.write(bytes),
WritableDst::ColoredRaw(ref mut t) => t.write(bytes),
}
}
fn flush(&mut self) -> io::Result<()> {
match *self {
WritableDst::Terminal(ref mut t) => t.flush(),
WritableDst::Buffered(_, ref mut buf) => buf.flush(),
WritableDst::Raw(ref mut w) => w.flush(),
WritableDst::ColoredRaw(ref mut w) => w.flush(),
}
}
}
impl<'a> Drop for WritableDst<'a> {
fn drop(&mut self) {
if let WritableDst::Buffered(ref mut dst, ref mut buf) = self {
drop(dst.print(buf));
}
}
}
pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
let found = match sm.span_to_snippet(sp) {
Ok(snippet) => snippet,
Err(e) => {
warn!("Invalid span {:?}. Err={:?}", sp, e);
return false;
}
};
let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
let confusable = found
.chars()
.zip(suggested.chars())
.filter(|(f, s)| f != s)
.all(|(f, s)| (ascii_confusables.contains(&f) || ascii_confusables.contains(&s)));
confusable && found.to_lowercase() == suggested.to_lowercase()
&& found != suggested
}