Skip to main content

luaur_compiler/records/
compile_error.rs

1extern crate alloc;
2
3use alloc::string::String;
4use luaur_ast::records::location::Location;
5
6#[derive(Debug, Clone)]
7pub struct CompileError {
8    pub(crate) location: Location,
9    pub(crate) message: String,
10    /// A NUL-terminated copy of `message` for the C++-style [`CompileError::what`],
11    /// which returns `*const c_char` and is read by callers with `CStr::from_ptr`.
12    ///
13    /// A Rust `String` is **not** NUL-terminated, so handing out
14    /// `message.as_ptr()` makes the reader over-run past the buffer into adjacent
15    /// memory — undefined behavior that surfaces as flaky trailing garbage
16    /// depending on the allocator / load (the cross-platform failure that
17    /// followed issue #3). Materialized once at construction so the pointer stays
18    /// valid for `&self`'s lifetime, mirroring C++'s `std::string::c_str()`.
19    pub(crate) c_message: alloc::ffi::CString,
20}
21
22impl CompileError {
23    /// Build a `CompileError`, materializing the NUL-terminated `what()` view.
24    pub(crate) fn new(location: Location, message: String) -> CompileError {
25        let c_message = nul_terminated(&message);
26        CompileError {
27            location,
28            message,
29            c_message,
30        }
31    }
32}
33
34/// Build a NUL-terminated C string from `s`. Compile-error messages never
35/// contain interior NUL bytes; strip any defensively so construction (which may
36/// run while a panic is being raised) can never itself fail.
37pub(crate) fn nul_terminated(s: &str) -> alloc::ffi::CString {
38    match alloc::ffi::CString::new(s) {
39        Ok(c) => c,
40        Err(_) => alloc::ffi::CString::new(s.replace('\0', "")).unwrap_or_default(),
41    }
42}
43
44impl core::fmt::Display for CompileError {
45    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46        write!(f, "{}", self.message)
47    }
48}
49
50impl std::error::Error for CompileError {}