Skip to main content

imping_codegen/
error.rs

1//! Error and warning types.
2
3use core::fmt;
4use imping_ir::ValidationErrors;
5
6/// Why [`Generator::generate`](crate::Generator::generate) failed.
7///
8/// Messages name the module, item or build involved.
9#[derive(Debug)]
10#[non_exhaustive]
11pub enum Error {
12    /// The IR failed validation.
13    InvalidIr(ValidationErrors),
14    /// Two IR modules would get the same Rust module name.
15    ModuleNameClash {
16        /// The first IR module with that name.
17        first: String,
18        /// The second IR module with that name.
19        second: String,
20        /// The Rust module name both would get.
21        rust_name: String,
22    },
23    /// [`Generator::module_name`](crate::Generator::module_name) or
24    /// [`Generator::accept_build`](crate::Generator::accept_build) names a
25    /// module that isn't in the IR.
26    UnknownModule {
27        /// The option that named it: `"module_name"` or `"accept_build"`.
28        option: &'static str,
29        /// The IR module name given.
30        name: String,
31    },
32    /// [`Generator::module_name`](crate::Generator::module_name) was given a
33    /// name that isn't a valid Rust module name.
34    InvalidModuleName {
35        /// The Rust module name given.
36        name: String,
37    },
38    /// Two builds have the same label, or labels that make the same `Build`
39    /// variant.
40    DuplicateBuild {
41        /// The second build's label.
42        label: String,
43    },
44    /// A build's label isn't lowercase letters, digits and single
45    /// underscores, starting with a letter.
46    InvalidBuildLabel {
47        /// The label given.
48        label: String,
49    },
50    /// [`Generator::accept_build`](crate::Generator::accept_build) names a
51    /// build label no IR has.
52    UnknownBuild {
53        /// The label given.
54        label: String,
55    },
56    /// Two builds' IRs disagree on a type, a function's signature or a
57    /// global's type, so one set of bindings can't fit both.
58    BuildsDiffer(Box<BuildsDiffer>),
59}
60
61/// What two builds' IRs disagree on; see [`Error::BuildsDiffer`].
62///
63/// Its [`Display`](fmt::Display) is the error's message.
64#[derive(Debug, Clone, PartialEq, Eq)]
65#[non_exhaustive]
66pub struct BuildsDiffer {
67    /// The IR module the item is in.
68    pub module: String,
69    /// The item, such as ``type `cVec4` `` or ``function `Entity::Update` ``.
70    pub item: String,
71    /// The label of the build whose IR has it first.
72    pub first: String,
73    /// The label of the build that disagrees.
74    pub second: String,
75    /// How they differ, such as `size 0x10 vs 0x20`.
76    pub difference: String,
77}
78
79impl fmt::Display for BuildsDiffer {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        let BuildsDiffer {
82            module,
83            item,
84            first,
85            second,
86            difference,
87        } = self;
88        write!(
89            f,
90            "`{module}`: {item} differs between builds `{first}` and `{second}`: {difference}"
91        )
92    }
93}
94
95impl fmt::Display for Error {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            Error::InvalidIr(errors) => write!(f, "the IR is invalid: {errors}"),
99            Error::ModuleNameClash {
100                first,
101                second,
102                rust_name,
103            } => write!(
104                f,
105                "IR modules `{first}` and `{second}` both become Rust module `{rust_name}`; rename one"
106            ),
107            Error::UnknownModule { option, name } => {
108                write!(f, "`{option}` names `{name}`, which is not in the IR")
109            }
110            Error::InvalidModuleName { name } => {
111                write!(f, "`{name}` is not a valid Rust module name")
112            }
113            Error::DuplicateBuild { label } => {
114                write!(
115                    f,
116                    "build `{label}` is given twice, or as another label's `Build` variant"
117                )
118            }
119            Error::InvalidBuildLabel { label } => write!(
120                f,
121                "`{label}` is not a valid build label; use lowercase letters, digits and single \
122                 underscores, starting with a letter, as in `steam_2021`"
123            ),
124            Error::UnknownBuild { label } => {
125                write!(f, "`accept_build` names build `{label}`, which no IR has")
126            }
127            Error::BuildsDiffer(differ) => differ.fmt(f),
128        }
129    }
130}
131
132impl core::error::Error for Error {
133    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
134        match self {
135            Error::InvalidIr(errors) => Some(errors),
136            _ => None,
137        }
138    }
139}
140
141/// Something the generator skipped or simplified.
142///
143/// Warnings never stop generation. Their [`Display`](fmt::Display) names the
144/// module and item.
145#[derive(Debug, Clone, PartialEq, Eq)]
146#[non_exhaustive]
147pub enum Warning {
148    /// A function uses custom argument storage, so only its RVA is generated.
149    CustomStorage {
150        /// The IR module the function is in.
151        module: String,
152        /// The function's name, including its namespace.
153        function: String,
154    },
155    /// A struct or union's layout can't be represented in Rust, so it was
156    /// emitted as opaque bytes of its size.
157    TypeMadeOpaque {
158        /// The IR module the type is in.
159        module: String,
160        /// The type's IR name.
161        type_name: String,
162        /// Why its layout can't be represented.
163        reason: String,
164    },
165    /// A function was skipped.
166    FunctionSkipped {
167        /// The IR module the function is in.
168        module: String,
169        /// The function's name, including its namespace.
170        function: String,
171        /// Why it was skipped.
172        reason: String,
173    },
174    /// A function can be called but not hooked, so it has no `hook`.
175    ///
176    /// Not raised for variadic functions, which never get a hook.
177    HookUnavailable {
178        /// The IR module the function is in.
179        module: String,
180        /// The function's name, including its namespace.
181        function: String,
182        /// Why it can't be hooked.
183        reason: String,
184    },
185    /// A function is tagged `imping-safe`, but a parameter has no safe form,
186    /// so its call wrapper stays `unsafe`.
187    SafeCallUnavailable {
188        /// The IR module the function is in.
189        module: String,
190        /// The function's name, including its namespace.
191        function: String,
192        /// Why the call can't be safe.
193        reason: String,
194    },
195    /// An enum constant's value fits neither the signed nor the unsigned
196    /// range of the enum's underlying type, so it was truncated to fit.
197    ///
198    /// A value that fits the other signedness of the same width (such as -1
199    /// in a `u32` enum) wraps as in C, and is not reported.
200    EnumValueTruncated {
201        /// The IR module the enum is in.
202        module: String,
203        /// The enum's IR name.
204        enum_name: String,
205        /// The constant's IR name.
206        variant: String,
207        /// The constant's value in the IR.
208        value: i64,
209    },
210}
211
212impl fmt::Display for Warning {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        match self {
215            Warning::CustomStorage { module, function } => write!(
216                f,
217                "`{module}` function `{function}` uses custom storage; only its RVA is generated"
218            ),
219            Warning::TypeMadeOpaque {
220                module,
221                type_name,
222                reason,
223            } => write!(
224                f,
225                "`{module}` type `{type_name}` emitted as opaque bytes: {reason}"
226            ),
227            Warning::FunctionSkipped {
228                module,
229                function,
230                reason,
231            } => write!(f, "`{module}` function `{function}` skipped: {reason}"),
232            Warning::HookUnavailable {
233                module,
234                function,
235                reason,
236            } => write!(
237                f,
238                "`{module}` function `{function}` can be called but not hooked: {reason}"
239            ),
240            Warning::SafeCallUnavailable {
241                module,
242                function,
243                reason,
244            } => write!(
245                f,
246                "`{module}` function `{function}` is tagged `imping-safe` but its call stays \
247                 unsafe: {reason}"
248            ),
249            Warning::EnumValueTruncated {
250                module,
251                enum_name,
252                variant,
253                value,
254            } => write!(
255                f,
256                "`{module}` enum `{enum_name}` constant `{variant}` = {value} does not fit \
257                 the underlying type and was truncated"
258            ),
259        }
260    }
261}