Skip to main content

rustc_errors/
annotate_snippet_emitter_writer.rs

1//! Emit diagnostics using the `annotate-snippets` library
2//!
3//! This is the equivalent of `./emitter.rs` but making use of the
4//! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves.
5//!
6//! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/
7
8use std::fmt::Debug;
9use std::io;
10use std::io::Write;
11use std::sync::Arc;
12
13use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
14use annotate_snippets::{AnnotationKind, Group, Origin, Padding, Patch, Renderer, Snippet};
15use anstream::ColorChoice;
16use derive_setters::Setters;
17use rustc_data_structures::sync::IntoDynSyncSend;
18use rustc_error_messages::{DiagArgMap, SpanLabel};
19use rustc_lint_defs::pluralize;
20use rustc_span::source_map::SourceMap;
21use rustc_span::{BytePos, FileName, Pos, SourceFile, Span};
22use tracing::debug;
23
24use crate::emitter::{
25    ConfusionType, Destination, MAX_SUGGESTIONS, OutputTheme, detect_confusion_type, is_different,
26    normalize_whitespace, should_show_source_code,
27};
28use crate::formatting::{format_diag_message, format_diag_messages};
29use crate::{
30    CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, Level, MultiSpan, Style, Subdiag,
31    Sublevel, SuggestionStyle, TerminalUrl,
32};
33
34/// Generates diagnostics using annotate-snippet
35#[derive(impl AnnotateSnippetEmitter {
    #[must_use]
    pub fn sm(mut self, value: Option<Arc<SourceMap>>) -> Self {
        self.sm = value;
        self
    }
    #[must_use]
    pub fn short_message(mut self, value: bool) -> Self {
        self.short_message = value;
        self
    }
    #[must_use]
    pub fn ui_testing(mut self, value: bool) -> Self {
        self.ui_testing = value;
        self
    }
    #[must_use]
    pub fn ignored_directories_in_source_blocks(mut self, value: Vec<String>)
        -> Self {
        self.ignored_directories_in_source_blocks = value;
        self
    }
    #[must_use]
    pub fn diagnostic_width(mut self, value: Option<usize>) -> Self {
        self.diagnostic_width = value;
        self
    }
    #[must_use]
    pub fn macro_backtrace(mut self, value: bool) -> Self {
        self.macro_backtrace = value;
        self
    }
    #[must_use]
    pub fn track_diagnostics(mut self, value: bool) -> Self {
        self.track_diagnostics = value;
        self
    }
    #[must_use]
    pub fn terminal_url(mut self, value: TerminalUrl) -> Self {
        self.terminal_url = value;
        self
    }
    #[must_use]
    pub fn theme(mut self, value: OutputTheme) -> Self {
        self.theme = value;
        self
    }
}Setters)]
36pub struct AnnotateSnippetEmitter {
37    #[setters(skip)]
38    dst: IntoDynSyncSend<Destination>,
39    sm: Option<Arc<SourceMap>>,
40    short_message: bool,
41    ui_testing: bool,
42    ignored_directories_in_source_blocks: Vec<String>,
43    diagnostic_width: Option<usize>,
44    macro_backtrace: bool,
45    track_diagnostics: bool,
46    terminal_url: TerminalUrl,
47    theme: OutputTheme,
48}
49
50impl Debug for AnnotateSnippetEmitter {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        let AnnotateSnippetEmitter {
53            dst,
54            sm,
55            short_message,
56            ui_testing,
57            ignored_directories_in_source_blocks,
58            diagnostic_width,
59            macro_backtrace,
60            track_diagnostics,
61            terminal_url,
62            theme,
63        } = self;
64
65        f.debug_struct("AnnotateSnippetEmitter")
66            .field("dst", &format_args!("<writer@{0:p}>", dst)format_args!("<writer@{dst:p}>"))
67            .field("sm", sm)
68            .field("short_message", short_message)
69            .field("ui_testing", ui_testing)
70            .field("ignored_directories_in_source_blocks", ignored_directories_in_source_blocks)
71            .field("diagnostic_width", diagnostic_width)
72            .field("macro_backtrace", macro_backtrace)
73            .field("track_diagnostics", track_diagnostics)
74            .field("terminal_url", terminal_url)
75            .field("theme", theme)
76            .finish()
77    }
78}
79
80impl Emitter for AnnotateSnippetEmitter {
81    /// The entry point for the diagnostics generation
82    fn emit_diagnostic(&mut self, mut diag: DiagInner) {
83        if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
84            diag.children.insert(0, diag.emitted_at_sub_diag());
85        }
86
87        let mut suggestions = diag.suggestions.unwrap_tag();
88        self.primary_span_formatted(&mut diag.span, &mut suggestions, &diag.args);
89
90        self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
91            &mut diag.span,
92            &mut diag.children,
93            &diag.level,
94            self.macro_backtrace,
95        );
96
97        self.emit_messages_default(
98            &diag.level,
99            &diag.messages,
100            &diag.args,
101            &diag.code,
102            &diag.span,
103            &diag.children,
104            suggestions,
105        );
106    }
107
108    fn source_map(&self) -> Option<&SourceMap> {
109        self.sm.as_deref()
110    }
111
112    fn should_show_explain(&self) -> bool {
113        !self.short_message
114    }
115
116    fn supports_color(&self) -> bool {
117        false
118    }
119}
120
121fn annotation_level_for_level(level: Level) -> annotate_snippets::level::Level<'static> {
122    match level {
123        Level::Bug | Level::DelayedBug => {
124            annotate_snippets::Level::ERROR.with_name("error: internal compiler error")
125        }
126        Level::Fatal | Level::Error => annotate_snippets::level::ERROR,
127        Level::Warning(_) => annotate_snippets::Level::WARNING,
128        Level::Note => annotate_snippets::Level::NOTE,
129        Level::Help => annotate_snippets::Level::HELP,
130        Level::FailureNote => annotate_snippets::Level::NOTE.no_name(),
131    }
132}
133
134fn annotation_level_for_sublevel(level: Sublevel) -> annotate_snippets::level::Level<'static> {
135    match level {
136        Sublevel::Error => annotate_snippets::Level::ERROR,
137        Sublevel::Warning => annotate_snippets::Level::WARNING,
138        Sublevel::Note | Sublevel::OnceNote => annotate_snippets::Level::NOTE,
139        Sublevel::Help | Sublevel::OnceHelp => annotate_snippets::Level::HELP,
140    }
141}
142
143impl AnnotateSnippetEmitter {
144    pub fn new(dst: Destination) -> Self {
145        Self {
146            dst: IntoDynSyncSend(dst),
147            sm: None,
148            short_message: false,
149            ui_testing: false,
150            ignored_directories_in_source_blocks: Vec::new(),
151            diagnostic_width: None,
152            macro_backtrace: false,
153            track_diagnostics: false,
154            terminal_url: TerminalUrl::No,
155            theme: OutputTheme::Ascii,
156        }
157    }
158
159    fn emit_messages_default(
160        &mut self,
161        level: &Level,
162        msgs: &[(DiagMessage, Style)],
163        args: &DiagArgMap,
164        code: &Option<ErrCode>,
165        msp: &MultiSpan,
166        children: &[Subdiag],
167        suggestions: Vec<CodeSuggestion>,
168    ) {
169        let renderer = self.renderer();
170        let annotation_level = annotation_level_for_level(*level);
171
172        // If at least one portion of the message is styled, we need to
173        // "pre-style" the message
174        let mut title = if msgs.iter().any(|(_, style)| style != &crate::Style::NoStyle) {
175            annotation_level.clone().secondary_title(self.pre_style_msgs(msgs, args))
176        } else {
177            annotation_level.clone().primary_title(format_diag_messages(msgs, args))
178        };
179
180        if let Some(c) = code {
181            title = title.id(c.to_string());
182            if let TerminalUrl::Yes = self.terminal_url {
183                title = title.id_url(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("https://doc.rust-lang.org/error_codes/{0}.html",
                c))
    })format!("https://doc.rust-lang.org/error_codes/{c}.html"));
184            }
185        }
186
187        let mut report = ::alloc::vec::Vec::new()vec![];
188        let mut group = Group::with_title(title);
189
190        // If we don't have span information, emit and exit
191        let Some(sm) = self.sm.as_ref() else {
192            group = group.elements(children.iter().map(|c| {
193                let msg = format_diag_messages(&c.messages, args);
194                let level = annotation_level_for_sublevel(c.level);
195                level.message(msg)
196            }));
197
198            report.push(group);
199            if let Err(e) = emit_to_destination(
200                renderer.render(&report),
201                level,
202                &mut self.dst,
203                self.short_message,
204            ) {
205                {
    ::core::panicking::panic_fmt(format_args!("failed to emit error: {0}",
            e));
};panic!("failed to emit error: {e}");
206            }
207            return;
208        };
209
210        let mut file_ann = collect_annotations(args, msp, sm);
211
212        // Make sure our primary file comes first
213        let primary_span = msp.primary_span().unwrap_or_default();
214        if !primary_span.is_dummy() {
215            let primary_lo = sm.lookup_char_pos(primary_span.lo());
216            if let Ok(pos) = file_ann.binary_search_by(|(f, _)| f.name.cmp(&primary_lo.file.name)) {
217                file_ann.swap(0, pos);
218            }
219
220            let file_ann_len = file_ann.len();
221            for (file_idx, (file, annotations)) in file_ann.into_iter().enumerate() {
222                if should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file) {
223                    if let Some(snippet) = self.annotated_snippet(annotations, &file.name, sm) {
224                        group = group.element(snippet);
225                    }
226                // we can't annotate anything if the source is unavailable.
227                } else if !self.short_message {
228                    // We'll just print unannotated messages
229                    group = self.unannotated_messages(
230                        annotations,
231                        &file.name,
232                        sm,
233                        file_idx,
234                        &mut report,
235                        group,
236                        &annotation_level,
237                    );
238                    // If this is the last annotation for a file, and
239                    // this is the last file, and the first child is a
240                    // "secondary" message, we need to add padding
241                    // ╭▸ /rustc/FAKE_PREFIX/library/core/src/clone.rs:236:13
242                    // │
243                    // ├ note: the late bound lifetime parameter
244                    // │ (<- It adds *this*)
245                    // ╰ warning: this was previously accepted
246                    if let Some(c) = children.first()
247                        && (!c.span.has_primary_spans() && !c.span.has_span_labels())
248                        && file_idx == file_ann_len - 1
249                    {
250                        group = group.element(Padding);
251                    }
252                }
253            }
254        }
255
256        for c in children {
257            let level = annotation_level_for_sublevel(c.level);
258
259            // If at least one portion of the message is styled, we need to
260            // "pre-style" the message
261            let msg = if c.messages.iter().any(|(_, style)| style != &crate::Style::NoStyle) {
262                self.pre_style_msgs(&c.messages, args)
263            } else {
264                format_diag_messages(&c.messages, args)
265            };
266
267            // This is a secondary message with no span info
268            if !c.span.has_primary_spans() && !c.span.has_span_labels() {
269                group = group.element(level.clone().message(msg));
270                continue;
271            }
272
273            report.push(std::mem::replace(
274                &mut group,
275                Group::with_title(level.clone().secondary_title(msg)),
276            ));
277
278            let mut file_ann = collect_annotations(args, &c.span, sm);
279            let primary_span = c.span.primary_span().unwrap_or_default();
280            if !primary_span.is_dummy() {
281                let primary_lo = sm.lookup_char_pos(primary_span.lo());
282                if let Ok(pos) =
283                    file_ann.binary_search_by(|(f, _)| f.name.cmp(&primary_lo.file.name))
284                {
285                    file_ann.swap(0, pos);
286                }
287            }
288
289            for (file_idx, (file, annotations)) in file_ann.into_iter().enumerate() {
290                if should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file) {
291                    if let Some(snippet) = self.annotated_snippet(annotations, &file.name, sm) {
292                        group = group.element(snippet);
293                    }
294                // we can't annotate anything if the source is unavailable.
295                } else if !self.short_message {
296                    // We'll just print unannotated messages
297                    group = self.unannotated_messages(
298                        annotations,
299                        &file.name,
300                        sm,
301                        file_idx,
302                        &mut report,
303                        group,
304                        &level,
305                    );
306                }
307            }
308        }
309
310        for suggestion in suggestions {
311            match suggestion.style {
312                SuggestionStyle::CompletelyHidden => {
313                    // do not display this suggestion, it is meant only for tools
314                }
315                SuggestionStyle::HideCodeAlways => {
316                    let msg = format_diag_message(&suggestion.msg, args).into_owned();
317                    group = group.element(annotate_snippets::Level::HELP.message(msg));
318                }
319                SuggestionStyle::HideCodeInline
320                | SuggestionStyle::ShowCode
321                | SuggestionStyle::ShowAlways => {
322                    let substitutions = suggestion
323                        .substitutions
324                        .into_iter()
325                        .filter(|subst| {
326                            // Suggestions coming from macros can have malformed spans. This is a heavy
327                            // handed approach to avoid ICEs by ignoring the suggestion outright.
328                            let invalid =
329                                subst.parts.iter().any(|item| sm.is_valid_span(item.span).is_err());
330                            if invalid {
331                                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs:331",
                        "rustc_errors::annotate_snippet_emitter_writer",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs"),
                        ::tracing_core::__macro_support::Option::Some(331u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_errors::annotate_snippet_emitter_writer"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("suggestion contains an invalid span: {0:?}",
                                                    subst) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("suggestion contains an invalid span: {:?}", subst);
332                            }
333                            !invalid
334                        })
335                        .filter_map(|mut subst| {
336                            // Assumption: all spans are in the same file, and all spans
337                            // are disjoint. Sort in ascending order.
338                            subst.parts.sort_by_key(|part| part.span.lo());
339                            // Verify the assumption that all spans are disjoint
340                            if true {
    {
        match (&subst.parts.array_windows().find(|[a, b]|
                            a.span.overlaps(b.span)), &None) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val,
                        ::core::option::Option::Some(format_args!("all spans must be disjoint")));
                }
            }
        }
    };
};debug_assert_eq!(
341                                subst.parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)),
342                                None,
343                                "all spans must be disjoint",
344                            );
345
346                            let lo = subst.parts.iter().map(|part| part.span.lo()).min()?;
347                            let lo_file = sm.lookup_source_file(lo);
348                            let hi = subst.parts.iter().map(|part| part.span.hi()).max()?;
349                            let hi_file = sm.lookup_source_file(hi);
350
351                            // The different spans might belong to different contexts, if so ignore suggestion.
352                            if lo_file.stable_id != hi_file.stable_id {
353                                return None;
354                            }
355
356                            // We can't splice anything if the source is unavailable.
357                            if !sm.ensure_source_file_source_present(&lo_file) {
358                                return None;
359                            }
360
361                            // Account for cases where we are suggesting the same code that's already
362                            // there. This shouldn't happen often, but in some cases for multipart
363                            // suggestions it's much easier to handle it here than in the origin.
364                            subst.parts.retain(|p| is_different(sm, &p.snippet, p.span));
365
366                            if subst.parts.is_empty() { None } else { Some(subst) }
367                        })
368                        .collect::<Vec<_>>();
369
370                    if substitutions.is_empty() {
371                        continue;
372                    }
373                    let mut msg = format_diag_message(&suggestion.msg, args).to_string();
374
375                    let lo = substitutions
376                        .iter()
377                        .find_map(|sub| sub.parts.first().map(|p| p.span.lo()))
378                        .unwrap();
379                    let file = sm.lookup_source_file(lo);
380
381                    let filename =
382                        sm.filename_for_diagnostics(&file.name).to_string_lossy().to_string();
383
384                    let other_suggestions = substitutions.len().saturating_sub(MAX_SUGGESTIONS);
385
386                    let subs = substitutions
387                        .into_iter()
388                        .take(MAX_SUGGESTIONS)
389                        .filter_map(|sub| {
390                            let mut confusion_type = ConfusionType::None;
391                            for part in &sub.parts {
392                                let part_confusion =
393                                    detect_confusion_type(sm, &part.snippet, part.span);
394                                confusion_type = confusion_type.combine(part_confusion);
395                            }
396
397                            if !#[allow(non_exhaustive_omitted_patterns)] match confusion_type {
    ConfusionType::None => true,
    _ => false,
}matches!(confusion_type, ConfusionType::None) {
398                                msg.push_str(confusion_type.label_text());
399                            }
400
401                            let mut parts = sub
402                                .parts
403                                .into_iter()
404                                .filter_map(|p| {
405                                    if is_different(sm, &p.snippet, p.span) {
406                                        Some((p.span, p.snippet))
407                                    } else {
408                                        None
409                                    }
410                                })
411                                .collect::<Vec<_>>();
412
413                            if parts.is_empty() {
414                                None
415                            } else {
416                                let spans = parts.iter().map(|(span, _)| *span).collect::<Vec<_>>();
417                                // The suggestion adds an entire line of code, ending on a newline, so we'll also
418                                // print the *following* line, to provide context of what we're advising people to
419                                // do. Otherwise you would only see contextless code that can be confused for
420                                // already existing code, despite the colors and UI elements.
421                                // We special case `#[derive(_)]\n` and other attribute suggestions, because those
422                                // are the ones where context is most useful.
423                                let fold = if let [(p, snippet)] = &mut parts[..]
424                                    && snippet.trim().starts_with("#[")
425                                    // This allows for spaces to come between the attribute and the newline
426                                    && snippet.trim().ends_with("]")
427                                    && snippet.ends_with('\n')
428                                    && p.hi() == p.lo()
429                                    && let Ok(b) = sm.span_to_prev_source(*p)
430                                    && let b = b.rsplit_once('\n').unwrap_or_else(|| ("", &b)).1
431                                    && b.trim().is_empty()
432                                {
433                                    // FIXME: This is a hack:
434                                    // The span for attribute suggestions often times points to the
435                                    // beginning of an item, disregarding leading whitespace. This
436                                    // causes the attribute to be properly indented, but leaves original
437                                    // item without indentation when rendered.
438                                    // This fixes that problem by adjusting the span to point to the start
439                                    // of the whitespace, and adds the whitespace to the replacement.
440                                    //
441                                    // Source: "    extern "custom" fn negate(a: i64) -> i64 {\n"
442                                    // Span: 4..4
443                                    // Replacement: "#[unsafe(naked)]\n"
444                                    //
445                                    // Before:
446                                    // help: convert this to an `#[unsafe(naked)]` function
447                                    //    |
448                                    // LL +     #[unsafe(naked)]
449                                    // LL | extern "custom" fn negate(a: i64) -> i64 {
450                                    //    |
451                                    //
452                                    // After
453                                    // help: convert this to an `#[unsafe(naked)]` function
454                                    //    |
455                                    // LL +     #[unsafe(naked)]
456                                    // LL |     extern "custom" fn negate(a: i64) -> i64 {
457                                    //    |
458                                    if !b.is_empty() && !snippet.ends_with(b) {
459                                        snippet.insert_str(0, b);
460                                        let offset = BytePos(b.len() as u32);
461                                        *p = p.with_lo(p.lo() - offset).shrink_to_lo();
462                                    }
463                                    false
464                                } else {
465                                    true
466                                };
467
468                                if let Some((bounding_span, source, line_offset)) =
469                                    shrink_file(spans.as_slice(), &file.name, sm)
470                                {
471                                    let adj_lo = bounding_span.lo().to_usize();
472                                    Some(
473                                        Snippet::source(source)
474                                            .line_start(line_offset)
475                                            .path(filename.clone())
476                                            .fold(fold)
477                                            .patches(parts.into_iter().map(
478                                                |(span, replacement)| {
479                                                    let lo =
480                                                        span.lo().to_usize().saturating_sub(adj_lo);
481                                                    let hi =
482                                                        span.hi().to_usize().saturating_sub(adj_lo);
483
484                                                    Patch::new(lo..hi, replacement)
485                                                },
486                                            )),
487                                    )
488                                } else {
489                                    None
490                                }
491                            }
492                        })
493                        .collect::<Vec<_>>();
494                    if !subs.is_empty() {
495                        report.push(std::mem::replace(
496                            &mut group,
497                            Group::with_title(annotate_snippets::Level::HELP.secondary_title(msg)),
498                        ));
499
500                        group = group.elements(subs);
501                        if other_suggestions > 0 {
502                            group = group.element(
503                                annotate_snippets::Level::NOTE.no_name().message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("and {0} other candidate{1}",
                other_suggestions,
                if other_suggestions == 1 { "" } else { "s" }))
    })format!(
504                                    "and {} other candidate{}",
505                                    other_suggestions,
506                                    pluralize!(other_suggestions)
507                                )),
508                            );
509                        }
510                    }
511                }
512            }
513        }
514
515        if !group.is_empty() {
516            report.push(group);
517        }
518        if let Err(e) =
519            emit_to_destination(renderer.render(&report), level, &mut self.dst, self.short_message)
520        {
521            {
    ::core::panicking::panic_fmt(format_args!("failed to emit error: {0}",
            e));
};panic!("failed to emit error: {e}");
522        }
523    }
524
525    fn renderer(&self) -> Renderer {
526        let width = if let Some(width) = self.diagnostic_width {
527            width
528        } else if self.ui_testing || falsecfg!(miri) {
529            DEFAULT_TERM_WIDTH
530        } else {
531            termize::dimensions().map(|(w, _)| w).unwrap_or(DEFAULT_TERM_WIDTH)
532        };
533        let decor_style = match self.theme {
534            OutputTheme::Ascii => annotate_snippets::renderer::DecorStyle::Ascii,
535            OutputTheme::Unicode => annotate_snippets::renderer::DecorStyle::Unicode,
536        };
537
538        match self.dst.current_choice() {
539            ColorChoice::AlwaysAnsi | ColorChoice::Always | ColorChoice::Auto => Renderer::styled(),
540            ColorChoice::Never => Renderer::plain(),
541        }
542        .term_width(width)
543        .anonymized_line_numbers(self.ui_testing)
544        .decor_style(decor_style)
545        .short_message(self.short_message)
546    }
547
548    fn pre_style_msgs(&self, msgs: &[(DiagMessage, Style)], args: &DiagArgMap) -> String {
549        msgs.iter()
550            .filter_map(|(m, style)| {
551                let text = format_diag_message(m, args);
552                let style = style.anstyle();
553                if text.is_empty() { None } else { Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}{0:#}", style, text))
    })format!("{style}{text}{style:#}")) }
