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