1use 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::{Translator, to_fluent_args};
31use crate::{
32 CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, Level, MultiSpan, Style, Subdiag,
33 SuggestionStyle, TerminalUrl,
34};
35
36#[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 #[setters(skip)]
43 translator: Translator,
44 short_message: bool,
45 ui_testing: bool,
46 ignored_directories_in_source_blocks: Vec<String>,
47 diagnostic_width: Option<usize>,
48
49 macro_backtrace: bool,
50 track_diagnostics: bool,
51 terminal_url: TerminalUrl,
52 theme: OutputTheme,
53}
54
55impl Debug for AnnotateSnippetEmitter {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("AnnotateSnippetEmitter")
58 .field("short_message", &self.short_message)
59 .field("ui_testing", &self.ui_testing)
60 .field(
61 "ignored_directories_in_source_blocks",
62 &self.ignored_directories_in_source_blocks,
63 )
64 .field("diagnostic_width", &self.diagnostic_width)
65 .field("macro_backtrace", &self.macro_backtrace)
66 .field("track_diagnostics", &self.track_diagnostics)
67 .field("terminal_url", &self.terminal_url)
68 .field("theme", &self.theme)
69 .finish()
70 }
71}
72
73impl Emitter for AnnotateSnippetEmitter {
74 fn emit_diagnostic(&mut self, mut diag: DiagInner) {
76 let fluent_args = to_fluent_args(diag.args.iter());
77
78 if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
79 diag.children.insert(0, diag.emitted_at_sub_diag());
80 }
81
82 let mut suggestions = diag.suggestions.unwrap_tag();
83 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
84
85 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
86 &mut diag.span,
87 &mut diag.children,
88 &diag.level,
89 self.macro_backtrace,
90 );
91
92 self.emit_messages_default(
93 &diag.level,
94 &diag.messages,
95 &fluent_args,
96 &diag.code,
97 &diag.span,
98 &diag.children,
99 suggestions,
100 );
101 }
102
103 fn source_map(&self) -> Option<&SourceMap> {
104 self.sm.as_deref()
105 }
106
107 fn should_show_explain(&self) -> bool {
108 !self.short_message
109 }
110
111 fn translator(&self) -> &Translator {
112 &self.translator
113 }
114
115 fn supports_color(&self) -> bool {
116 false
117 }
118}
119
120fn annotation_level_for_level(level: Level) -> annotate_snippets::level::Level<'static> {
121 match level {
122 Level::Bug | Level::DelayedBug => {
123 annotate_snippets::Level::ERROR.with_name("error: internal compiler error")
124 }
125 Level::Fatal | Level::Error => annotate_snippets::level::ERROR,
126 Level::ForceWarning | Level::Warning => annotate_snippets::Level::WARNING,
127 Level::Note | Level::OnceNote => annotate_snippets::Level::NOTE,
128 Level::Help | Level::OnceHelp => annotate_snippets::Level::HELP,
129 Level::FailureNote => annotate_snippets::Level::NOTE.no_name(),
130 Level::Allow => { ::core::panicking::panic_fmt(format_args!("Should not call with Allow")); }panic!("Should not call with Allow"),
131 Level::Expect => { ::core::panicking::panic_fmt(format_args!("Should not call with Expect")); }panic!("Should not call with Expect"),
132 }
133}
134
135impl AnnotateSnippetEmitter {
136 pub fn new(dst: Destination, translator: Translator) -> Self {
137 Self {
138 dst: IntoDynSyncSend(dst),
139 sm: None,
140 translator,
141 short_message: false,
142 ui_testing: false,
143 ignored_directories_in_source_blocks: Vec::new(),
144 diagnostic_width: None,
145 macro_backtrace: false,
146 track_diagnostics: false,
147 terminal_url: TerminalUrl::No,
148 theme: OutputTheme::Ascii,
149 }
150 }
151
152 fn emit_messages_default(
153 &mut self,
154 level: &Level,
155 msgs: &[(DiagMessage, Style)],
156 args: &FluentArgs<'_>,
157 code: &Option<ErrCode>,
158 msp: &MultiSpan,
159 children: &[Subdiag],
160 suggestions: Vec<CodeSuggestion>,
161 ) {
162 let renderer = self.renderer();
163 let annotation_level = annotation_level_for_level(*level);
164
165 let mut title = if msgs.iter().any(|(_, style)| style != &crate::Style::NoStyle) {
168 annotation_level
169 .clone()
170 .secondary_title(Cow::Owned(self.pre_style_msgs(msgs, *level, args)))
171 } else {
172 annotation_level.clone().primary_title(self.translator.translate_messages(msgs, args))
173 };
174
175 if let Some(c) = code {
176 title = title.id(c.to_string());
177 if let TerminalUrl::Yes = self.terminal_url {
178 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"));
179 }
180 }
181
182 let mut report = ::alloc::vec::Vec::new()vec![];
183 let mut group = Group::with_title(title);
184
185 let Some(sm) = self.sm.as_ref() else {
187 group = group.elements(children.iter().map(|c| {
188 let msg = self.translator.translate_messages(&c.messages, args).to_string();
189 let level = annotation_level_for_level(c.level);
190 level.message(msg)
191 }));
192
193 report.push(group);
194 if let Err(e) = emit_to_destination(
195 renderer.render(&report),
196 level,
197 &mut self.dst,
198 self.short_message,
199 ) {
200 {
::core::panicking::panic_fmt(format_args!("failed to emit error: {0}",
e));
};panic!("failed to emit error: {e}");
201 }
202 return;
203 };
204
205 let mut file_ann = collect_annotations(args, msp, sm, &self.translator);
206
207 let primary_span = msp.primary_span().unwrap_or_default();
209 if !primary_span.is_dummy() {
210 let primary_lo = sm.lookup_char_pos(primary_span.lo());
211 if let Ok(pos) = file_ann.binary_search_by(|(f, _)| f.name.cmp(&primary_lo.file.name)) {
212 file_ann.swap(0, pos);
213 }
214
215 let file_ann_len = file_ann.len();
216 for (file_idx, (file, annotations)) in file_ann.into_iter().enumerate() {
217 if should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file) {
218 if let Some(snippet) = self.annotated_snippet(annotations, &file.name, sm) {
219 group = group.element(snippet);
220 }
221 } else if !self.short_message {
223 group = self.unannotated_messages(
225 annotations,
226 &file.name,
227 sm,
228 file_idx,
229 &mut report,
230 group,
231 &annotation_level,
232 );
233 if let Some(c) = children.first()
242 && (!c.span.has_primary_spans() && !c.span.has_span_labels())
243 && file_idx == file_ann_len - 1
244 {
245 group = group.element(Padding);
246 }
247 }
248 }
249 }
250
251 for c in children {
252 let level = annotation_level_for_level(c.level);
253
254 let msg = if c.messages.iter().any(|(_, style)| style != &crate::Style::NoStyle) {
257 Cow::Owned(self.pre_style_msgs(&c.messages, c.level, args))
258 } else {
259 self.translator.translate_messages(&c.messages, args)
260 };
261
262 if !c.span.has_primary_spans() && !c.span.has_span_labels() {
264 group = group.element(level.clone().message(msg));
265 continue;
266 }
267
268 report.push(std::mem::replace(
269 &mut group,
270 Group::with_title(level.clone().secondary_title(msg)),
271 ));
272
273 let mut file_ann = collect_annotations(args, &c.span, sm, &self.translator);
274 let primary_span = c.span.primary_span().unwrap_or_default();
275 if !primary_span.is_dummy() {
276 let primary_lo = sm.lookup_char_pos(primary_span.lo());
277 if let Ok(pos) =
278 file_ann.binary_search_by(|(f, _)| f.name.cmp(&primary_lo.file.name))
279 {
280 file_ann.swap(0, pos);
281 }
282 }
283
284 for (file_idx, (file, annotations)) in file_ann.into_iter().enumerate() {
285 if should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file) {
286 if let Some(snippet) = self.annotated_snippet(annotations, &file.name, sm) {
287 group = group.element(snippet);
288 }
289 } else if !self.short_message {
291 group = self.unannotated_messages(
293 annotations,
294 &file.name,
295 sm,
296 file_idx,
297 &mut report,
298 group,
299 &level,
300 );
301 }
302 }
303 }
304
305 for suggestion in suggestions {
306 match suggestion.style {
307 SuggestionStyle::CompletelyHidden => {
308 }
310 SuggestionStyle::HideCodeAlways => {
311 let msg = self
312 .translator
313 .translate_messages(&[(suggestion.msg.to_owned(), Style::HeaderMsg)], args);
314 group = group.element(annotate_snippets::Level::HELP.message(msg));
315 }
316 SuggestionStyle::HideCodeInline
317 | SuggestionStyle::ShowCode
318 | SuggestionStyle::ShowAlways => {
319 let substitutions = suggestion
320 .substitutions
321 .into_iter()
322 .filter(|subst| {
323 let invalid =
326 subst.parts.iter().any(|item| sm.is_valid_span(item.span).is_err());
327 if invalid {
328 {
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:328",
"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(328u32),
::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);
329 }
330 !invalid
331 })
332 .filter_map(|mut subst| {
333 subst.parts.sort_by_key(|part| part.span.lo());
336 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!(
338 subst.parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)),
339 None,
340 "all spans must be disjoint",
341 );
342
343 let lo = subst.parts.iter().map(|part| part.span.lo()).min()?;
344 let lo_file = sm.lookup_source_file(lo);
345 let hi = subst.parts.iter().map(|part| part.span.hi()).max()?;
346 let hi_file = sm.lookup_source_file(hi);
347
348 if lo_file.stable_id != hi_file.stable_id {
350 return None;
351 }
352
353 if !sm.ensure_source_file_source_present(&lo_file) {
355 return None;
356 }
357
358 subst.parts.retain(|p| is_different(sm, &p.snippet, p.span));
362
363 if subst.parts.is_empty() { None } else { Some(subst) }
364 })
365 .collect::<Vec<_>>();
366
367 if substitutions.is_empty() {
368 continue;
369 }
370 let mut msg = self
371 .translator
372 .translate_message(&suggestion.msg, args)
373 .map_err(Report::new)
374 .unwrap()
375 .to_string();
376
377 let lo = substitutions
378 .iter()
379 .find_map(|sub| sub.parts.first().map(|p| p.span.lo()))
380 .unwrap();
381 let file = sm.lookup_source_file(lo);
382
383 let filename =
384 sm.filename_for_diagnostics(&file.name).to_string_lossy().to_string();
385
386 let other_suggestions = substitutions.len().saturating_sub(MAX_SUGGESTIONS);
387
388 let subs = substitutions
389 .into_iter()
390 .take(MAX_SUGGESTIONS)
391 .filter_map(|sub| {
392 let mut confusion_type = ConfusionType::None;
393 for part in &sub.parts {
394 let part_confusion =
395 detect_confusion_type(sm, &part.snippet, part.span);
396 confusion_type = confusion_type.combine(part_confusion);
397 }
398
399 if !#[allow(non_exhaustive_omitted_patterns)] match confusion_type {
ConfusionType::None => true,
_ => false,
}matches!(confusion_type, ConfusionType::None) {
400 msg.push_str(confusion_type.label_text());
401 }
402
403 let mut parts = sub
404 .parts
405 .into_iter()
406 .filter_map(|p| {
407 if is_different(sm, &p.snippet, p.span) {
408 Some((p.span, p.snippet))
409 } else {
410 None
411 }
412 })
413 .collect::<Vec<_>>();
414
415 if parts.is_empty() {
416 None
417 } else {
418 let spans = parts.iter().map(|(span, _)| *span).collect::<Vec<_>>();
419 let fold = if let [(p, snippet)] = &mut parts[..]
426 && snippet.trim().starts_with("#[")
427 && snippet.trim().ends_with("]")
429 && snippet.ends_with('\n')
430 && p.hi() == p.lo()
431 && let Ok(b) = sm.span_to_prev_source(*p)
432 && let b = b.rsplit_once('\n').unwrap_or_else(|| ("", &b)).1
433 && b.trim().is_empty()
434 {
435 if !b.is_empty() && !snippet.ends_with(b) {
461 snippet.insert_str(0, b);
462 let offset = BytePos(b.len() as u32);
463 *p = p.with_lo(p.lo() - offset).shrink_to_lo();
464 }
465 false
466 } else {
467 true
468 };
469
470 if let Some((bounding_span, source, line_offset)) =
471 shrink_file(spans.as_slice(), &file.name, sm)
472 {
473 let adj_lo = bounding_span.lo().to_usize();
474 Some(
475 Snippet::source(source)
476 .line_start(line_offset)
477 .path(filename.clone())
478 .fold(fold)
479 .patches(parts.into_iter().map(
480 |(span, replacement)| {
481 let lo =
482 span.lo().to_usize().saturating_sub(adj_lo);
483 let hi =
484 span.hi().to_usize().saturating_sub(adj_lo);
485
486 Patch::new(lo..hi, replacement)
487 },
488 )),
489 )
490 } else {
491 None
492 }
493 }
494 })
495 .collect::<Vec<_>>();
496 if !subs.is_empty() {
497 report.push(std::mem::replace(
498 &mut group,
499 Group::with_title(annotate_snippets::Level::HELP.secondary_title(msg)),
500 ));
501
502 group = group.elements(subs);
503 if other_suggestions > 0 {
504 group = group.element(
505 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!(
506 "and {} other candidate{}",
507 other_suggestions,
508 pluralize!(other_suggestions)
509 )),
510 );
511 }
512 }
513 }
514 }
515 }
516
517 if !group.is_empty() {
518 report.push(group);
519 }
520 if let Err(e) =
521 emit_to_destination(renderer.render(&report), level, &mut self.dst, self.short_message)
522 {
523 {
::core::panicking::panic_fmt(format_args!("failed to emit error: {0}",
e));
};panic!("failed to emit error: {e}");
524 }
525 }
526
527 fn renderer(&self) -> Renderer {
528 let width = if let Some(width) = self.diagnostic_width {
529 width
530 } else if self.ui_testing || falsecfg!(miri) {
531 DEFAULT_TERM_WIDTH
532 } else {
533 termize::dimensions().map(|(w, _)| w).unwrap_or(DEFAULT_TERM_WIDTH)
534 };
535 let decor_style = match self.theme {
536 OutputTheme::Ascii => annotate_snippets::renderer::DecorStyle::Ascii,
537 OutputTheme::Unicode => annotate_snippets::renderer::DecorStyle::Unicode,
538 };
539
540 match self.dst.current_choice() {
541 ColorChoice::AlwaysAnsi | ColorChoice::Always | ColorChoice::Auto => Renderer::styled(),
542 ColorChoice::Never => Renderer::plain(),
543 }
544 .term_width(width)
545 .anonymized_line_numbers(self.ui_testing)
546 .decor_style(decor_style)
547 .short_message(self.short_message)
548 }
549
550 fn pre_style_msgs(
551 &self,
552 msgs: &[(DiagMessage, Style)],
553 level: Level,
554 args: &FluentArgs<'_>,
555 ) -> String {
556 msgs.iter()
557 .filter_map(|(m, style)| {
558 let text = self.translator.translate_message(m, args).map_err(Report::new).unwrap();
559 let style = style.anstyle(level);
560 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:#}")) }
561 })
562 .collect()
563 }
564
565 fn annotated_snippet<'a>(
566 &self,
567 annotations: Vec<Annotation>,
568 file_name: &FileName,
569 sm: &Arc<SourceMap>,
570 ) -> Option<Snippet<'a, annotate_snippets::Annotation<'a>>> {
571 let spans = annotations.iter().map(|a| a.span).collect::<Vec<_>>();
572 if let Some((bounding_span, source, offset_line)) = shrink_file(&spans, file_name, sm) {
573 let adj_lo = bounding_span.lo().to_usize();
574 let filename = sm.filename_for_diagnostics(file_name).to_string_lossy().to_string();
575 Some(Snippet::source(source).line_start(offset_line).path(filename).annotations(
576 annotations.into_iter().map(move |a| {
577 let lo = a.span.lo().to_usize().saturating_sub(adj_lo);
578 let hi = a.span.hi().to_usize().saturating_sub(adj_lo);
579 let ann = a.kind.span(lo..hi);
580 if let Some(label) = a.label { ann.label(label) } else { ann }
581 }),
582 ))
583 } else {
584 None
585 }
586 }
587
588 fn unannotated_messages<'a>(
589 &self,
590 annotations: Vec<Annotation>,
591 file_name: &FileName,
592 sm: &Arc<SourceMap>,
593 file_idx: usize,
594 report: &mut Vec<Group<'a>>,
595 mut group: Group<'a>,
596 level: &annotate_snippets::level::Level<'static>,
597 ) -> Group<'a> {
598 let filename = sm.filename_for_diagnostics(file_name).to_string_lossy().to_string();
599 let mut line_tracker = ::alloc::vec::Vec::new()vec![];
600 for (i, a) in annotations.into_iter().enumerate() {
601 let lo = sm.lookup_char_pos(a.span.lo());
602 let hi = sm.lookup_char_pos(a.span.hi());
603 if i == 0 || (a.label.is_some()) {
604 if i == 0 && file_idx != 0 {
615 report.push(std::mem::replace(&mut group, Group::with_level(level.clone())));
616 }
617
618 if !line_tracker.contains(&lo.line) && (i == 0 || hi.line <= lo.line) {
619 line_tracker.push(lo.line);
620 group = group.element(
625 Origin::path(filename.clone())
626 .line(sm.doctest_offset_line(file_name, lo.line))
627 .char_column(lo.col_display),
628 );
629 }
630
631 if hi.line > lo.line
632 && a.label.as_ref().is_some_and(|l| !l.is_empty())
633 && !line_tracker.contains(&hi.line)
634 {
635 line_tracker.push(hi.line);
636 group = group.element(
641 Origin::path(filename.clone())
642 .line(sm.doctest_offset_line(file_name, hi.line))
643 .char_column(hi.col_display),
644 );
645 }
646
647 if let Some(label) = a.label
648 && !label.is_empty()
649 {
650 group = group
655 .element(Padding)
656 .element(annotate_snippets::Level::NOTE.message(label));
657 }
658 }
659 }
660 group
661 }
662}
663
664fn emit_to_destination(
665 rendered: String,
666 lvl: &Level,
667 dst: &mut Destination,
668 short_message: bool,
669) -> io::Result<()> {
670 use crate::lock;
671 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
672 dst.write_fmt(format_args!("{0}\n", rendered))writeln!(dst, "{rendered}")?;
673 if !short_message && !lvl.is_failure_note() {
674 dst.write_fmt(format_args!("\n"))writeln!(dst)?;
675 }
676 dst.flush()?;
677 Ok(())
678}
679
680#[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)]
681struct Annotation {
682 kind: AnnotationKind,
683 span: Span,
684 label: Option<String>,
685}
686
687fn collect_annotations(
688 args: &FluentArgs<'_>,
689 msp: &MultiSpan,
690 sm: &Arc<SourceMap>,
691 translator: &Translator,
692) -> Vec<(Arc<SourceFile>, Vec<Annotation>)> {
693 let mut output: Vec<(Arc<SourceFile>, Vec<Annotation>)> = ::alloc::vec::Vec::new()vec![];
694
695 for SpanLabel { span, is_primary, label } in msp.span_labels() {
696 let span = match (span.is_dummy(), msp.primary_span()) {
699 (_, None) | (false, _) => span,
700 (true, Some(span)) => span,
701 };
702 let file = sm.lookup_source_file(span.lo());
703
704 let kind = if is_primary { AnnotationKind::Primary } else { AnnotationKind::Context };
705
706 let label = label.as_ref().map(|m| {
707 normalize_whitespace(
708 &translator.translate_message(m, args).map_err(Report::new).unwrap(),
709 )
710 });
711
712 let ann = Annotation { kind, span, label };
713 if sm.is_valid_span(ann.span).is_ok() {
714 if let Some((_, annotations)) =
719 output.iter_mut().find(|(f, _)| f.stable_id == file.stable_id)
720 {
721 annotations.push(ann);
722 } else {
723 output.push((file, <[_]>::into_vec(::alloc::boxed::box_new([ann]))vec![ann]));
724 }
725 }
726 }
727
728 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 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}