]> git.lizzy.rs Git - rust.git/blobdiff - src/librustc_errors/emitter.rs
rustc_errors: hide "in this macro invocation" when redundant, more explicitly.
[rust.git] / src / librustc_errors / emitter.rs
index b0e0cb611afaffb60711b97e46c21e669ff2d4dc..4857ff47462c8b49ad78470e6328ae6abaebb225 100644 (file)
 
 use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString};
 use crate::styled_buffer::StyledBuffer;
-use crate::Level::Error;
 use crate::{
     pluralize, CodeSuggestion, Diagnostic, DiagnosticId, Level, SubDiagnostic, SuggestionStyle,
 };
 
+use log::*;
 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;
@@ -52,19 +53,11 @@ pub fn new_emitter(
         source_map: Option<Lrc<SourceMap>>,
         teach: bool,
         terminal_width: Option<usize>,
-        external_macro_backtrace: bool,
+        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,
-            external_macro_backtrace,
-        )
+        EmitterWriter::new(dst, source_map, short, teach, color, terminal_width, macro_backtrace)
     }
 }
 
@@ -278,10 +271,7 @@ fn primary_span_formatted<'a>(
         }
     }
 
-    // This does a small "fix" for multispans by looking to see if it can find any that
-    // point directly at <*macros>. Since these are often difficult to read, this
-    // will change the span to point at the use site.
-    fn fix_multispans_in_std_macros(
+    fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
         &self,
         source_map: &Option<Lrc<SourceMap>>,
         span: &mut MultiSpan,
@@ -289,125 +279,168 @@ fn fix_multispans_in_std_macros(
         level: &Level,
         backtrace: bool,
     ) {
-        let mut spans_updated = self.fix_multispan_in_std_macros(source_map, span, backtrace);
-        for child in children.iter_mut() {
-            spans_updated |=
-                self.fix_multispan_in_std_macros(source_map, &mut child.span, backtrace);
+        let mut external_spans_updated = false;
+        if !backtrace {
+            external_spans_updated =
+                self.fix_multispans_in_extern_macros(source_map, span, children);
         }
-        let msg = if level == &Error {
-            "this error originates in a macro outside of the current crate \
-             (in Nightly builds, run with -Z external-macro-backtrace \
-              for more info)"
-                .to_string()
-        } else {
-            "this warning originates in a macro outside of the current crate \
-             (in Nightly builds, run with -Z external-macro-backtrace \
-              for more info)"
-                .to_string()
-        };
 
-        if spans_updated {
-            children.push(SubDiagnostic {
-                level: Level::Note,
-                message: vec![(msg, Style::NoStyle)],
-                span: MultiSpan::new(),
-                render_span: None,
-            });
+        self.render_multispans_macro_backtrace(span, children, backtrace);
+
+        if !backtrace {
+            if external_spans_updated {
+                let msg = format!(
+                    "this {} originates in a macro outside of the current crate \
+                    (in Nightly builds, run with -Z macro-backtrace for more info)",
+                    level,
+                );
+
+                children.push(SubDiagnostic {
+                    level: Level::Note,
+                    message: vec![(msg, Style::NoStyle)],
+                    span: MultiSpan::new(),
+                    render_span: None,
+                });
+            }
         }
     }
 
-    // This "fixes" MultiSpans that contain Spans that are pointing to locations inside of
-    // <*macros>. Since these locations are often difficult to read, we move these Spans from
-    // <*macros> to their corresponding use site.
-    fn fix_multispan_in_std_macros(
+    fn render_multispans_macro_backtrace(
         &self,
-        source_map: &Option<Lrc<SourceMap>>,
         span: &mut MultiSpan,
-        always_backtrace: bool,
-    ) -> bool {
-        let sm = match source_map {
-            Some(ref sm) => sm,
-            None => return false,
-        };
+        children: &mut Vec<SubDiagnostic>,
+        backtrace: bool,
+    ) {
+        self.render_multispan_macro_backtrace(span, backtrace);
+        for child in children.iter_mut() {
+            self.render_multispan_macro_backtrace(&mut child.span, backtrace);
+        }
+    }
 
-        let mut before_after: Vec<(Span, Span)> = vec![];
+    fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
         let mut new_labels: Vec<(Span, String)> = vec![];
 
-        // First, find all the spans in <*macros> and point instead at their use site
-        for sp in span.primary_spans() {
+        for &sp in span.primary_spans() {
             if sp.is_dummy() {
                 continue;
             }
-            let call_sp = sm.call_span_if_macro(*sp);
-            if call_sp != *sp && !always_backtrace {
-                before_after.push((*sp, call_sp));
-            }
-            let backtrace_len = sp.macro_backtrace().len();
-            for (i, trace) in sp.macro_backtrace().iter().rev().enumerate() {
-                // Only show macro locations that are local
-                // and display them like a span_note
-                if trace.def_site_span.is_dummy() {
+
+            // FIXME(eddyb) use `retain` on `macro_backtrace` to remove all the
+            // entries we don't want to print, to make sure the indices being
+            // printed are contiguous (or omitted if there's only one entry).
+            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_span,
+                        trace.def_site,
                         format!(
                             "in this expansion of `{}`{}",
-                            trace.macro_decl_name,
-                            if backtrace_len > 2 {
-                                // if backtrace_len == 1 it'll be pointed
-                                // at by "in this macro invocation"
+                            trace.kind.descr(),
+                            if macro_backtrace.len() > 2 {
+                                // if macro_backtrace.len() == 1 it'll be
+                                // pointed at by "in this macro invocation"
                                 format!(" (#{})", i + 1)
                             } else {
                                 String::new()
-                            }
+                            },
                         ),
                     ));
                 }
-                // Check to make sure we're not in any <*macros>
-                if !sm.span_to_filename(trace.def_site_span).is_macros()
-                    && !trace.macro_decl_name.starts_with("desugaring of ")
-                    && !trace.macro_decl_name.starts_with("#[")
+
+                // Don't add a label on the call site if the diagnostic itself
+                // already points to (a part of) that call site, as the label
+                // is meant for showing the relevant invocation when the actual
+                // diagnostic is pointing to some part of macro definition.
+                //
+                // This also handles the case where an external span got replaced
+                // with the call site span by `fix_multispans_in_extern_macros`.
+                //
+                // NB: `-Zmacro-backtrace` overrides this, for uniformity, as the
+                // "in this expansion of" label above is always added in that mode,
+                // and it needs an "in this macro invocation" label to match that.
+                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 backtrace_len > 2 && always_backtrace {
+                            if macro_backtrace.len() > 2 && always_backtrace {
                                 // only specify order when the macro
                                 // backtrace is multiple levels deep
                                 format!(" (#{})", i + 1)
                             } else {
                                 String::new()
-                            }
+                            },
                         ),
                     ));
-                    if !always_backtrace {
-                        break;
-                    }
+                }
+                if !always_backtrace {
+                    break;
                 }
             }
         }
         for (label_span, label_text) in new_labels {
             span.push_span_label(label_span, label_text);
         }
