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, RuntimeDiagnostic, RuntimeDiagnosticKind,
19 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, 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::Runtime(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::Runtime(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.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.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.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.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([format!("Expected `{expected}`"), format!("Got `{actual}`")]);
226 }
227 _ => {}
228 };
229
230 if let Some(&DebugInst {
231 source_id, span, ..
232 }) = get(at)
233 {
234 labels.push(
235 d::Label::primary(source_id, span.range()).with_message(at.try_to_string()?),
236 );
237 }
238 }
239
240 if let Some(&DebugInst {
241 source_id, span, ..
242 }) = get(self.error())
243 {
244 labels.push(
245 d::Label::primary(source_id, span.range())
246 .with_message(self.error().try_to_string()?),
247 );
248 };
249
250 for at in [self.error()].into_iter().chain(self.chain()) {
251 if let VmErrorKind::MissingInstanceFunction { hash, instance } = at.kind() {
253 if let Some(&DebugInst {
260 source_id, span, ..
261 }) = get(at)
262 {
263 let instance_hash = Hash::associated_function(instance.type_hash(), *hash);
264
265 if let Some(ident) = get_ident(at, instance_hash) {
266 labels.push(d::Label::secondary(source_id, span.range()).with_message(
267 format!(
268 "This corresponds to the `{instance}::{ident}` instance function"
269 ),
270 ));
271 }
272
273 if let Some(protocol) = Protocol::from_hash(instance_hash) {
274 labels.push(
275 d::Label::secondary(source_id, span.range())
276 .with_message(format!("This corresponds to the `{protocol}` protocol function for `{instance}`")),
277 );
278 }
279 }
280 };
281
282 if let VmErrorKind::ObjectIndexMissing { slot } = at.kind() {
286 if let Some(&DebugInst {
287 source_id, span, ..
288 }) = get(at)
289 {
290 if let Some(string) = self
291 .stacktrace()
292 .get(at.index())
293 .and_then(|l| l.unit.lookup_string(*slot))
294 {
295 labels.push(
296 d::Label::secondary(source_id, span.range())
297 .with_message(format!("This corresponds to the field `{string}`")),
298 );
299 }
300 }
301 }
302 }
303
304 let diagnostic = d::Diagnostic::error()
305 .with_message(self.error().try_to_string()?)
306 .with_labels(labels)
307 .with_notes(notes);
308
309 term::emit_to_write_style(out, &config, sources, &diagnostic)?;
310
311 if !backtrace.is_empty() {
312 writeln!(out, "Backtrace:")?;
313
314 for frame in &backtrace {
315 let Some(source) = sources.get(frame.source_id) else {
316 continue;
317 };
318
319 let (line, line_count, [prefix, mid, suffix]) = match source.line(frame.span) {
320 Some((line, line_count, text)) => {
321 (line.saturating_add(1), line_count.saturating_add(1), text)
322 }
323 None => continue,
324 };
325
326 writeln!(out, "{}:{line}:{line_count}:", source.name())?;
327 write!(out, "{prefix}")?;
328 out.set_color(&red)?;
329 write!(out, "{mid}")?;
330 out.reset()?;
331 writeln!(out, "{}", suffix.trim_end_matches(['\n', '\r']))?;
332 }
333 }
334
335 Ok(())
336 }
337}
338
339impl FatalDiagnostic {
340 pub fn emit<O>(&self, out: &mut O, sources: &Sources) -> Result<(), EmitError>
345 where
346 O: WriteColor,
347 {
348 let config = term::Config::default();
349 fatal_diagnostics_emit(self, out, sources, &config)
350 }
351}
352
353impl WarningDiagnostic {
354 pub fn emit<O>(&self, out: &mut O, sources: &Sources) -> Result<(), EmitError>
359 where
360 O: WriteColor,
361 {
362 let config = term::Config::default();
363 warning_diagnostics_emit(self, out, sources, &config)
364 }
365}
366
367impl RuntimeDiagnostic {
368 pub fn emit<O>(
373 &self,
374 out: &mut O,
375 sources: &Sources,
376 debug_info: Option<&DebugInfo>,
377 context: Option<&Context>,
378 ) -> Result<(), EmitError>
379 where
380 O: WriteColor,
381 {
382 let config = term::Config::default();
383 runtime_warning_diagnostics_emit(self, out, sources, &config, debug_info, context)
384 }
385}
386
387impl Unit {
388 pub fn emit_instructions<O>(
390 &self,
391 out: &mut O,
392 sources: &Sources,
393 without_source: bool,
394 ) -> io::Result<()>
395 where
396 O: WriteColor,
397 {
398 let mut first_function = true;
399
400 for (n, inst) in self.iter_instructions() {
401 let debug = self.debug_info().and_then(|d| d.instruction_at(n));
402
403 if let Some((hash, signature)) = self.debug_info().and_then(|d| d.function_at(n)) {
404 if !std::mem::take(&mut first_function) {
405 writeln!(out)?;
406 }
407
408 writeln!(out, "fn {signature} ({hash}):")?;
409 }
410
411 for label in debug.map(|d| d.labels.as_slice()).unwrap_or_default() {
412 writeln!(out, "{label}:")?;
413 }
414
415 write!(out, " {n:04} = {inst}")?;
416
417 if let Some(comment) = debug.and_then(|d| d.comment.as_ref()) {
418 write!(out, " // {comment}")?;
419 }
420
421 writeln!(out)?;
422
423 if !without_source {
424 if let Some((source, span)) =
425 debug.and_then(|d| sources.get(d.source_id).map(|s| (s, d.span)))
426 {
427 if let Some(line) = source.source_line(span) {
428 write!(out, " ")?;
429 line.write(out)?;
430 writeln!(out)?;
431 }
432 }
433 }
434 }
435
436 Ok(())
437 }
438}
439
440fn warning_diagnostics_emit<O>(
442 this: &WarningDiagnostic,
443 out: &mut O,
444 sources: &Sources,
445 config: &term::Config,
446) -> Result<(), EmitError>
447where
448 O: WriteColor,
449{
450 let mut notes = rust_alloc::vec::Vec::new();
451 let mut labels = rust_alloc::vec::Vec::new();
452
453 labels.push(
454 d::Label::primary(this.source_id(), this.span().range())
455 .with_message(this.try_to_string()?),
456 );
457
458 match this.kind() {
459 WarningDiagnosticKind::LetPatternMightPanic { span, .. } => {
460 if let Some(binding) = sources.source(this.source_id(), *span) {
461 let mut note = String::new();
462 writeln!(note, "Hint: Rewrite to:")?;
463 writeln!(note, "if {binding} {{")?;
464 writeln!(note, " // ..")?;
465 writeln!(note, "}}")?;
466 notes.push(note.into_std());
467 }
468 }
469 WarningDiagnosticKind::RemoveTupleCallParams { variant, .. } => {
470 if let Some(variant) = sources.source(this.source_id(), *variant) {
471 let mut note = String::new();
472 writeln!(note, "Hint: Rewrite to `{variant}`")?;
473 notes.push(note.into_std());
474 }
475 }
476 WarningDiagnosticKind::Unreachable { cause, .. } => {
477 labels.push(
478 d::Label::secondary(this.source_id(), cause.range())
479 .with_message("This code diverges"),
480 );
481 }
482 _ => {}
483 };
484
485 if let Some(context) = this.context() {
486 labels.push(
487 d::Label::secondary(this.source_id(), context.range()).with_message("In this context"),
488 );
489 }
490
491 let diagnostic = d::Diagnostic::warning()
495 .with_message(this.try_to_string()?)
496 .with_labels(labels)
497 .with_notes(notes);
498
499 term::emit_to_write_style(out, config, sources, &diagnostic)?;
500 Ok(())
501}
502
503fn runtime_warning_diagnostics_emit<O>(
505 this: &RuntimeDiagnostic,
506 out: &mut O,
507 sources: &Sources,
508 config: &term::Config,
509 debug_info: Option<&DebugInfo>,
510 context: Option<&Context>,
511) -> Result<(), EmitError>
512where
513 O: WriteColor,
514{
515 let mut notes = rust_alloc::vec::Vec::new();
516 let mut labels = rust_alloc::vec::Vec::new();
517 let mut message = String::new();
518
519 match this.kind {
520 RuntimeDiagnosticKind::UsedDeprecated { hash } => {
521 let name = match context
523 .map(|c| c.lookup_meta_by_hash(hash))
524 .and_then(|m| m.into_iter().next())
525 .and_then(|e| e.item.as_ref())
526 {
527 Some(e) => e.try_to_string()?,
528 None => hash.try_to_string()?,
529 };
530 writeln!(message, "Used deprecated function: {name}")?;
531
532 if let Some(context) = context {
534 if let Some(deprecation) = context.lookup_deprecation(hash) {
535 let mut note = String::new();
536 writeln!(note, "Deprecated: {deprecation}")?;
537 notes.push(note.into_std());
538 }
539 }
540
541 if let Some(inst) = debug_info.and_then(|d| d.instruction_at(this.ip)) {
543 labels.push(
544 d::Label::primary(inst.source_id, inst.span.range())
545 .with_message(this.try_to_string()?),
546 );
547 }
548 }
549 };
550
551 let diagnostic = d::Diagnostic::warning()
552 .with_message(message)
553 .with_labels(labels)
554 .with_notes(notes);
555
556 term::emit_to_write_style(out, config, sources, &diagnostic)?;
557 Ok(())
558}
559
560fn fatal_diagnostics_emit<O>(
562 this: &FatalDiagnostic,
563 out: &mut O,
564 sources: &Sources,
565 config: &term::Config,
566) -> Result<(), EmitError>
567where
568 O: WriteColor,
569{
570 let mut labels = rust_alloc::vec::Vec::new();
571 let mut notes = rust_alloc::vec::Vec::new();
572
573 if let Some(span) = this.span() {
574 labels.push(
575 d::Label::primary(this.source_id(), span.range())
576 .with_message(this.kind().try_to_string()?),
577 );
578 }
579
580 match this.kind() {
581 FatalDiagnosticKind::Custom(message) => {
582 writeln!(out, "{message}")?;
583 return Ok(());
584 }
585 FatalDiagnosticKind::LinkError(error) => {
586 match error {
587 LinkerError::MissingFunction { hash, spans } => {
588 let mut labels = rust_alloc::vec::Vec::new();
589
590 for (span, source_id) in spans {
591 labels.push(
592 d::Label::primary(*source_id, span.range())
593 .with_message("called here."),
594 );
595 }
596
597 let diagnostic = d::Diagnostic::error()
598 .with_message(format!("linker error: missing function with hash `{hash}`",))
599 .with_labels(labels);
600
601 term::emit_to_write_style(out, config, sources, &diagnostic)?;
602 }
603 }
604
605 return Ok(());
606 }
607 FatalDiagnosticKind::CompileError(error) => {
608 format_compile_error(
609 this,
610 sources,
611 error.span(),
612 error.kind(),
613 &mut labels,
614 &mut notes,
615 )?;
616 }
617 };
618
619 let diagnostic = d::Diagnostic::error()
620 .with_message(this.kind().try_to_string()?)
621 .with_labels(labels)
622 .with_notes(notes);
623
624 term::emit_to_write_style(out, config, sources, &diagnostic)?;
625 return Ok(());
626
627 fn format_compile_error(
628 this: &FatalDiagnostic,
629 sources: &Sources,
630 span: Span,
631 kind: &ErrorKind,
632 labels: &mut rust_alloc::vec::Vec<d::Label<SourceId>>,
633 notes: &mut rust_alloc::vec::Vec<rust_alloc::string::String>,
634 ) -> Result<(), EmitError> {
635 match kind {
636 ErrorKind::ImportCycle { path } => {
637 let mut it = path.iter();
638 let last = it.next_back();
639
640 for (step, entry) in (1..).zip(it) {
641 labels.push(
642 d::Label::secondary(entry.location.source_id, entry.location.span.range())
643 .with_message(format!("Step #{} for `{}`", step, entry.item)),
644 );
645 }
646
647 if let Some(entry) = last {
648 labels.push(
649 d::Label::secondary(entry.location.source_id, entry.location.span.range())
650 .with_message(format!("Final step cycling back to `{}`", entry.item)),
651 );
652 }
653 }
654 ErrorKind::NotVisible {
655 chain,
656 location: Location { source_id, span },
657 ..
658 } => {
659 for Location { source_id, span } in chain {
660 labels.push(
661 d::Label::secondary(*source_id, span.range())
662 .with_message("Re-exported here"),
663 );
664 }
665
666 labels.push(
667 d::Label::secondary(*source_id, span.range()).with_message("defined here"),
668 );
669 }
670 ErrorKind::NotVisibleMod {
671 chain,
672 location: Location { source_id, span },
673 ..
674 } => {
675 for Location { source_id, span } in chain {
676 labels.push(
677 d::Label::secondary(*source_id, span.range())
678 .with_message("Re-exported here"),
679 );
680 }
681
682 labels.push(
683 d::Label::secondary(*source_id, span.range())
684 .with_message("Module defined here"),
685 );
686 }
687 ErrorKind::AmbiguousItem { locations, .. } => {
688 for (Location { source_id, span }, item) in locations {
689 labels.push(
690 d::Label::secondary(*source_id, span.range())
691 .with_message(format!("Here as `{item}`")),
692 );
693 }
694 }
695 ErrorKind::AmbiguousContextItem { infos, .. } => {
696 for info in infos.as_ref() {
697 labels.push(
698 d::Label::secondary(this.source_id, span.range())
699 .with_message(format!("Could be `{info}`")),
700 );
701 }
702 }
703 ErrorKind::DuplicateObjectKey { existing, object } => {
704 labels.push(
705 d::Label::secondary(this.source_id(), existing.range())
706 .with_message("Previously defined here"),
707 );
708
709 labels.push(
710 d::Label::secondary(this.source_id(), object.range())
711 .with_message("Object being defined here"),
712 );
713 }
714 ErrorKind::ModAlreadyLoaded { existing, .. } => {
715 let (existing_source_id, existing_span) = *existing;
716
717 labels.push(
718 d::Label::secondary(existing_source_id, existing_span.range())
719 .with_message("Previously loaded here"),
720 );
721 }
722 ErrorKind::ExpectedBlockSemiColon { followed_span } => {
723 labels.push(
724 d::Label::secondary(this.source_id(), followed_span.range())
725 .with_message("Because this immediately follows"),
726 );
727
728 let binding = sources.source(this.source_id(), span);
729
730 if let Some(binding) = binding {
731 let mut note = String::new();
732 writeln!(note, "Hint: Rewrite to `{binding};`")?;
733 notes.push(note.into_std());
734 }
735 }
736 ErrorKind::VariableMoved { moved_at, .. } => {
737 labels.push(
738 d::Label::secondary(this.source_id(), moved_at.range())
739 .with_message("Moved here"),
740 );
741 }
742 ErrorKind::NestedTest { nested_span } => {
743 labels.push(
744 d::Label::secondary(this.source_id(), nested_span.range())
745 .with_message("Nested in here"),
746 );
747 }
748 ErrorKind::NestedBench { nested_span } => {
749 labels.push(
750 d::Label::secondary(this.source_id(), nested_span.range())
751 .with_message("Nested in here"),
752 );
753 }
754 ErrorKind::PatternMissingFields { fields, .. } => {
755 let pl = if fields.len() == 1 { "field" } else { "fields" };
756
757 let fields = fields.join(", ");
758
759 labels.push(
760 d::Label::secondary(this.source_id(), span.range())
761 .with_message(format!("Missing {pl}: {fields}")),
762 );
763
764 notes.push(
765 "You can also make the pattern non-exhaustive by adding `..`"
766 .try_to_string()?
767 .into_std(),
768 );
769 }
770 ErrorKind::ConflictingLabels { existing, .. } => {
771 labels.push(
772 d::Label::secondary(this.source_id(), existing.range())
773 .with_message("Existing label here"),
774 );
775 }
776 ErrorKind::DuplicateSelectDefault { existing, .. } => {
777 labels.push(
778 d::Label::secondary(this.source_id(), existing.range())
779 .with_message("Existing branch here"),
780 );
781 }
782 _ => (),
783 }
784
785 Ok(())
786 }
787}