554            })
555            .collect()
556    }
557
558    fn annotated_snippet<'a>(
559        &self,
560        annotations: Vec<Annotation>,
561        file_name: &FileName,
562        sm: &Arc<SourceMap>,
563    ) -> Option<Snippet<'a, annotate_snippets::Annotation<'a>>> {
564        let spans = annotations.iter().map(|a| a.span).collect::<Vec<_>>();
565        if let Some((bounding_span, source, offset_line)) = shrink_file(&spans, file_name, sm) {
566            let adj_lo = bounding_span.lo().to_usize();
567            let filename = sm.filename_for_diagnostics(file_name).to_string_lossy().to_string();
568            Some(Snippet::source(source).line_start(offset_line).path(filename).annotations(
569                annotations.into_iter().map(move |a| {
570                    let lo = a.span.lo().to_usize().saturating_sub(adj_lo);
571                    let hi = a.span.hi().to_usize().saturating_sub(adj_lo);
572                    let ann = a.kind.span(lo..hi);
573                    if let Some(label) = a.label { ann.label(label) } else { ann }
574                }),
575            ))
576        } else {
577            None
578        }
579    }
580
581    fn unannotated_messages<'a>(
582        &self,
583        annotations: Vec<Annotation>,
584        file_name: &FileName,
585        sm: &Arc<SourceMap>,
586        file_idx: usize,
587        report: &mut Vec<Group<'a>>,
588        mut group: Group<'a>,
589        level: &annotate_snippets::level::Level<'static>,
590    ) -> Group<'a> {
591        let filename = sm.filename_for_diagnostics(file_name).to_string_lossy().to_string();
592        let mut line_tracker = ::alloc::vec::Vec::new()vec![];
593        for (i, a) in annotations.into_iter().enumerate() {
594            let lo = sm.lookup_char_pos(a.span.lo());
595            let hi = sm.lookup_char_pos(a.span.hi());
596            if i == 0 || (a.label.is_some()) {
597                // Render each new file after the first in its own Group
598                //    ╭▸ $DIR/deriving-meta-unknown-trait.rs:1:10
599                //    │
600                // LL │ #[derive(Eqr)]
601                //    │          ━━━
602                //    ╰╴ (<- It makes it so *this* will get printed)
603                //    ╭▸ $SRC_DIR/core/src/option.rs:594:0
604                //    ⸬  $SRC_DIR/core/src/option.rs:602:4
605                //    │
606                //    ╰ note: not covered
607                if i == 0 && file_idx != 0 {
608                    report.push(std::mem::replace(&mut group, Group::with_level(level.clone())));
609                }
610
611                if !line_tracker.contains(&lo.line) && (i == 0 || hi.line <= lo.line) {
612                    line_tracker.push(lo.line);
613                    // ╭▸ $SRC_DIR/core/src/option.rs:594:0 (<- It adds *this*)
614                    // ⸬  $SRC_DIR/core/src/option.rs:602:4
615                    // │
616                    // ╰ note: not covered
617                    group = group.element(
618                        Origin::path(filename.clone())
619                            .line(sm.doctest_offset_line(file_name, lo.line))
620                            .char_column(lo.col_display),
621                    );
622                }
623
624                if hi.line > lo.line
625                    && a.label.as_ref().is_some_and(|l| !l.is_empty())
626                    && !line_tracker.contains(&hi.line)
627                {
628                    line_tracker.push(hi.line);
629                    // ╭▸ $SRC_DIR/core/src/option.rs:594:0
630                    // ⸬  $SRC_DIR/core/src/option.rs:602:4 (<- It adds *this*)
631                    // │
632                    // ╰ note: not covered
633                    group = group.element(
634                        Origin::path(filename.clone())
635                            .line(sm.doctest_offset_line(file_name, hi.line))
636                            .char_column(hi.col_display),
637                    );
638                }
639
640                if let Some(label) = a.label
641                    && !label.is_empty()
642                {
643                    // ╭▸ $SRC_DIR/core/src/option.rs:594:0
644                    // ⸬  $SRC_DIR/core/src/option.rs:602:4
645                    // │ (<- It adds *this*)
646                    // ╰ note: not covered (<- and *this*)
647                    group = group
648                        .element(Padding)
649                        .element(annotate_snippets::Level::NOTE.message(label));
650                }
651            }
652        }
653        group
654    }
655}
656
657fn emit_to_destination(
658    rendered: String,
659    lvl: &Level,
660    dst: &mut Destination,
661    short_message: bool,
662) -> io::Result<()> {
663    use crate::lock;
664    let _buffer_lock = lock::acquire_global_lock("rustc_errors");
665    dst.write_fmt(format_args!("{0}\n", rendered))writeln!(dst, "{rendered}")?;
666    if !short_message && !lvl.is_failure_note() {
667        dst.write_fmt(format_args!("\n"))writeln!(dst)?;
668    }
669    dst.flush()?;
670    Ok(())
671}
672
673#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Annotation {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "Annotation",
            "kind", &self.kind, "span", &self.span, "label", &&self.label)
    }
}Debug)]
674struct Annotation {
675    kind: AnnotationKind,
676    span: Span,
677    label: Option<String>,
678}
679
680fn collect_annotations(
681    args: &DiagArgMap,
682    msp: &MultiSpan,
683    sm: &Arc<SourceMap>,
684) -> Vec<(Arc<SourceFile>, Vec<Annotation>)> {
685    let mut output: Vec<(Arc<SourceFile>, Vec<Annotation>)> = ::alloc::vec::Vec::new()vec![];
686
687    for SpanLabel { span, is_primary, label } in msp.span_labels() {
688        // If we don't have a useful span, pick the primary span if that exists.
689        // Worst case we'll just print an error at the top of the main file.
690        let span = match (span.is_dummy(), msp.primary_span()) {
691            (_, None) | (false, _) => span,
692            (true, Some(span)) => span,
693        };
694        let file = sm.lookup_source_file(span.lo());
695
696        let kind = if is_primary { AnnotationKind::Primary } else { AnnotationKind::Context };
697
698        let label = label.as_ref().map(|m| normalize_whitespace(&format_diag_message(m, args)));
699
700        let ann = Annotation { kind, span, label };
701        if sm.is_valid_span(ann.span).is_ok() {
702            // Look through each of our files for the one we're adding to. We
703            // use each files `stable_id` to avoid issues with file name
704            // collisions when multiple versions of the same crate are present
705            // in the dependency graph
706            if let Some((_, annotations)) =
707                output.iter_mut().find(|(f, _)| f.stable_id == file.stable_id)
708            {
709                annotations.push(ann);
710            } else {
711                output.push((file, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ann]))vec![ann]));
712            }
713        }
714    }
715
716    for span in msp.span_context() {
717        let file = sm.lookup_source_file(span.lo());
718        let ann = Annotation { kind: AnnotationKind::Visible, span: *span, label: None };
719        if let Some((_, annotations)) =
720            output.iter_mut().find(|(f, _)| f.stable_id == file.stable_id)
721        {
722            annotations.push(ann);
723        } else {
724            output.push((file, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ann]))vec![ann]));
725        }
726    }
727
728    // Sort annotations within each file by line number
729    for (_, ann) in output.iter_mut() {
730        ann.sort_by_key(|a| {
731            let lo = sm.lookup_char_pos(a.span.lo());
732            lo.line
733        });
734    }
735    output
736}
737
738fn shrink_file(
739    spans: &[Span],
740    file_name: &FileName,
741    sm: &Arc<SourceMap>,
742) -> Option<(Span, String, usize)> {
743    let lo_byte = spans.iter().map(|s| s.lo()).min()?;
744    let lo_loc = sm.lookup_char_pos(lo_byte);
745
746    let hi_byte = spans.iter().map(|s| s.hi()).max()?;
747    let hi_loc = sm.lookup_char_pos(hi_byte);
748
749    if lo_loc.file.stable_id != hi_loc.file.stable_id {
750        // this may happen when spans cross file boundaries due to macro expansion.
751        return None;
752    }
753
754    let lo = lo_loc.file.line_bounds(lo_loc.line.saturating_sub(1)).start;
755    let hi = hi_loc.file.line_bounds(hi_loc.line.saturating_sub(1)).end;
756
757    let bounding_span = Span::with_root_ctxt(lo, hi);
758    let source = sm.span_to_snippet(bounding_span).ok()?;
759    let offset_line = sm.doctest_offset_line(file_name, lo_loc.line);
760
761    Some((bounding_span, source, offset_line))
762}