-        for sp_label in span.span_labels() {
-            if sp_label.span.is_dummy() {
-                continue;
-            }
-            if sm.span_to_filename(sp_label.span.clone()).is_macros() && !always_backtrace {
-                let v = sp_label.span.macro_backtrace();
-                if let Some(use_site) = v.last() {
-                    before_after.push((sp_label.span.clone(), use_site.call_site.clone()));
-                }
-            }
+    }
+
+    // This does a small "fix" for multispans by looking to see if it can find any that
+    // point directly at <*macros>. Since these are often difficult to read, this
+    // will change the span to point at the use site.
+    fn fix_multispans_in_extern_macros(
+        &self,
+        source_map: &Option<Lrc<SourceMap>>,
+        span: &mut MultiSpan,
+        children: &mut Vec<SubDiagnostic>,
+    ) -> bool {
+        let mut spans_updated = self.fix_multispan_in_extern_macros(source_map, span);
+        for child in children.iter_mut() {
+            spans_updated |= self.fix_multispan_in_extern_macros(source_map, &mut child.span);
         }
+        spans_updated
+    }
+
+    // This "fixes" MultiSpans that contain Spans that are pointing to locations inside of
+    // <*macros>. Since these locations are often difficult to read, we move these Spans from
+    // <*macros> to their corresponding use site.
+    fn fix_multispan_in_extern_macros(
+        &self,
+        source_map: &Option<Lrc<SourceMap>>,
+        span: &mut MultiSpan,
+    ) -> bool {
+        let sm = match source_map {
+            Some(ref sm) => sm,
+            None => return false,
+        };
+
+        // First, find all the spans in <*macros> and point instead at their use site
+        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.span_to_filename(sp).is_macros() {
+                    let maybe_callsite = sp.source_callsite();
+                    if sp != maybe_callsite {
+                        return Some((sp, maybe_callsite));
+                    }
+                }
+                None
+            })
+            .collect();
+
         // After we have them, make sure we replace these 'bad' def sites with their use sites
