1use crate::Diagnostic;
14use crate::rt::Source;
15use ariadne::Color;
16use ariadne::Label;
17use ariadne::Report;
18use ariadne::ReportKind;
19use candela_vm::captured_output;
20use std::cell::RefCell;
21
22thread_local! {
23 static WARNINGS: RefCell<Option<Vec<Diagnostic>>> = const { RefCell::new(None) };
26}
27
28struct Restore(Option<Vec<Diagnostic>>);
31
32impl Drop for Restore {
33 fn drop(&mut self) {
34 let previous = self.0.take();
35 WARNINGS.with(|w| *w.borrow_mut() = previous);
36 }
37}
38
39pub fn collect_warnings<R>(f: impl FnOnce() -> R) -> (R, Vec<Diagnostic>) {
45 let previous = WARNINGS.with(|w| w.borrow_mut().replace(Vec::new()));
46 let restore = Restore(previous);
47 let result = f();
48 let warnings = WARNINGS.with(|w| w.borrow_mut().take()).unwrap_or_default();
49 drop(restore);
50 (result, warnings)
51}
52
53pub fn emit_warning(sources: &[Source], title: &str, warning: Diagnostic) {
57 let unrecorded = WARNINGS.with(|w| match w.borrow_mut().as_mut() {
58 Some(warnings) => {
59 warnings.push(warning);
60 None
61 }
62 None => Some(warning),
63 });
64 let Some(warning) = unrecorded else {
65 return;
66 };
67 let contents = sources
68 .iter()
69 .find(|source| source.filename == warning.filename)
70 .map_or("", |source| source.contents.as_str());
71 let filename = warning.filename.as_str();
72 let report = Report::build(ReportKind::Warning, (filename, warning.span.clone()))
73 .with_message(title)
74 .with_label(
75 Label::new((filename, warning.span.clone()))
76 .with_message(warning.message.as_str())
77 .with_color(Color::Yellow),
78 )
79 .finish();
80 let _ = report.write(
82 (filename, ariadne::Source::from(contents)),
83 &mut captured_output::stderr(),
84 );
85}
86
87pub fn warn_unraisable_catch_kinds(sources: &[Source], std_files: &[bool]) {
96 let scanned: Vec<_> = sources
97 .iter()
98 .map(|source| crate::parser::catch_kinds_and_strings(&source.contents))
99 .collect();
100 for (file, (kinds, _)) in scanned.iter().enumerate() {
101 if std_files.get(file).copied().unwrap_or(false) {
102 continue;
103 }
104 for (kind, span) in kinds {
105 let raisable = candela_vm::errors::BUILTIN_KINDS.contains(&kind.as_str())
106 || scanned
107 .iter()
108 .any(|(_, strings)| strings.iter().any(|s| s == kind));
109 if raisable {
110 continue;
111 }
112 emit_warning(
113 sources,
114 "Catch for a kind nothing raises",
115 Diagnostic {
116 filename: sources[file].filename.to_string(),
117 span: (span.start as usize)..(span.end as usize),
118 message: format!(
119 "No built-in error has the kind {kind:?} and nothing in the program throws it, so this catch never runs. Check the spelling"
120 ),
121 code: String::from("unraisable_catch_kind"),
122 },
123 );
124 }
125 }
126}