Skip to main content

weaveffi_model/abi/
ctype.rs

1//! The C-type algebra used by the ABI model.
2//!
3//! [`CType`] is a prefix-agnostic description of a single C type as it appears
4//! in a WeaveFFI ABI signature. The canonical C rendering lives in
5//! [`CType::render_c`]; every other language generator maps `CType` onto its
6//! own FFI vocabulary (ctypes, P/Invoke, `dart:ffi`, ...). Because the
7//! structure is shared, every target agrees on the calling convention by
8//! construction.
9
10/// Placement of a `const` qualifier on a pointer.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ConstPos {
13    /// No `const` (mutable pointer): `T*`.
14    None,
15    /// West `const`: `const T*`.
16    West,
17}
18
19/// A single C type in an ABI signature, independent of the configured symbol
20/// prefix (applied at render time by [`CType::render_c`]).
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum CType {
23    /// `int8_t` (the `i8` primitive).
24    Int8,
25    /// `int16_t` (the `i16` primitive).
26    Int16,
27    /// `int32_t` (the `i32` primitive).
28    Int32,
29    /// `int64_t` (the `i64` primitive).
30    Int64,
31    /// `uint8_t` as a standalone scalar (the `u8` primitive). Also used as the
32    /// pointee of a byte-buffer pointer.
33    Uint8,
34    /// `uint16_t` (the `u16` primitive).
35    Uint16,
36    /// `uint32_t` (the `u32` primitive).
37    Uint32,
38    /// `uint64_t` (the `u64` primitive).
39    Uint64,
40    /// `float` (the `f32` primitive).
41    Float,
42    /// `double` (the `f64` primitive).
43    Double,
44    /// `bool` (from `<stdbool.h>`).
45    Bool,
46    /// `size_t`.
47    Size,
48    /// `char` (only appears as the pointee of a string pointer).
49    Char,
50    /// `void`.
51    Void,
52    /// `{prefix}_cancel_token`.
53    CancelToken,
54    /// `{prefix}_error`.
55    Error,
56    /// An enum value type: `{prefix}_{module}_{name}`.
57    Enum {
58        /// Underscore-joined symbol path of the module that declares the enum.
59        module: String,
60        /// The enum's bare type name.
61        name: String,
62    },
63    /// An interface's opaque object tag: `{prefix}_{module}_{name}`.
64    StructTag {
65        /// Underscore-joined symbol path of the module that declares the type.
66        module: String,
67        /// The interface's bare type name.
68        name: String,
69    },
70    /// A callback interface's vtable struct: `{prefix}_{module}_{name}_vtable`.
71    VtableTag {
72        /// Underscore-joined symbol path of the module that declares the
73        /// callback interface.
74        module: String,
75        /// The callback interface's bare type name.
76        name: String,
77    },
78    /// A prefixed named type emitted elsewhere in the header (async callback
79    /// function-pointer typedefs, iterator opaque structs, ...). Renders as
80    /// `{prefix}_{core}`.
81    Named(String),
82    /// A pointer to `pointee` with the given `const` placement.
83    Ptr {
84        /// Where the `const` qualifier sits, if any.
85        konst: ConstPos,
86        /// The type the pointer refers to.
87        pointee: Box<CType>,
88    },
89}
90
91impl CType {
92    /// Pointer to `pointee` with no `const`.
93    pub fn ptr(pointee: CType) -> CType {
94        CType::Ptr {
95            konst: ConstPos::None,
96            pointee: Box::new(pointee),
97        }
98    }
99
100    /// Pointer to `pointee` with west `const` (`const T*`).
101    pub fn const_ptr(pointee: CType) -> CType {
102        CType::Ptr {
103            konst: ConstPos::West,
104            pointee: Box::new(pointee),
105        }
106    }
107
108    /// Whether this type is represented as a pointer at the C ABI boundary.
109    pub fn is_pointer(&self) -> bool {
110        matches!(self, CType::Ptr { .. })
111    }
112
113    /// Render this type as the Rust `extern "C"` spelling a producer cdylib
114    /// uses, with `prefix` for every WeaveFFI-owned symbol.
115    ///
116    /// This is the Rust counterpart of [`render_c`](Self::render_c): it is the
117    /// single source of truth for the `extern "C"` signatures the
118    /// `#[weaveffi::module]` macro emits, so a macro-generated signature
119    /// matches the generated C header by construction. Notable lowerings:
120    ///
121    /// * [`Char`](Self::Char) renders bare as `c_char` (the emitted code
122    ///   imports `std::os::raw::c_char`), and [`Void`](Self::Void) as
123    ///   `std::ffi::c_void` (only meaningful as a pointee; a bare `void`
124    ///   *return* is the absence of a `-> T`, handled by the caller).
125    /// * A C-style [`Enum`](Self::Enum) crosses the ABI as its `int`-sized
126    ///   discriminant, so it lowers to `i32` (matching the header's
127    ///   `int`-backed `typedef enum`).
128    /// * A `const` pointer is `*const`; a non-`const` pointer is `*mut`.
129    pub fn render_rust(&self, prefix: &str) -> String {
130        match self {
131            CType::Int8 => "i8".to_string(),
132            CType::Int16 => "i16".to_string(),
133            CType::Int32 => "i32".to_string(),
134            CType::Int64 => "i64".to_string(),
135            CType::Uint8 => "u8".to_string(),
136            CType::Uint16 => "u16".to_string(),
137            CType::Uint32 => "u32".to_string(),
138            CType::Uint64 => "u64".to_string(),
139            CType::Float => "f32".to_string(),
140            CType::Double => "f64".to_string(),
141            CType::Bool => "bool".to_string(),
142            CType::Size => "usize".to_string(),
143            CType::Char => "c_char".to_string(),
144            CType::Void => "std::ffi::c_void".to_string(),
145            // The runtime types come from `weaveffi::abi` and keep their fixed
146            // names regardless of the configured business-symbol prefix.
147            CType::CancelToken => "::weaveffi::abi::FfiCancelToken".to_string(),
148            CType::Error => "::weaveffi::abi::FfiError".to_string(),
149            // A C-style enum is passed/returned as its int discriminant.
150            CType::Enum { .. } => "i32".to_string(),
151            CType::StructTag { module, name } => format!("{prefix}_{module}_{name}"),
152            CType::VtableTag { module, name } => format!("{prefix}_{module}_{name}_vtable"),
153            CType::Named(core) => format!("{prefix}_{core}"),
154            CType::Ptr { konst, pointee } => {
155                let inner = pointee.render_rust(prefix);
156                match konst {
157                    ConstPos::None => format!("*mut {inner}"),
158                    ConstPos::West => format!("*const {inner}"),
159                }
160            }
161        }
162    }
163
164    /// Render this type as canonical C source using `prefix` for every
165    /// WeaveFFI-owned symbol.
166    pub fn render_c(&self, prefix: &str) -> String {
167        match self {
168            CType::Int8 => "int8_t".to_string(),
169            CType::Int16 => "int16_t".to_string(),
170            CType::Int32 => "int32_t".to_string(),
171            CType::Int64 => "int64_t".to_string(),
172            CType::Uint8 => "uint8_t".to_string(),
173            CType::Uint16 => "uint16_t".to_string(),
174            CType::Uint32 => "uint32_t".to_string(),
175            CType::Uint64 => "uint64_t".to_string(),
176            CType::Float => "float".to_string(),
177            CType::Double => "double".to_string(),
178            CType::Bool => "bool".to_string(),
179            CType::Size => "size_t".to_string(),
180            CType::Char => "char".to_string(),
181            CType::Void => "void".to_string(),
182            CType::CancelToken => format!("{prefix}_cancel_token"),
183            CType::Error => format!("{prefix}_error"),
184            CType::Enum { module, name } => format!("{prefix}_{module}_{name}"),
185            CType::StructTag { module, name } => format!("{prefix}_{module}_{name}"),
186            CType::VtableTag { module, name } => format!("{prefix}_{module}_{name}_vtable"),
187            CType::Named(core) => format!("{prefix}_{core}"),
188            CType::Ptr { konst, pointee } => {
189                let inner = pointee.render_c(prefix);
190                match konst {
191                    ConstPos::None => format!("{inner}*"),
192                    ConstPos::West => format!("const {inner}*"),
193                }
194            }
195        }
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn scalars_render() {
205        assert_eq!(CType::Int32.render_c("weaveffi"), "int32_t");
206        assert_eq!(CType::Size.render_c("weaveffi"), "size_t");
207        assert_eq!(CType::Error.render_c("myffi"), "myffi_error");
208    }
209
210    #[test]
211    fn string_pointer_renders_with_west_const() {
212        let s = CType::const_ptr(CType::Char);
213        assert_eq!(s.render_c("weaveffi"), "const char*");
214        assert_eq!(CType::ptr(CType::Char).render_c("weaveffi"), "char*");
215    }
216
217    #[test]
218    fn struct_and_vtable_tags_use_prefix_and_module() {
219        let t = CType::StructTag {
220            module: "contacts".into(),
221            name: "Book".into(),
222        };
223        assert_eq!(t.render_c("weaveffi"), "weaveffi_contacts_Book");
224        assert_eq!(
225            CType::ptr(t).render_c("weaveffi"),
226            "weaveffi_contacts_Book*"
227        );
228        let v = CType::VtableTag {
229            module: "events".into(),
230            name: "Listener".into(),
231        };
232        assert_eq!(
233            CType::const_ptr(v.clone()).render_c("weaveffi"),
234            "const weaveffi_events_Listener_vtable*"
235        );
236        assert_eq!(
237            CType::const_ptr(v).render_rust("weaveffi"),
238            "*const weaveffi_events_Listener_vtable"
239        );
240    }
241
242    #[test]
243    fn named_type_is_prefixed() {
244        assert_eq!(
245            CType::Named("events_fetch_callback".into()).render_c("weaveffi"),
246            "weaveffi_events_fetch_callback"
247        );
248    }
249
250    #[test]
251    fn rust_scalars_render() {
252        assert_eq!(CType::Int32.render_rust("weaveffi"), "i32");
253        assert_eq!(CType::Uint8.render_rust("weaveffi"), "u8");
254        assert_eq!(CType::Float.render_rust("weaveffi"), "f32");
255        assert_eq!(CType::Double.render_rust("weaveffi"), "f64");
256        assert_eq!(CType::Bool.render_rust("weaveffi"), "bool");
257        assert_eq!(CType::Size.render_rust("weaveffi"), "usize");
258    }
259
260    #[test]
261    fn rust_runtime_types_name_the_runtime_structs() {
262        assert_eq!(
263            CType::Error.render_rust("acme"),
264            "::weaveffi::abi::FfiError"
265        );
266        assert_eq!(
267            CType::CancelToken.render_rust("acme"),
268            "::weaveffi::abi::FfiCancelToken"
269        );
270    }
271
272    #[test]
273    fn rust_string_param_is_const_char_pointer() {
274        assert_eq!(
275            CType::const_ptr(CType::Char).render_rust("weaveffi"),
276            "*const c_char"
277        );
278        assert_eq!(
279            CType::ptr(CType::Char).render_rust("weaveffi"),
280            "*mut c_char"
281        );
282    }
283
284    #[test]
285    fn rust_c_style_enum_lowers_to_i32() {
286        let e = CType::Enum {
287            module: "gfx".into(),
288            name: "Color".into(),
289        };
290        assert_eq!(e.render_rust("weaveffi"), "i32");
291    }
292
293    #[test]
294    fn rust_void_is_only_a_pointee() {
295        assert_eq!(
296            CType::ptr(CType::Void).render_rust("weaveffi"),
297            "*mut std::ffi::c_void"
298        );
299    }
300}