Skip to main content

prebindgen_registry/registry/
error.rs

1//! What can go wrong before a binding is written.
2
3use std::fmt;
4
5use prebindgen::SourceLocation;
6
7impl From<prebindgen_flat::flat::ParseError> for ScanError {
8    fn from(e: prebindgen_flat::flat::ParseError) -> Self {
9        match e {
10            prebindgen_flat::flat::ParseError::DuplicateName(d) => {
11                ScanError::DuplicateName(Box::new(DuplicateNameError {
12                    name: d.name,
13                    first: d.first,
14                    second: d.second,
15                    first_crate: d.first_crate,
16                    second_crate: d.second_crate,
17                }))
18            }
19        }
20    }
21}
22
23/// One item of a [`ScanError::NotExpressible`] report.
24#[derive(Debug)]
25pub struct NotExpressibleEntry {
26    /// The item's name, or `None` for an item kind that has none.
27    pub name: Option<syn::Ident>,
28    /// Rendered [`ItemError`](prebindgen_flat::flat::ItemError) — the frontend's own
29    /// message, so one authority produces it.
30    pub reason: String,
31    pub location: SourceLocation,
32}
33
34/// Payload of [`ScanError::DuplicateName`], boxed to keep the error enum
35/// small (`clippy::result_large_err`).
36#[derive(Debug)]
37pub struct DuplicateNameError {
38    pub name: syn::Ident,
39    pub first: SourceLocation,
40    pub second: SourceLocation,
41    /// Origin crates of the colliding items, when known (multi-source
42    /// ingestion via several `Flat::builder().source(..)` feeders) — the `SourceLocation`
43    /// file paths are crate-relative, so with several sources they alone
44    /// may not identify the colliding crates.
45    pub first_crate: Option<String>,
46    pub second_crate: Option<String>,
47}
48
49/// Errors surfaced by the scan phase.
50#[derive(Debug)]
51pub enum ScanError {
52    DuplicateName(Box<DuplicateNameError>),
53    /// Items the flat language cannot express, all of them at once.
54    ///
55    /// The message for each comes from
56    /// [`ItemError`](prebindgen_flat::flat::ItemError), so one authority produces it.
57    /// This replaces the per-item guards the registry used to duplicate — a `self`
58    /// receiver, a non-ident parameter pattern, a disallowed `impl Trait` — which
59    /// the frontend now catches with a richer diagnosis (it names the parameter).
60    NotExpressible {
61        entries: Vec<NotExpressibleEntry>,
62    },
63    /// An adapter-invariant check failed — see
64    /// [`Prebindgen::validate`](crate::Prebindgen::validate).
65    /// The message is adapter-authored and printed verbatim.
66    AdapterInvariant {
67        message: String,
68    },
69    /// Explicitly declared items (functions, helper functions, constants)
70    /// that match no indexed `#[prebindgen]` item. A declaration is a
71    /// statement of intent — its target being absent is always a bug (a
72    /// typo in build.rs, or the item was renamed/removed in the source
73    /// crate), so this is a hard error, unlike the soft warnings for stale
74    /// *ignore* entries. All missing names are collected before failing.
75    DeclaredNotFound {
76        entries: Vec<(&'static str, String)>,
77    },
78    /// Declared type keys that qualify a source item with its crate path
79    /// (`ptr_class!(myflat::Foo)` where `myflat` is a chained source crate).
80    /// Source items live in one flat namespace and are keyed by their bare
81    /// name — the qualified spelling can never match a captured signature,
82    /// so it is a hard error with a fix-it instead of a silent miss (issue
83    /// #95). All offenders are collected before failing.
84    QualifiedDeclaredTypes {
85        /// `(qualified spelling, bare fix-it name)` pairs.
86        entries: Vec<(String, String)>,
87    },
88}
89
90impl fmt::Display for ScanError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self {
93            ScanError::DuplicateName(e) => {
94                let in_crate = |c: &Option<String>| match c {
95                    Some(c) => format!(" in crate `{c}`"),
96                    None => String::new(),
97                };
98                write!(
99                    f,
100                    "duplicate prebindgen name `{}`: first{} at {}, second{} at {} — prebindgen \
101                     items live in one flat namespace across all sources; rename one of them",
102                    e.name,
103                    in_crate(&e.first_crate),
104                    e.first,
105                    in_crate(&e.second_crate),
106                    e.second
107                )
108            }
109            ScanError::NotExpressible { entries } => {
110                // Not "`#[prebindgen]` item(s)": two populations reach this report
111                // and only one of them is a marked item. The other is a type the
112                // *binding* put on the boundary — a declared crossing, or a
113                // spelling expansion composed — which no source crate ever wrote
114                // and whose author would go looking for a `#[prebindgen]` that is
115                // not there. Each entry's own line says which it is.
116                write!(
117                    f,
118                    "the flat language cannot express {} of this binding's items and types:",
119                    entries.len()
120                )?;
121                for e in entries {
122                    // The crate, because a captured path is crate-relative: with
123                    // several sources, two offenders both read `src/lib.rs:..`
124                    // and the location alone says nothing about which one to fix.
125                    // Same reason the duplicate-name diagnostic carries it.
126                    //
127                    // Gated on `has_position`, because not every offender has a
128                    // place: a type a binding composed was never written in a file,
129                    // and neither was an item from a hand-built stream. Rendering
130                    // the default location anyway prints `:0:0:`, which reads as a
131                    // real position — the fault this whole `has_position` split
132                    // exists to prevent, and it is worse than saying nothing.
133                    let mut prefix = String::new();
134                    if e.location.has_position() {
135                        prefix.push_str(&e.location.to_string());
136                    }
137                    if let Some(c) = &e.location.crate_name {
138                        if !prefix.is_empty() {
139                            prefix.push(' ');
140                        }
141                        prefix.push_str(&format!("in crate `{c}`"));
142                    }
143                    if !prefix.is_empty() {
144                        prefix.push_str(": ");
145                    }
146                    match &e.name {
147                        Some(name) => write!(f, "\n  {prefix}{name} {}", e.reason)?,
148                        None => write!(f, "\n  {prefix}{}", e.reason)?,
149                    }
150                }
151                Ok(())
152            }
153            ScanError::AdapterInvariant { message } => write!(f, "{}", message),
154            ScanError::DeclaredNotFound { entries } => {
155                writeln!(
156                    f,
157                    "{} declared item(s) not found among #[prebindgen] items:",
158                    entries.len()
159                )?;
160                for (kind, name) in entries {
161                    writeln!(f, "  - {kind} `{name}`")?;
162                }
163                write!(
164                    f,
165                    "a declaration names an item that does not exist — typo in build.rs, \
166                     or renamed/removed in the source crate?"
167                )
168            }
169            ScanError::QualifiedDeclaredTypes { entries } => {
170                writeln!(
171                    f,
172                    "{} declared type(s) qualify a source item with its crate path:",
173                    entries.len()
174                )?;
175                for (spelled, bare) in entries {
176                    writeln!(f, "  - `{spelled}` — declare it as `{bare}`")?;
177                }
178                write!(
179                    f,
180                    "source items live in one flat namespace keyed by their bare name; \
181                     a crate-qualified spelling never matches captured signatures"
182                )
183            }
184        }
185    }
186}
187
188impl std::error::Error for ScanError {}
189
190/// Combined error surfaced by `RegistryBuilder::build` and by a generator's
191/// own `build` / `write_rust`.
192#[derive(Debug)]
193pub enum WriteRustError {
194    Scan(ScanError),
195    Expand(crate::expand::ExpandError),
196    Unfold(crate::unfold::UnfoldError),
197    Resolve(crate::resolve::ResolveError),
198    Write(crate::write::WriteError),
199}
200
201impl fmt::Display for WriteRustError {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            WriteRustError::Scan(e) => write!(f, "{}", e),
205            WriteRustError::Expand(e) => write!(f, "{}", e),
206            WriteRustError::Unfold(e) => write!(f, "{}", e),
207            WriteRustError::Resolve(e) => write!(f, "{}", e),
208            WriteRustError::Write(e) => write!(f, "{}", e),
209        }
210    }
211}
212
213impl std::error::Error for WriteRustError {}
214
215impl From<ScanError> for WriteRustError {
216    fn from(e: ScanError) -> Self {
217        WriteRustError::Scan(e)
218    }
219}
220
221impl From<crate::expand::ExpandError> for WriteRustError {
222    fn from(e: crate::expand::ExpandError) -> Self {
223        WriteRustError::Expand(e)
224    }
225}
226
227impl From<crate::unfold::UnfoldError> for WriteRustError {
228    fn from(e: crate::unfold::UnfoldError) -> Self {
229        WriteRustError::Unfold(e)
230    }
231}
232
233impl From<crate::resolve::ResolveError> for WriteRustError {
234    fn from(e: crate::resolve::ResolveError) -> Self {
235        WriteRustError::Resolve(e)
236    }
237}
238
239impl From<crate::write::WriteError> for WriteRustError {
240    fn from(e: crate::write::WriteError) -> Self {
241        WriteRustError::Write(e)
242    }
243}