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 {}