-        let spans_updated = !before_after.is_empty();
-        for (before, after) in before_after {
-            span.replace(before, after);
+        let spans_updated = !replacements.is_empty();
+        for (from, to) in replacements {
+            span.replace(from, to);
         }
 
         spans_updated
@@ -423,12 +456,12 @@ fn emit_diagnostic(&mut self, diag: &Diagnostic) {
         let mut children = diag.children.clone();
         let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
 
-        self.fix_multispans_in_std_macros(
+        self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
             &self.sm,
             &mut primary_span,
             &mut children,
             &diag.level,
-            self.external_macro_backtrace,
+            self.macro_backtrace,
         );
 
         self.emit_messages_default(
@@ -456,9 +489,14 @@ fn source_map(&self) -> Option<&Lrc<SourceMap>> {
     fn emit_diagnostic(&mut self, _: &Diagnostic) {}
 }
 
-/// maximum number of lines we will print for each error; arbitrary.
+/// Maximum number of lines we will print for each error; arbitrary.
 pub const MAX_HIGHLIGHT_LINES: usize = 6;
-/// maximum number of suggestions to be shown
+/// Maximum number of lines we will print for a multiline suggestion; arbitrary.
+///
+/// This should be replaced with a more involved mechanism to output multiline suggestions that
+/// more closely mimmics the regular diagnostic output, where irrelevant code lines are elided.
+pub const MAX_SUGGESTION_HIGHLIGHT_LINES: usize = 6;
+/// Maximum number of suggestions to be shown
 ///
 /// Arbitrary, but taken from trait import suggestion limit
 pub const MAX_SUGGESTIONS: usize = 4;
@@ -502,7 +540,7 @@ pub struct EmitterWriter {
     ui_testing: bool,
     terminal_width: Option<usize>,
 
-    external_macro_backtrace: bool,
+    macro_backtrace: bool,
 }
 
 #[derive(Debug)]
@@ -519,7 +557,7 @@ pub fn stderr(
         short_message: bool,
         teach: bool,
         terminal_width: Option<usize>,
-        external_macro_backtrace: bool,
+        macro_backtrace: bool,
     ) -> EmitterWriter {
         let dst = Destination::from_stderr(color_config);
         EmitterWriter {
@@ -529,7 +567,7 @@ pub fn stderr(
             teach,
             ui_testing: false,
             terminal_width,
-            external_macro_backtrace,
+            macro_backtrace,
         }
     }
 
@@ -540,7 +578,7 @@ pub fn new(
         teach: bool,
         colored: bool,
         terminal_width: Option<usize>,
-        external_macro_backtrace: bool,
+        macro_backtrace: bool,
     ) -> EmitterWriter {
         EmitterWriter {
             dst: Raw(dst, colored),
@@ -549,7 +587,7 @@ pub fn new(
             teach,
             ui_testing: false,
             terminal_width,
-            external_macro_backtrace,
+            macro_backtrace,
         }
     }
 
@@ -1179,13 +1217,13 @@ fn emit_message_default(
             let level_str = level.to_string();
             // The failure note level itself does not provide any useful diagnostic information
             if *level != Level::FailureNote && !level_str.is_empty() {
-                buffer.append(0, &level_str, Style::Level(level.clone()));
+                buffer.append(0, &level_str, Style::Level(*level));
             }
             // only render error codes, not lint codes
             if let Some(DiagnosticId::Error(ref code)) = *code {
-                buffer.append(0, "[", Style::Level(level.clone()));
-                buffer.append(0, &code, Style::Level(level.clone()));
-                buffer.append(0, "]", Style::Level(level.clone()));
+                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);
@@ -1361,7 +1399,7 @@ fn emit_message_default(
                 } else if self.ui_testing {
                     140
                 } else {
-                    term_size::dimensions()
+                    termize::dimensions()
                         .map(|(w, _)| w.saturating_sub(code_offset))
                         .unwrap_or(std::usize::MAX)
                 };
@@ -1490,7 +1528,7 @@ fn emit_suggestion_default(
         // Render the suggestion message
         let level_str = level.to_string();
         if !level_str.is_empty() {
-            buffer.append(0, &level_str, Style::Level(level.clone()));
+            buffer.append(0, &level_str, Style::Level(*level));
             buffer.append(0, ": ", Style::HeaderMsg);
         }
         self.msg_to_buffer(
@@ -1521,7 +1559,7 @@ fn emit_suggestion_default(
             draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
             let mut line_pos = 0;
             let mut lines = complete.lines();
-            for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) {
+            for line in lines.by_ref().take(MAX_SUGGESTION_HIGHLIGHT_LINES) {
                 // Print the span column to avoid confusion
                 buffer.puts(
                     row_num,
@@ -2103,7 +2141,13 @@ fn drop(&mut self) {
 /// Whether the original and suggested code are visually similar enough to warrant extra wording.
 pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
     // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
-    let found = sm.span_to_snippet(sp).unwrap();
+    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'];
     // All the chars that differ in capitalization are confusable (above):
     let confusable = found