sway_error/handler.rs
1use crate::{
2 error::CompileError,
3 warning::{CompileInfo, CompileWarning},
4};
5use core::cell::RefCell;
6
7/// A handler with which you can emit diagnostics.
8#[derive(Default, Debug, Clone)]
9pub struct Handler {
10 /// The inner handler.
11 /// This construction is used to avoid `&mut` all over the compiler.
12 inner: RefCell<HandlerDiagnostics>,
13}
14
15/// Contains the actual data for `Handler`.
16/// Modeled this way to afford an API using interior mutability.
17#[derive(Default, Debug, Clone)]
18struct HandlerDiagnostics {
19 /// The sink through which errors will be emitted.
20 errors: Vec<CompileError>,
21 /// The sink through which warnings will be emitted.
22 warnings: Vec<CompileWarning>,
23 /// The sink through which infos will be emitted.
24 infos: Vec<CompileInfo>,
25}
26
27impl Handler {
28 pub fn from_parts(
29 errors: Vec<CompileError>,
30 warnings: Vec<CompileWarning>,
31 infos: Vec<CompileInfo>,
32 ) -> Self {
33 Self {
34 inner: RefCell::new(HandlerDiagnostics {
35 errors,
36 warnings,
37 infos,
38 }),
39 }
40 }
41
42 /// Emit the error `err`.
43 pub fn emit_err(&self, err: CompileError) -> ErrorEmitted {
44 self.inner.borrow_mut().errors.push(err);
45 ErrorEmitted { _priv: () }
46 }
47
48 // Compilation should be canceled.
49 pub fn cancel(&self) -> ErrorEmitted {
50 ErrorEmitted { _priv: () }
51 }
52
53 /// Emit the warning `warn`.
54 pub fn emit_warn(&self, warn: CompileWarning) {
55 self.inner.borrow_mut().warnings.push(warn);
56 }
57
58 /// Emit the info `info`.
59 pub fn emit_info(&self, info: CompileInfo) {
60 self.inner.borrow_mut().infos.push(info);
61 }
62
63 pub fn has_errors(&self) -> bool {
64 !self.inner.borrow().errors.is_empty()
65 }
66
67 pub fn find_error(&self, f: impl FnMut(&&CompileError) -> bool) -> Option<CompileError> {
68 self.inner.borrow().errors.iter().find(f).cloned()
69 }
70
71 pub fn has_warnings(&self) -> bool {
72 !self.inner.borrow().warnings.is_empty()
73 }
74
75 /// Aggregate errors emitted when running `f`.
76 ///
77 /// Runs `f` in a fresh error-collecting scope, returning `f`'s result, or an
78 /// aggregated [ErrorEmitted] if *any* error was emitted while running `f`.
79 ///
80 /// A fresh, scoped [Handler] is passed to `f`. After `f` returns, its errors are
81 /// appended to `self`. If at least one error was emitted into the scoped handler,
82 /// the scope returns `Err(ErrorEmitted)` regardless of what `f` returned; otherwise
83 /// it returns `f`'s result.
84 ///
85 /// # Swallowing errors within a scope is intended
86 ///
87 /// Because the scope aggregates *every* error emitted into the scoped handler, code
88 /// inside `f` should keep going after a failing sub-operation instead of
89 /// short-circuiting on the first error. Swallowing an individual sub-operation's
90 /// [Result] and continuing with the next one is therefore the *intended* pattern.
91 /// Independent sub-operations each get to report their own diagnostics, so the user
92 /// sees all of them at once rather than only the first.
93 ///
94 /// When iterating over a collection, use `fold` together with `unwrap_or_default`
95 /// (rather than `try_fold` with `?`) so that every element is still processed:
96 ///
97 /// ```ignore
98 /// handler.scope(|handler| {
99 /// // Each element's error is swallowed but stays captured by the scope, so
100 /// // sibling elements can also report their diagnostics.
101 /// Ok(items.iter_mut().fold(HasChanges::No, |has_changes, item| {
102 /// has_changes | item.do_something(handler).unwrap_or_default()
103 /// }))
104 /// })
105 /// ```
106 ///
107 /// For a fixed set of sub-operations, `sway_core`'s `has_changes_scoped!` macro
108 /// applies the same swallow-and-continue behavior.
109 ///
110 /// Use `?` only when a later step genuinely depends on an earlier step succeeding,
111 /// i.e. when continuing would panic or produce misleading cascade errors.
112 ///
113 /// Note that swallowing an [ErrorEmitted] is only sound *inside* a scope (or with
114 /// a scope somewhere up the call stack). Outside a scope, a swallowed error is lost
115 /// from the returned [Result] even though it was emitted.
116 pub fn scope<T>(
117 &self,
118 f: impl FnOnce(&Handler) -> Result<T, ErrorEmitted>,
119 ) -> Result<T, ErrorEmitted> {
120 let scoped_handler = Handler::default();
121 let closure_res = f(&scoped_handler);
122
123 match self.append(scoped_handler) {
124 Some(err) => Err(err),
125 None => closure_res,
126 }
127 }
128
129 /// Extract all the diagnostics from this handler.
130 pub fn consume(self) -> (Vec<CompileError>, Vec<CompileWarning>, Vec<CompileInfo>) {
131 let inner = self.inner.into_inner();
132 (inner.errors, inner.warnings, inner.infos)
133 }
134
135 pub fn append(&self, other: Handler) -> Option<ErrorEmitted> {
136 let other_has_errors = other.has_errors();
137
138 let (errors, warnings, infos) = other.consume();
139 for warn in warnings {
140 self.emit_warn(warn);
141 }
142 for err in errors {
143 self.emit_err(err);
144 }
145 for inf in infos {
146 self.emit_info(inf);
147 }
148
149 if other_has_errors {
150 Some(ErrorEmitted { _priv: () })
151 } else {
152 None
153 }
154 }
155
156 pub fn dedup(&self) {
157 let mut inner = self.inner.borrow_mut();
158 inner.errors = dedup_unsorted(inner.errors.clone());
159 inner.warnings = dedup_unsorted(inner.warnings.clone());
160 }
161
162 /// Retains only the elements specified by the predicate.
163 ///
164 /// In other words, remove all elements `e` for which `f(&e)` returns `false`.
165 /// This method operates in place, visiting each element exactly once in the
166 /// original order, and preserves the order of the retained elements.
167 pub fn retain_err<F>(&self, f: F)
168 where
169 F: FnMut(&CompileError) -> bool,
170 {
171 self.inner.borrow_mut().errors.retain(f)
172 }
173
174 // Map all errors from `other` into this handler. If any mapping returns `None` it is ignored. This
175 // method returns if any error was mapped or not.
176 pub fn map_and_emit_errors_from(
177 &self,
178 other: Handler,
179 mut f: impl FnMut(CompileError) -> Option<CompileError>,
180 ) -> Result<(), ErrorEmitted> {
181 let mut emitted = Ok(());
182
183 let (errs, _, _) = other.consume();
184 for err in errs {
185 if let Some(err) = (f)(err) {
186 emitted = Err(self.emit_err(err));
187 }
188 }
189
190 emitted
191 }
192}
193
194/// Proof that an error was emitted through a `Handler`.
195#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
196pub struct ErrorEmitted {
197 _priv: (),
198}
199
200/// We want compile errors and warnings to retain their ordering, since typically
201/// they are grouped by relevance. However, we want to deduplicate them.
202/// Stdlib dedup in Rust assumes sorted data for efficiency, but we don't want that.
203/// A hash set would also mess up the order, so this is just a brute force way of doing it
204/// with a vector.
205fn dedup_unsorted<T: PartialEq + std::hash::Hash + Clone + Eq>(mut data: Vec<T>) -> Vec<T> {
206 use std::collections::HashSet;
207
208 let mut seen = HashSet::new();
209 data.retain(|item| seen.insert(item.clone()));
210 data
211}