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