Skip to main content

leo_errors/emitter/
mod.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::LeoWarning;
18
19use super::LeoError;
20
21use itertools::Itertools as _;
22use leo_span::Span;
23use std::{cell::RefCell, collections::HashSet, fmt, rc::Rc};
24
25/// Types that are sinks for compiler errors.
26pub trait Emitter {
27    /// Emit the error `err`.
28    fn emit_err(&mut self, err: LeoError);
29
30    /// Tracks last emitted error.
31    fn last_emitted_err_code(&self) -> Option<i32>;
32
33    /// Emit the warning.
34    fn emit_warning(&mut self, warning: LeoWarning);
35}
36
37/// A trivial `Emitter` using the standard error.
38pub struct StderrEmitter {
39    /// Exit code of the last emitted error.
40    last_error_code: Option<i32>,
41}
42
43impl Emitter for StderrEmitter {
44    fn emit_err(&mut self, err: LeoError) {
45        self.last_error_code = Some(err.exit_code());
46        eprintln!("{err}");
47    }
48
49    fn last_emitted_err_code(&self) -> Option<i32> {
50        self.last_error_code
51    }
52
53    fn emit_warning(&mut self, warning: LeoWarning) {
54        eprintln!("{warning}");
55    }
56}
57
58/// A buffer of `T`s.
59#[derive(Debug)]
60pub struct Buffer<T>(Vec<T>);
61
62impl<T> Default for Buffer<T> {
63    fn default() -> Self {
64        Self(Vec::new())
65    }
66}
67
68impl<T> Buffer<T> {
69    /// Push `x` to the buffer.
70    pub fn push(&mut self, x: T) {
71        self.0.push(x);
72    }
73
74    /// Extract the underlying list of Ts.
75    pub fn into_inner(self) -> Vec<T> {
76        self.0
77    }
78
79    /// Last entry to the buffer.
80    pub fn last_entry(&self) -> Option<&T> {
81        self.0.last()
82    }
83
84    // How many items in the buffer?
85    pub fn len(&self) -> usize {
86        self.0.len()
87    }
88
89    // Is the buffer empty?
90    pub fn is_empty(&self) -> bool {
91        self.0.is_empty()
92    }
93}
94
95impl<T: fmt::Display> fmt::Display for Buffer<T> {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        self.0.iter().format("").fmt(f)
98    }
99}
100
101/// A buffer of `LeoError`s.
102pub type ErrBuffer = Buffer<LeoError>;
103/// A buffer of `LeoWarning`s.
104pub type WarningBuffer = Buffer<LeoWarning>;
105
106/// An `Emitter` that collects into a list.
107#[derive(Default, Clone)]
108pub struct BufferEmitter(Rc<RefCell<ErrBuffer>>, Rc<RefCell<WarningBuffer>>);
109
110impl BufferEmitter {
111    /// Returns a new buffered emitter.
112    pub fn new() -> Self {
113        BufferEmitter(<_>::default(), <_>::default())
114    }
115
116    /// Extracts all the errors collected in this emitter.
117    pub fn extract_errs(&self) -> ErrBuffer {
118        self.0.take()
119    }
120
121    /// Extracts all the errors collected in this emitter.
122    pub fn extract_warnings(&self) -> WarningBuffer {
123        self.1.take()
124    }
125}
126
127impl Emitter for BufferEmitter {
128    fn emit_err(&mut self, err: LeoError) {
129        self.0.borrow_mut().push(err);
130    }
131
132    fn last_emitted_err_code(&self) -> Option<i32> {
133        let temp = &*self.0.borrow();
134        temp.last_entry().map(|entry| entry.exit_code())
135    }
136
137    fn emit_warning(&mut self, warning: LeoWarning) {
138        self.1.borrow_mut().push(warning);
139    }
140}
141
142/// A handler deals with errors and other compiler output.
143#[derive(Clone)]
144pub struct Handler {
145    inner: Rc<RefCell<HandlerInner>>,
146}
147
148pub struct HandlerInner {
149    /// Number of errors emitted thus far.
150    err_count: usize,
151    /// Number of warnings emitted thus far.
152    warn_count: usize,
153    /// The Emitter used.
154    emitter: Box<dyn Emitter>,
155    /// Spans and error codes for which a warning has already been emitted (used for dedup).
156    warned_spans: HashSet<(Span, String)>,
157}
158
159impl Default for Handler {
160    fn default() -> Self {
161        Self::new(StderrEmitter { last_error_code: None })
162    }
163}
164
165impl Handler {
166    /// Construct a `Handler` using the given `emitter`.
167    pub fn new<T: 'static + Emitter>(emitter: T) -> Self {
168        Handler {
169            inner: Rc::new(RefCell::new(HandlerInner {
170                err_count: 0,
171                warn_count: 0,
172                emitter: Box::new(emitter),
173                warned_spans: HashSet::new(),
174            })),
175        }
176    }
177
178    /// Construct a `Handler` that will append to `buf`.
179    pub fn new_with_buf() -> (Self, BufferEmitter) {
180        let buf = BufferEmitter::default();
181        let handler = Self::new(buf.clone());
182        (handler, buf)
183    }
184
185    /// Runs `logic` provided a handler that collects all errors into the `String`,
186    /// or if there were none, returns some `T`.
187    pub fn with<T>(logic: impl for<'a> FnOnce(&'a Handler) -> Result<T, LeoError>) -> Result<T, ErrBuffer> {
188        let (handler, buf) = Handler::new_with_buf();
189        handler.extend_if_error(logic(&handler)).map_err(|_| buf.extract_errs())
190    }
191
192    /// Gets the last emitted error's exit code.
193    fn last_emitted_err_code(&self) -> Option<i32> {
194        self.inner.borrow().emitter.last_emitted_err_code()
195    }
196
197    /// Emit the error `err`.
198    pub fn emit_err(&self, err: impl Into<LeoError>) {
199        let mut inner = self.inner.borrow_mut();
200        inner.err_count = inner.err_count.saturating_add(1);
201        inner.emitter.emit_err(err.into());
202    }
203
204    /// Emit the error `err`.
205    pub fn emit_warning(&self, warning: impl Into<LeoWarning>) {
206        let mut inner = self.inner.borrow_mut();
207        inner.warn_count = inner.warn_count.saturating_add(1);
208        inner.emitter.emit_warning(warning.into());
209    }
210
211    /// Emit a warning only once per span. Subsequent calls with the same span are ignored.
212    pub fn emit_warning_once(&self, span: Span, warning: impl Into<LeoWarning>) {
213        let mut inner = self.inner.borrow_mut();
214        let warn = warning.into();
215        if inner.warned_spans.insert((span, warn.error_code())) {
216            inner.warn_count = inner.warn_count.saturating_add(1);
217            inner.emitter.emit_warning(warn);
218        }
219    }
220
221    /// The number of errors thus far.
222    pub fn err_count(&self) -> usize {
223        self.inner.borrow().err_count
224    }
225
226    /// The number of warnings thus far.
227    pub fn warning_count(&self) -> usize {
228        self.inner.borrow().warn_count
229    }
230
231    /// Did we have any errors thus far?
232    pub fn had_errors(&self) -> bool {
233        self.err_count() > 0
234    }
235
236    /// Gets the last emitted error's exit code if it exists.
237    /// Then exits the program with it if it did exist.
238    pub fn last_err(&self) -> Result<(), LeoError> {
239        if let Some(code) = self.last_emitted_err_code() { Err(LeoError::LastErrorCode(code)) } else { Ok(()) }
240    }
241
242    /// Extend handler with `error` given `res = Err(error)`.
243    #[allow(clippy::result_unit_err)]
244    pub fn extend_if_error<T>(&self, res: Result<T, LeoError>) -> Result<T, ()> {
245        match res {
246            Ok(_) if self.had_errors() => Err(()),
247            Ok(x) => Ok(x),
248            Err(e) => {
249                self.emit_err(e);
250                Err(())
251            }
252        }
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use crate::Formatted;
260    use leo_span::{Span, create_session_if_not_set_then};
261
262    #[test]
263    fn fresh_no_errors() {
264        let handler = Handler::new(BufferEmitter::new());
265        assert_eq!(handler.err_count(), 0);
266        assert!(!handler.had_errors());
267    }
268
269    #[test]
270    fn buffer_works() {
271        create_session_if_not_set_then(|_| {
272            let res: Result<(), _> = Handler::with(|h| {
273                let s = Span::default();
274                assert_eq!(h.err_count(), 0);
275                h.emit_err(Formatted::error("TST", 0, "test error", s));
276                assert_eq!(h.err_count(), 1);
277                h.emit_err(Formatted::error("TST", 0, "test error", s));
278                assert_eq!(h.err_count(), 2);
279                Err(Formatted::error("TST", 0, "test error", s).into())
280            });
281
282            assert_eq!(res.unwrap_err().len(), 3);
283
284            let res: Result<(), _> = Handler::with(|h| {
285                let s = Span::default();
286                h.emit_err(Formatted::error("TST", 0, "test error", s));
287                h.emit_err(Formatted::error("TST", 0, "test error", s));
288                Ok(())
289            });
290            assert_eq!(res.unwrap_err().len(), 2);
291
292            Handler::with(|_| Ok(())).unwrap();
293        })
294    }
295}