re_types_builder/
report.rs

1use std::sync::mpsc;
2
3use camino::Utf8Path;
4
5/// Creates a new context.
6///
7/// The [`Reporter`] can be freely cloned and sent to other threads.
8///
9/// The [`Report`] should not be sent to other threads.
10pub fn init() -> (Report, Reporter) {
11    let (errors_tx, errors_rx) = mpsc::channel();
12    let (warnings_tx, warnings_rx) = mpsc::channel();
13    (
14        Report::new(errors_rx, warnings_rx),
15        Reporter::new(errors_tx, warnings_tx),
16    )
17}
18
19/// Used to accumulate errors and warnings.
20#[derive(Clone)]
21pub struct Reporter {
22    errors: mpsc::Sender<String>,
23    warnings: mpsc::Sender<String>,
24}
25
26impl Reporter {
27    fn new(errors: mpsc::Sender<String>, warnings: mpsc::Sender<String>) -> Self {
28        Self { errors, warnings }
29    }
30
31    /// Error about a file as a whole.
32    ///
33    /// Use sparingly for things like failing to write a file or failing to format it.
34    #[expect(clippy::needless_pass_by_value)] // `&impl ToString` has worse usability
35    pub fn error_file(&self, path: &Utf8Path, text: impl ToString) {
36        self.errors
37            .send(format!("{path}: {}", text.to_string()))
38            .ok();
39    }
40
41    #[expect(clippy::needless_pass_by_value)] // `&impl ToString` has worse usability
42    pub fn error(&self, virtpath: &str, fqname: &str, text: impl ToString) {
43        self.errors
44            .send(format!(
45                "{} {fqname}: {}",
46                Self::format_virtpath(virtpath),
47                text.to_string()
48            ))
49            .ok();
50    }
51
52    #[expect(clippy::needless_pass_by_value)] // `&impl ToString` has worse usability
53    pub fn warn_no_context(&self, text: impl ToString) {
54        self.warnings.send(text.to_string()).ok();
55    }
56
57    #[expect(clippy::needless_pass_by_value)] // `&impl ToString` has worse usability
58    pub fn warn(&self, virtpath: &str, fqname: &str, text: impl ToString) {
59        self.warnings
60            .send(format!(
61                "{} {fqname}: {}",
62                Self::format_virtpath(virtpath),
63                text.to_string()
64            ))
65            .ok();
66    }
67
68    #[expect(clippy::needless_pass_by_value)] // `&impl ToString` has worse usability
69    pub fn error_any(&self, text: impl ToString) {
70        self.errors.send(text.to_string()).ok();
71    }
72
73    // Tries to format a virtual fbs path such that it can be clicked in the CLI.
74    fn format_virtpath(virtpath: &str) -> String {
75        if let Ok(path) = Utf8Path::new(virtpath).canonicalize() {
76            path.display().to_string()
77        } else if let Ok(path) =
78            Utf8Path::new(&format!("crates/store/re_types/definitions/{virtpath}")).canonicalize()
79        {
80            path.display().to_string()
81        } else {
82            virtpath.to_owned()
83        }
84    }
85}
86
87/// Report which holds accumulated errors and warnings.
88///
89/// This should only exist on the main thread.
90pub struct Report {
91    errors: mpsc::Receiver<String>,
92    warnings: mpsc::Receiver<String>,
93    _not_send: std::marker::PhantomData<*mut ()>,
94}
95
96impl Report {
97    fn new(errors: mpsc::Receiver<String>, warnings: mpsc::Receiver<String>) -> Self {
98        Self {
99            errors,
100            warnings,
101            _not_send: std::marker::PhantomData,
102        }
103    }
104
105    /// This outputs all errors and warnings to stderr and panics if there were any errors.
106    pub fn finalize(&self, warnings_as_errors: bool) {
107        use colored::Colorize as _;
108
109        let mut any_errors = false;
110
111        while let Ok(warn) = self.warnings.try_recv() {
112            if warnings_as_errors {
113                any_errors = true;
114                eprintln!(
115                    "{} {}",
116                    "Error (warnings as errors enabled): ".red().bold(),
117                    warn
118                );
119            } else {
120                eprintln!("{} {}", "Warning: ".yellow().bold(), warn);
121            }
122        }
123
124        while let Ok(err) = self.errors.try_recv() {
125            any_errors = true;
126            eprintln!("{} {}", "Error: ".red().bold(), err);
127        }
128
129        if any_errors {
130            println!("Some errors occurred.");
131            std::process::exit(1);
132        }
133    }
134}
135
136const _: () = {
137    // We want to ensure `Report` is `!Send`, so that it stays
138    // on the main thread.
139    //
140    // This works by creating a type which has a different number of possible
141    // implementations of a trait depending on its `Send`-ness:
142    // - One impl for all `T: !Send`
143    // - Two impls for all `T: Send`
144    //
145    // In an invocation like `Check<T> as IsNotSend<_>`, we're asking
146    // the compiler to infer the type given to `IsNotSend` for us.
147    // But if `Check<T>: Send`, then it has two possible implementations:
148    // `IsNotSend<True>`, and `IsNotSend<False>`. This is a local ambiguity
149    // and rustc has no way to disambiguate between the two implementations,
150    // so it will instead output a type inference error.
151    //
152    // We could use `static_assertions` here, but we'd rather not pay the
153    // compilation cost for a single invocation. Some of the macros from
154    // that crate are pretty gnarly!
155    trait IsNotSend<T> {
156        fn __() {}
157    }
158
159    type False = ();
160
161    struct True;
162
163    struct Check<T: ?Sized>(T);
164
165    impl<T: ?Sized> IsNotSend<True> for Check<T> {}
166
167    impl<T: ?Sized + Send> IsNotSend<False> for Check<T> {}
168
169    // if this fails with a type inference error,
170    // then `Report` is `Send`, which it should _not_ be.
171    let _ = <Check<Report> as IsNotSend<_>>::__;
172
173    fn assert_send<T: Send>() {}
174    let _ = assert_send::<Reporter>;
175};