1use core::fmt;
4
5use std::io;
6
7use codespan_reporting::diagnostic as d;
8use codespan_reporting::term;
9pub use codespan_reporting::term::termcolor;
10use codespan_reporting::term::termcolor::WriteColor;
11
12use crate::alloc::fmt::TryWrite;
13use crate::alloc::prelude::*;
14use crate::alloc::{self, String};
15use crate::ast::{Span, Spanned};
16use crate::compile::{ErrorKind, LinkerError, Location};
17use crate::diagnostics::{
18 Diagnostic, FatalDiagnostic, FatalDiagnosticKind, RuntimeWarningDiagnostic,
19 RuntimeWarningDiagnosticKind, WarningDiagnostic, WarningDiagnosticKind,
20};
21use crate::hash::Hash;
22use crate::runtime::DebugInfo;
23use crate::runtime::{DebugInst, Protocol, Unit, VmError, VmErrorAt, VmErrorKind};
24use crate::Context;
25use crate::{Diagnostics, Source, SourceId, Sources};
26
27struct StackFrame {
28 source_id: SourceId,
29 span: Span,
30}
31
32#[derive(Debug)]
34#[non_exhaustive]
35pub enum EmitError {
36 Io(io::Error),
38 Alloc(alloc::Error),
40 CodespanReporting(codespan_reporting::files::Error),
42}
43
44impl fmt::Display for EmitError {
45 #[inline]
46 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47 match self {
48 EmitError::Io(error) => error.fmt(f),
49 EmitError::Alloc(error) => error.fmt(f),
50 EmitError::CodespanReporting(error) => error.fmt(f),
51 }
52 }
53}
54
55impl From<io::Error> for EmitError {
56 fn from(error: io::Error) -> Self {
57 EmitError::Io(error)
58 }
59}
60
61impl From<alloc::Error> for EmitError {
62 fn from(error: alloc::Error) -> Self {
63 EmitError::Alloc(error)
64 }
65}
66
67impl From<codespan_reporting::files::Error> for EmitError {
68 fn from(error: codespan_reporting::files::Error) -> Self {
69 EmitError::CodespanReporting(error)
70 }
71}
72
73impl core::error::Error for EmitError {
74 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
75 match self {
76 EmitError::Io(error) => Some(error),
77 EmitError::Alloc(error) => Some(error),
78 EmitError::CodespanReporting(error) => Some(error),
79 }
80 }
81}
82
83impl Diagnostics {
84 pub fn emit<O>(&self, out: &mut O, sources: &Sources) -> Result<(), EmitError>
89 where
90 O: WriteColor,
91 {
92 if self.is_empty() {
93 return Ok(());
94 }
95
96 let config = term::Config::default();
97
98 for diagnostic in self.diagnostics() {
99 match diagnostic {
100 Diagnostic::Fatal(e) => {
101 fatal_diagnostics_emit(e, out, sources, &config)?;
102 }
103 Diagnostic::Warning(w) => {
104 warning_diagnostics_emit(w, out, sources, &config)?;
105 }
106 Diagnostic::RuntimeWarning(w) => {
107 runtime_warning_diagnostics_emit(w, out, sources, &config, None, None)?;
108 }
109 }
110 }
111
112 Ok(())
113 }
114
115 pub fn emit_detailed<O>(
120 &self,
121 out: &mut O,
122 sources: &Sources,
123 unit: &Unit,
124 context: &Context,
125 ) -> Result<(), EmitError>
126 where
127 O: WriteColor,
128 {
129 if self.is_empty() {
130 return Ok(());
131 }
132
133 let debug_info = unit.debug_info();
134
135 let config = term::Config::default();
136
137 for diagnostic in self.diagnostics() {
138 match diagnostic {
139 Diagnostic::Fatal(e) => {
140 fatal_diagnostics_emit(e, out, sources, &config)?;
141 }
142 Diagnostic::Warning(w) => {
143 warning_diagnostics_emit(w, out, sources, &config)?;
144 }
145 Diagnostic::RuntimeWarning(w) => {
146 runtime_warning_diagnostics_emit(
147 w,
148 out,
149 sources,
150 &config,
151 debug_info,
152 Some(context),
153 )?;
154 }
155 }
156 }
157
158 Ok(())
159 }
160}
161
162impl VmError {
163 pub fn emit<O>(&self, out: &mut O, sources: &Sources) -> Result<(), EmitError>
168 where
169 O: WriteColor,
170 {
171 let mut red = termcolor::ColorSpec::new();
172 red.set_fg(Some(termcolor::Color::Red));
173
174 let mut backtrace = vec![];
175 let config = term::Config::default();
176
177 for l in &self.inner.stacktrace {
178 let debug_info = match l.unit.debug_info() {
179 Some(debug_info) => debug_info,
180 None => continue,
181 };
182
183 for ip in [l.ip]
184 .into_iter()
185 .chain(l.frames.iter().rev().map(|v| v.ip))
186 {
187 let debug_inst = match debug_info.instruction_at(ip) {
188 Some(debug_inst) => debug_inst,
189 None => continue,
190 };
191
192 let source_id = debug_inst.source_id;
193 let span = debug_inst.span;
194
195 backtrace.push(StackFrame { source_id, span });
196 }
197 }
198
199 let mut labels = ::rust_alloc::vec::Vec::new();
200 let mut notes = ::rust_alloc::vec::Vec::new();
201
202 let get = |at: &VmErrorAt| -> Option<&DebugInst> {
203 let l = self.inner.stacktrace.get(at.index())?;
204 let debug_info = l.unit.debug_info()?;
205 let debug_inst = debug_info.instruction_at(l.ip)?;
206 Some(debug_inst)
207 };
208
209 let get_ident = |at: &VmErrorAt, hash: Hash| {
210 let l = self.inner.stacktrace.get(at.index())?;
211 let debug_info = l.unit.debug_info()?;
212 debug_info.ident_for_hash(hash)
213 };
214
215 for at in &self.inner.chain {
216 match at.kind() {
218 VmErrorKind::UnsupportedBinaryOperation { lhs, rhs, .. } => {
219 notes.extend(vec![
220 format!("Left hand side has type `{}`", lhs),
221 format!("Right hand side has type `{}`", rhs),
222 ]);
223 }
224 VmErrorKind::BadArgumentCount { actual, expected } => {
225 notes.extend([
226 format!("Expected `{}`", expected),
227 format!("Got `{}`", actual),
228 ]);
229 }
230 _ => {}
231 };
232
233 if let Some(&DebugInst {
234 source_id, span, ..
235 }) = get(at)
236 {
237 labels.push(
238 d::Label::primary(source_id, span.range()).with_message(at.try_to_string()?),
239 );
240 }
241 }
242
243 if let Some(&DebugInst {
244 source_id, span, ..
245 }) = get(&self.inner.error)
246 {
247 labels.push(
248 d::Label::primary(source_id, span.range())
249 .with_message(self.inner.error.try_to_string()?),
250 );
251 };
252
253 for at in [&self.inner.error].into_iter().chain(&self.inner.chain) {
254 if let VmErrorKind::MissingInstanceFunction { hash, instance } = at.kind() {
256 if let Some(&DebugInst {
263 source_id, span, ..
264 }) = get(at)
265 {
266 let instance_hash = Hash::associated_function(instance.type_hash(), *hash);
267
268 if let Some(ident) = get_ident(at, instance_hash) {
269 labels.push(d::Label::secondary(source_id, span.range()).with_message(
270 format!(
271 "This corresponds to the `{instance}::{ident}` instance function"
272 ),
273 ));
274 }
275
276 if let Some(protocol) = Protocol::from_hash(instance_hash) {
277 labels.push(
278 d::Label::secondary(source_id, span.range())
279 .with_message(format!("This corresponds to the `{protocol}` protocol function for `{instance}`")),
280 );
281 }
282 }
283 };
284 }
285
286 let diagnostic = d::Diagnostic::error()
287 .with_message(self.inner.error.try_to_string()?)
288 .with_labels(labels)
289 .with_notes(notes);
290
291 term::emit(out, &config, sources, &diagnostic)?;
292
293 if !backtrace.is_empty() {
294 writeln!(out, "Backtrace:")?;
295
296 for frame in &backtrace {
297 let Some(source) = sources.get(frame.source_id) else {
298 continue;
299 };
300
301 let (line, line_count, [prefix, mid, suffix]) = match source.line(frame.span) {
302 Some((line, line_count, text)) => {
303 (line.saturating_add(1), line_count.saturating_add(1), text)
304 }
305 None => continue,
306 };
307
308 writeln!(out, "{}:{line}:{line_count}:", source.name())?;
309 write!(out, "{prefix}")?;
310 out.set_color(&red)?;
311 write!(out, "{mid}")?;
312 out.reset()?;
313 writeln!(out, "{}", suffix.trim_end_matches(['\n', '\r']))?;
314 }
315 }
316
317 Ok(())
318 }
319}
320
321impl FatalDiagnostic {
322 pub fn emit<O>(&self, out: &mut O, sources: &Sources) -> Result<(), EmitError>
327 where
328 O: WriteColor,
329 {
330 let config = term::Config::default();
331 fatal_diagnostics_emit(self, out, sources, &config)
332 }
333}
334
335impl WarningDiagnostic {
336 pub fn emit<O>(&self, out: &mut O, sources: &Sources) -> Result<(), EmitError>
341 where
342 O: WriteColor,
343 {
344 let config = term::Config::default();
345 warning_diagnostics_emit(self, out, sources, &config)
346 }
347}
348
349impl RuntimeWarningDiagnostic {
350 pub fn emit<O>(
355 &self,
356 out: &mut O,
357 sources: &Sources,
358 debug_info: Option<&DebugInfo>,
359 context: Option<&Context>,
360 ) -> Result<(), EmitError>
361 where
362 O: WriteColor,
363 {
364 let config = term::Config::default();
365 runtime_warning_diagnostics_emit(self, out, sources, &config, debug_info, context)
366 }
367}
368
369impl Unit {
370 pub fn emit_instructions<O>(
372 &self,
373 out: &mut O,
374 sources: &Sources,
375 without_source: bool,
376 ) -> io::Result<()>
377 where
378 O: WriteColor,
379 {
380 let mut first_function = true;
381
382 for (n, inst) in self.iter_instructions() {
383 let debug = self.debug_info().and_then(|d| d.instruction_at(n));
384
385 if let Some((hash, signature)) = self.debug_info().and_then(|d| d.function_at(n)) {
386 if !std::mem::take(&mut first_function) {
387 writeln!(out)?;
388 }
389
390 writeln!(out, "fn {} ({}):", signature, hash)?;
391 }
392
393 for label in debug.map(|d| d.labels.as_slice()).unwrap_or_default() {
394 writeln!(out, "{}:", label)?;
395 }
396
397 write!(out, " {n:04} = {inst}")?;
398
399 if let Some(comment) = debug.and_then(|d| d.comment.as_ref()) {
400 write!(out, " // {}", comment)?;
401 }
402
403 writeln!(out)?;
404
405 if !without_source {
406 if let Some((source, span)) =
407 debug.and_then(|d| sources.get(d.source_id).map(|s| (s, d.span)))
408 {
409 if let Some(line) = source.source_line(span) {
410 write!(out, " ")?;
411 line.write(out)?;
412 writeln!(out)?;
413 }
414 }
415 }
416 }
417
418 Ok(())
419 }
420}
421
422impl Source {
423 pub fn source_line(&self, span: Span) -> Option<SourceLine<'_>> {
425 let (count, column, line, span) = line_for(self, span)?;
426
427 Some(SourceLine {
428 name: self.name(),
429 count,
430 column,
431 line,
432 span,
433 })
434 }
435}
436
437pub struct SourceLine<'a> {
439 name: &'a str,
440 count: usize,
441 column: usize,
442 line: &'a str,
443 span: Span,
444}
445
446impl SourceLine<'_> {
447 pub fn write(&self, o: &mut dyn WriteColor) -> io::Result<()> {
449 let mut highlight = termcolor::ColorSpec::new();
450 highlight.set_fg(Some(termcolor::Color::Yellow));
451
452 let mut new_line = termcolor::ColorSpec::new();
453 new_line.set_fg(Some(termcolor::Color::Red));
454
455 let line = self.line.trim_end();
456 let end = self.span.end.into_usize().min(line.len());
457
458 let before = &line[0..self.span.start.into_usize()].trim_start();
459 let inner = &line[self.span.start.into_usize()..end];
460 let after = &line[end..];
461
462 {
463 let name = self.name;
464 let column = self.count + 1;
465 let start = self.column + 1;
466 let end = start + inner.chars().count();
467 write!(o, "{name}:{column}:{start}-{end}: ")?;
468 }
469
470 write!(o, "{before}")?;
471 o.set_color(&highlight)?;
472 write!(o, "{inner}")?;
473 o.reset()?;
474 write!(o, "{after}")?;
475
476 if self.span.end != end {
477 o.set_color(&new_line)?;
478 write!(o, "\\n")?;
479 o.reset()?;
480 }
481
482 Ok(())
483 }
484}
485
486pub fn line_for(source: &Source, span: Span) -> Option<(usize, usize, &str, Span)> {
488 let line_starts = source.line_starts();
489
490 let line = match line_starts.binary_search(&span.start.into_usize()) {
491 Ok(n) => n,
492 Err(n) => n.saturating_sub(1),
493 };
494
495 let start = *line_starts.get(line)?;
496 let end = line.checked_add(1)?;
497
498 let s = if let Some(end) = line_starts.get(end) {
499 source.get(start..*end)?
500 } else {
501 source.get(start..)?
502 };
503
504 let line_end = span.start.into_usize().saturating_sub(start);
505 let column = s
506 .get(..line_end)
507 .into_iter()
508 .flat_map(|s| s.chars())
509 .count();
510
511 let start = start.try_into().unwrap();
512
513 Some((
514 line,
515 column,
516 s,
517 Span::new(
518 span.start.saturating_sub(start),
519 span.end.saturating_sub(start),
520 ),
521 ))
522}
523
524fn warning_diagnostics_emit<O>(
526 this: &WarningDiagnostic,
527 out: &mut O,
528 sources: &Sources,
529 config: &term::Config,
530) -> Result<(), EmitError>
531where
532 O: WriteColor,
533{
534 let mut notes = ::rust_alloc::vec::Vec::new();
535 let mut labels = ::rust_alloc::vec::Vec::new();
536
537 labels.push(
538 d::Label::primary(this.source_id(), this.span().range())
539 .with_message(this.try_to_string()?),
540 );
541
542 match this.kind() {
543 WarningDiagnosticKind::LetPatternMightPanic { span, .. } => {
544 if let Some(binding) = sources.source(this.source_id(), *span) {
545 let mut note = String::new();
546 writeln!(note, "Hint: Rewrite to:")?;
547 writeln!(note, "if {} {{", binding)?;
548 writeln!(note, " // ..")?;
549 writeln!(note, "}}")?;
550 notes.push(note.into_std());
551 }
552 }
553 WarningDiagnosticKind::RemoveTupleCallParams { variant, .. } => {
554 if let Some(variant) = sources.source(this.source_id(), *variant) {
555 let mut note = String::new();
556 writeln!(note, "Hint: Rewrite to `{}`", variant)?;
557 notes.push(note.into_std());
558 }
559 }
560 WarningDiagnosticKind::Unreachable { cause, .. } => {
561 labels.push(
562 d::Label::secondary(this.source_id(), cause.range())
563 .with_message("This code diverges"),
564 );
565 }
566 _ => {}
567 };
568
569 if let Some(context) = this.context() {
570 labels.push(
571 d::Label::secondary(this.source_id(), context.range()).with_message("In this context"),
572 );
573 }
574
575 let diagnostic = d::Diagnostic::warning()
576 .with_message("Warning")
577 .with_labels(labels)
578 .with_notes(notes);
579
580 term::emit(out, config, sources, &diagnostic)?;
581 Ok(())
582}
583
584fn runtime_warning_diagnostics_emit<O>(
586 this: &RuntimeWarningDiagnostic,
587 out: &mut O,
588 sources: &Sources,
589 config: &term::Config,
590 debug_info: Option<&DebugInfo>,
591 context: Option<&Context>,
592) -> Result<(), EmitError>
593where
594 O: WriteColor,
595{
596 let mut notes = ::rust_alloc::vec::Vec::new();
597 let mut labels = ::rust_alloc::vec::Vec::new();
598 let mut message = String::new();
599
600 match this.kind {
601 RuntimeWarningDiagnosticKind::UsedDeprecated { hash } => {
602 let name = match context
604 .map(|c| c.lookup_meta_by_hash(hash))
605 .and_then(|m| m.into_iter().next())
606 .and_then(|e| e.item.as_ref())
607 {
608 Some(e) => e.try_to_string()?,
609 None => hash.try_to_string()?,
610 };
611 writeln!(message, "Used deprecated function: {}", name)?;
612
613 if let Some(context) = context {
615 if let Some(deprecation) = context.lookup_deprecation(hash) {
616 let mut note = String::new();
617 writeln!(note, "Deprecated: {}", deprecation)?;
618 notes.push(note.into_std());
619 }
620 }
621
622 if let Some(inst) = debug_info.and_then(|d| d.instruction_at(this.ip)) {
624 labels.push(
625 d::Label::primary(inst.source_id, inst.span.range())
626 .with_message(this.try_to_string()?),
627 );
628 }
629 }
630 };
631
632 let diagnostic = d::Diagnostic::warning()
633 .with_message(message)
634 .with_labels(labels)
635 .with_notes(notes);
636
637 term::emit(out, config, sources, &diagnostic)?;
638 Ok(())
639}
640
641fn fatal_diagnostics_emit<O>(
643 this: &FatalDiagnostic,
644 out: &mut O,
645 sources: &Sources,
646 config: &term::Config,
647) -> Result<(), EmitError>
648where
649 O: WriteColor,
650{
651 let mut labels = ::rust_alloc::vec::Vec::new();
652 let mut notes = ::rust_alloc::vec::Vec::new();
653
654 if let Some(span) = this.span() {
655 labels.push(
656 d::Label::primary(this.source_id(), span.range())
657 .with_message(this.kind().try_to_string()?),
658 );
659 }
660
661 match this.kind() {
662 FatalDiagnosticKind::Internal(message) => {
663 writeln!(out, "internal error: {}", message)?;
664 return Ok(());
665 }
666 FatalDiagnosticKind::LinkError(error) => {
667 match error {
668 LinkerError::MissingFunction { hash, spans } => {
669 let mut labels = ::rust_alloc::vec::Vec::new();
670
671 for (span, source_id) in spans {
672 labels.push(
673 d::Label::primary(*source_id, span.range())
674 .with_message("called here."),
675 );
676 }
677
678 let diagnostic = d::Diagnostic::error()
679 .with_message(format!(
680 "linker error: missing function with hash `{}`",
681 hash
682 ))
683 .with_labels(labels);
684
685 term::emit(out, config, sources, &diagnostic)?;
686 }
687 }
688
689 return Ok(());
690 }
691 FatalDiagnosticKind::CompileError(error) => {
692 format_compile_error(
693 this,
694 sources,
695 error.span(),
696 error.kind(),
697 &mut labels,
698 &mut notes,
699 )?;
700 }
701 };
702
703 let diagnostic = d::Diagnostic::error()
704 .with_message(this.kind().try_to_string()?)
705 .with_labels(labels)
706 .with_notes(notes);
707
708 term::emit(out, config, sources, &diagnostic)?;
709 return Ok(());
710
711 fn format_compile_error(
712 this: &FatalDiagnostic,
713 sources: &Sources,
714 span: Span,
715 kind: &ErrorKind,
716 labels: &mut ::rust_alloc::vec::Vec<d::Label<SourceId>>,
717 notes: &mut ::rust_alloc::vec::Vec<rust_alloc::string::String>,
718 ) -> Result<(), EmitError> {
719 match kind {
720 ErrorKind::ImportCycle { path } => {
721 let mut it = path.iter();
722 let last = it.next_back();
723
724 for (step, entry) in (1..).zip(it) {
725 labels.push(
726 d::Label::secondary(entry.location.source_id, entry.location.span.range())
727 .with_message(format!("Step #{} for `{}`", step, entry.item)),
728 );
729 }
730
731 if let Some(entry) = last {
732 labels.push(
733 d::Label::secondary(entry.location.source_id, entry.location.span.range())
734 .with_message(format!("Final step cycling back to `{}`", entry.item)),
735 );
736 }
737 }
738 ErrorKind::NotVisible {
739 chain,
740 location: Location { source_id, span },
741 ..
742 } => {
743 for Location { source_id, span } in chain {
744 labels.push(
745 d::Label::secondary(*source_id, span.range())
746 .with_message("Re-exported here"),
747 );
748 }
749
750 labels.push(
751 d::Label::secondary(*source_id, span.range()).with_message("defined here"),
752 );
753 }
754 ErrorKind::NotVisibleMod {
755 chain,
756 location: Location { source_id, span },
757 ..
758 } => {
759 for Location { source_id, span } in chain {
760 labels.push(
761 d::Label::secondary(*source_id, span.range())
762 .with_message("Re-exported here"),
763 );
764 }
765
766 labels.push(
767 d::Label::secondary(*source_id, span.range())
768 .with_message("Module defined here"),
769 );
770 }
771 ErrorKind::AmbiguousItem { locations, .. } => {
772 for (Location { source_id, span }, item) in locations {
773 labels.push(
774 d::Label::secondary(*source_id, span.range())
775 .with_message(format!("Here as `{item}`")),
776 );
777 }
778 }
779 ErrorKind::AmbiguousContextItem { infos, .. } => {
780 for info in infos.as_ref() {
781 labels.push(
782 d::Label::secondary(this.source_id, span.range())
783 .with_message(format!("Could be `{info}`")),
784 );
785 }
786 }
787 ErrorKind::DuplicateObjectKey { existing, object } => {
788 labels.push(
789 d::Label::secondary(this.source_id(), existing.range())
790 .with_message("Previously defined here"),
791 );
792
793 labels.push(
794 d::Label::secondary(this.source_id(), object.range())
795 .with_message("Object being defined here"),
796 );
797 }
798 ErrorKind::ModAlreadyLoaded { existing, .. } => {
799 let (existing_source_id, existing_span) = *existing;
800
801 labels.push(
802 d::Label::secondary(existing_source_id, existing_span.range())
803 .with_message("Previously loaded here"),
804 );
805 }
806 ErrorKind::ExpectedBlockSemiColon { followed_span } => {
807 labels.push(
808 d::Label::secondary(this.source_id(), followed_span.range())
809 .with_message("Because this immediately follows"),
810 );
811
812 let binding = sources.source(this.source_id(), span);
813
814 if let Some(binding) = binding {
815 let mut note = String::new();
816 writeln!(note, "Hint: Rewrite to `{};`", binding)?;
817 notes.push(note.into_std());
818 }
819 }
820 ErrorKind::VariableMoved { moved_at, .. } => {
821 labels.push(
822 d::Label::secondary(this.source_id(), moved_at.range())
823 .with_message("Moved here"),
824 );
825 }
826 ErrorKind::NestedTest { nested_span } => {
827 labels.push(
828 d::Label::secondary(this.source_id(), nested_span.range())
829 .with_message("Nested in here"),
830 );
831 }
832 ErrorKind::NestedBench { nested_span } => {
833 labels.push(
834 d::Label::secondary(this.source_id(), nested_span.range())
835 .with_message("Nested in here"),
836 );
837 }
838 ErrorKind::PatternMissingFields { fields, .. } => {
839 let pl = if fields.len() == 1 { "field" } else { "fields" };
840
841 let fields = fields.join(", ");
842
843 labels.push(
844 d::Label::secondary(this.source_id(), span.range())
845 .with_message(format!("Missing {}: {}", pl, fields)),
846 );
847
848 notes.push(
849 "You can also make the pattern non-exhaustive by adding `..`"
850 .try_to_string()?
851 .into_std(),
852 );
853 }
854 ErrorKind::ConflictingLabels { existing, .. } => {
855 labels.push(
856 d::Label::secondary(this.source_id(), existing.range())
857 .with_message("Existing label here"),
858 );
859 }
860 ErrorKind::DuplicateSelectDefault { existing, .. } => {
861 labels.push(
862 d::Label::secondary(this.source_id(), existing.range())
863 .with_message("Existing branch here"),
864 );
865 }
866 _ => (),
867 }
868
869 Ok(())
870 }
871}