Skip to main content

weaveffi_core/
cabi.rs

1//! Shared rendering of the **C ABI declarations** from a
2//! [`BindingModel`](crate::model::BindingModel).
3//!
4//! Both the C generator (which emits the canonical `{prefix}.h`) and the C++
5//! generator (whose idiomatic wrapper opens an `extern "C"` block re-declaring
6//! the same symbols) render their C declarations through this module. Before it
7//! existed the two re-derived the ABI independently and drifted. Most visibly,
8//! the C++ `extern "C"` block lowered `iter<T>` as a list and omitted callbacks
9//! and listeners entirely. Routing both through one model-driven renderer makes
10//! that class of drift impossible.
11
12use std::fmt::Write;
13
14use crate::abi::AbiParam;
15use crate::codegen::common::{emit_doc as common_emit_doc, DocCommentStyle};
16use crate::codegen::CodeWriter;
17use crate::model::{AbiFn, CallShape, EnumBinding, ModuleBinding, StructBinding};
18
19/// Emit a `/** ... */` doc comment at `indent`.
20pub fn emit_doc(out: &mut String, doc: &Option<String>, indent: &str) {
21    common_emit_doc(out, doc, indent, DocCommentStyle::Javadoc);
22}
23
24/// Join lowered ABI slots into a `"<c-type> <name>, ..."` declaration string.
25pub fn params_str(params: &[AbiParam], prefix: &str) -> String {
26    params
27        .iter()
28        .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name))
29        .collect::<Vec<_>>()
30        .join(", ")
31}
32
33/// The export-visibility macro name for `prefix`, for example `WEAVEFFI_API`.
34///
35/// Every exported function prototype is tagged with this macro so a non-Rust
36/// producer that implements the header can export the symbols under hidden
37/// default visibility, and Windows consumers import them through `dllimport`.
38/// See [`render_visibility_macros`] for the macro's definition.
39fn export_macro(prefix: &str) -> String {
40    format!("{}_API", prefix.to_uppercase())
41}
42
43/// The deprecation macro name for `prefix`, for example `WEAVEFFI_DEPRECATED`.
44///
45/// Used in place of a bare `__attribute__((deprecated))` so the marker also
46/// compiles under MSVC (which spells it `__declspec(deprecated(...))`).
47fn deprecated_macro(prefix: &str) -> String {
48    format!("{}_DEPRECATED", prefix.to_uppercase())
49}
50
51/// Render the portable export-visibility and deprecation macros that the C ABI
52/// declarations are tagged with.
53///
54/// The C ABI header is both consumed (callers link the prebuilt library) and,
55/// for non-Rust producers, implemented directly (C, C++, or Zig supply the
56/// symbols). A bare prototype exports nothing under hidden default visibility
57/// (`-fvisibility=hidden`, the norm for release builds and the MSVC default),
58/// so an implementing library compiled that way ships no usable symbols. These
59/// macros fix that portably:
60///
61/// - `{PREFIX}_API` expands to `__declspec(dllexport)` when the producer
62///   defines `{PREFIX}_BUILD`, `__declspec(dllimport)` otherwise on Windows,
63///   `__attribute__((visibility("default")))` on GCC and Clang, and nothing
64///   elsewhere.
65/// - `{PREFIX}_DEPRECATED(msg)` expands to the compiler's deprecation marker.
66///
67/// Both definitions are wrapped in `#ifndef` guards so a translation unit that
68/// includes both the C header and the C++ header (which inlines the same
69/// declarations) defines each macro only once. The names are derived from the
70/// configured symbol prefix so two WeaveFFI libraries included together never
71/// collide.
72pub fn render_visibility_macros(out: &mut String, prefix: &str) {
73    let body = r#"#ifndef @U@_API
74#  if defined(_WIN32) || defined(__CYGWIN__)
75#    ifdef @U@_BUILD
76#      define @U@_API __declspec(dllexport)
77#    else
78#      define @U@_API __declspec(dllimport)
79#    endif
80#  elif defined(__GNUC__) && (__GNUC__ >= 4)
81#    define @U@_API __attribute__((visibility("default")))
82#  else
83#    define @U@_API
84#  endif
85#endif
86
87#ifndef @U@_DEPRECATED
88#  if defined(_MSC_VER)
89#    define @U@_DEPRECATED(msg) __declspec(deprecated(msg))
90#  elif defined(__GNUC__) || defined(__clang__)
91#    define @U@_DEPRECATED(msg) __attribute__((deprecated(msg)))
92#  else
93#    define @U@_DEPRECATED(msg)
94#  endif
95#endif
96
97"#;
98    out.push_str(&body.replace("@U@", &prefix.to_uppercase()));
99}
100
101/// Render a full `{API} {ret} {symbol}({params});` declaration for a lowered
102/// symbol, tagged with the export-visibility macro (see
103/// [`render_visibility_macros`]).
104pub fn fn_decl(out: &mut String, f: &AbiFn, prefix: &str) {
105    let _ = writeln!(
106        out,
107        "{} {} {}({});",
108        export_macro(prefix),
109        f.ret.render_c(prefix),
110        f.symbol,
111        params_str(&f.params, prefix)
112    );
113}
114
115/// Render the runtime typedefs and helper prototypes (`handle_t`, `error`,
116/// `free_*`, `cancel_token`) that every WeaveFFI C surface depends on.
117pub fn render_runtime_decls(out: &mut String, prefix: &str) {
118    let api = export_macro(prefix);
119    let _ = write!(
120        out,
121        "typedef uint64_t {prefix}_handle_t;\n\n\
122         typedef struct {prefix}_error {{ int32_t code; const char* message; }} {prefix}_error;\n\n\
123         {api} void {prefix}_error_clear({prefix}_error* err);\n\
124         {api} void {prefix}_free_string(const char* ptr);\n\
125         {api} void {prefix}_free_bytes(uint8_t* ptr, size_t len);\n\n\
126         typedef struct {prefix}_cancel_token {prefix}_cancel_token;\n\
127         {api} {prefix}_cancel_token* {prefix}_cancel_token_create(void);\n\
128         {api} void {prefix}_cancel_token_cancel({prefix}_cancel_token* token);\n\
129         {api} bool {prefix}_cancel_token_is_cancelled(const {prefix}_cancel_token* token);\n\
130         {api} void {prefix}_cancel_token_destroy({prefix}_cancel_token* token);\n\n",
131    );
132}
133
134/// Render an enum's discriminant constants as a C `typedef enum` named
135/// `type_name`. Multi-line when any variant is documented.
136fn render_enum_constants(out: &mut String, e: &EnumBinding, type_name: &str) {
137    let mut w = CodeWriter::four_space();
138    w.doc(&e.doc, DocCommentStyle::Javadoc);
139    if e.variants.iter().any(|v| v.doc.is_some()) {
140        w.block("typedef enum {", format!("}} {type_name};"), |w| {
141            let last = e.variants.len();
142            for (i, v) in e.variants.iter().enumerate() {
143                w.doc(&v.doc, DocCommentStyle::Javadoc);
144                let comma = if i + 1 == last { "" } else { "," };
145                w.line(format!("{} = {}{comma}", v.c_const, v.value));
146            }
147        });
148    } else {
149        let variants: Vec<String> = e
150            .variants
151            .iter()
152            .map(|v| format!("{} = {}", v.c_const, v.value))
153            .collect();
154        w.line(format!(
155            "typedef enum {{ {} }} {type_name};",
156            variants.join(", ")
157        ));
158    }
159    out.push_str(&w.finish());
160}
161
162/// Render a C-style enum typedef. Multi-line when any variant is documented.
163pub fn render_enum_decl(out: &mut String, e: &EnumBinding) {
164    render_enum_constants(out, e, &e.c_tag);
165}
166
167/// Render the *discriminant* enum of a rich (algebraic) enum, named
168/// `{c_tag}_Tag`. The payload-carrying value itself is an opaque struct
169/// `{c_tag}` (forward-declared via [`render_module_type_tags`]); the tag getter
170/// returns one of these discriminant constants as `int32_t`.
171fn render_rich_enum_tag_decl(out: &mut String, e: &EnumBinding) {
172    let tag_enum = format!("{}_Tag", e.c_tag);
173    render_enum_constants(out, e, &tag_enum);
174}
175
176/// Render the function surface of a rich (algebraic) enum: the tag getter, each
177/// variant's constructor and field getters, then the destructor. Assumes the
178/// opaque object tag and every referenced type tag are already forward-declared.
179fn render_rich_enum_fn_decls(out: &mut String, e: &EnumBinding, prefix: &str) {
180    let Some(rich) = &e.rich else {
181        return;
182    };
183    let api = export_macro(prefix);
184    let tag = &e.c_tag;
185    emit_doc(out, &e.doc, "");
186    let _ = writeln!(out, "{api} int32_t {}(const {tag}* self);", rich.tag_symbol);
187    for v in &rich.variants {
188        emit_doc(out, &v.doc, "");
189        fn_decl(out, &v.create, prefix);
190        for field in &v.fields {
191            emit_doc(out, &field.doc, "");
192            let mut parts = vec![format!("const {tag}* self")];
193            parts.extend(
194                field
195                    .getter_out_params
196                    .iter()
197                    .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
198            );
199            let _ = writeln!(
200                out,
201                "{api} {} {}({});",
202                field.getter_ret.render_c(prefix),
203                field.getter_symbol,
204                parts.join(", ")
205            );
206        }
207    }
208    let _ = writeln!(out, "{api} void {}({tag}* self);", rich.destroy_symbol);
209    out.push('\n');
210}
211
212/// Render the opaque struct/builder *tags* (forward typedefs) for one struct.
213///
214/// These reference no other types, so emitting every struct's tags before any
215/// function declaration lets a function in one module accept or return a struct
216/// declared in *another* module (a parent module referencing a child's type).
217fn render_struct_tags(out: &mut String, s: &StructBinding) {
218    let tag = &s.c_tag;
219    let _ = writeln!(out, "typedef struct {tag} {tag};");
220    if let Some(b) = &s.builder {
221        let bt = &b.builder_tag;
222        let _ = writeln!(out, "typedef struct {bt} {bt};");
223    }
224}
225
226/// Render the function declarations for one struct: create/destroy/getters and,
227/// if present, the fluent builder's new/setters/build/destroy. Assumes the
228/// struct (and every other struct it may reference) already has a forward
229/// typedef emitted via [`render_struct_tags`].
230fn render_struct_fn_decls(out: &mut String, s: &StructBinding, prefix: &str) {
231    let api = export_macro(prefix);
232    let tag = &s.c_tag;
233    emit_doc(out, &s.doc, "");
234    fn_decl(out, &s.create, prefix);
235    let _ = writeln!(out, "{api} void {}({tag}* ptr);", s.destroy_symbol);
236    for field in &s.fields {
237        emit_doc(out, &field.doc, "");
238        let mut parts = vec![format!("const {tag}* ptr")];
239        parts.extend(
240            field
241                .getter_out_params
242                .iter()
243                .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
244        );
245        let _ = writeln!(
246            out,
247            "{api} {} {}({});",
248            field.getter_ret.render_c(prefix),
249            field.getter_symbol,
250            parts.join(", ")
251        );
252    }
253    out.push('\n');
254
255    if let Some(b) = &s.builder {
256        let bt = &b.builder_tag;
257        let _ = writeln!(out, "{api} {bt}* {}(void);", b.new_symbol);
258        for (field, (_, setter)) in s.fields.iter().zip(&b.setters) {
259            emit_doc(out, &field.doc, "");
260            let _ = writeln!(
261                out,
262                "{api} void {setter}({bt}* builder, {});",
263                params_str(&field.value_params, prefix)
264            );
265        }
266        let _ = writeln!(
267            out,
268            "{api} {tag}* {}({bt}* builder, {prefix}_error* out_err);",
269            b.build_symbol
270        );
271        let _ = writeln!(out, "{api} void {}({bt}* builder);", b.destroy_symbol);
272        out.push('\n');
273    }
274}
275
276/// Phase 1a: enum definitions for one module. Enums reference no other types,
277/// so they are emitted first across all modules.
278pub fn render_module_enum_defs(out: &mut String, module: &ModuleBinding) {
279    for e in &module.enums {
280        if e.is_rich() {
281            render_rich_enum_tag_decl(out, e);
282        } else {
283            render_enum_decl(out, e);
284        }
285    }
286}
287
288/// Phase 1b: opaque struct/builder/iterator forward typedefs for one module.
289/// Pointers to these are all the C ABI ever uses, so a forward typedef is
290/// sufficient and lets declarations in any module reference any struct.
291pub fn render_module_type_tags(out: &mut String, module: &ModuleBinding) {
292    // A rich (algebraic) enum is an opaque object, declared like a struct tag.
293    for e in &module.enums {
294        if e.is_rich() {
295            let t = &e.c_tag;
296            let _ = writeln!(out, "typedef struct {t} {t};");
297        }
298    }
299    for s in &module.structs {
300        render_struct_tags(out, s);
301    }
302    for f in &module.functions {
303        if let CallShape::Iterator(it) = &f.shape {
304            let t = &it.iter_tag;
305            let _ = writeln!(out, "typedef struct {t} {t};");
306        }
307    }
308}
309
310/// Phase 1c: callback / async-callback function-pointer typedefs for one
311/// module. These may reference enums (by value) and structs (by pointer), so
312/// they are emitted after every module's enums and type tags.
313pub fn render_module_callback_types(out: &mut String, module: &ModuleBinding, prefix: &str) {
314    for cb in &module.callbacks {
315        emit_doc(out, &cb.doc, "");
316        let _ = writeln!(
317            out,
318            "typedef void (*{})({});",
319            cb.c_fn_type,
320            params_str(&cb.abi_params, prefix)
321        );
322    }
323    for f in &module.functions {
324        if let CallShape::Async(a) = &f.shape {
325            let _ = writeln!(
326                out,
327                "typedef void (*{})({});",
328                a.callback_type,
329                params_str(&a.callback_params, prefix)
330            );
331        }
332    }
333}
334
335/// Phase 2: every function prototype for one module: struct create/destroy/
336/// getters and builders, listeners, then sync/async/iterator functions. All
337/// type tags and callback typedefs are assumed already emitted (phases 1a–1c).
338/// Caller controls the leading `// Module:` comment and any framing.
339pub fn render_module_fn_decls(out: &mut String, module: &ModuleBinding, prefix: &str) {
340    let api = export_macro(prefix);
341    let deprecated = deprecated_macro(prefix);
342    for e in &module.enums {
343        render_rich_enum_fn_decls(out, e, prefix);
344    }
345    for s in &module.structs {
346        render_struct_fn_decls(out, s, prefix);
347    }
348    for l in &module.listeners {
349        emit_doc(out, &l.doc, "");
350        let _ = writeln!(
351            out,
352            "{api} uint64_t {}({} callback, void* context);",
353            l.register_symbol, l.callback_c_fn_type
354        );
355        emit_doc(out, &l.doc, "");
356        let _ = writeln!(out, "{api} void {}(uint64_t id);", l.unregister_symbol);
357    }
358    for f in &module.functions {
359        emit_doc(out, &f.doc, "");
360        if let Some(msg) = &f.deprecated {
361            let _ = writeln!(out, "{deprecated}(\"{}\")", msg.replace('"', "\\\""));
362        }
363        match &f.shape {
364            CallShape::Iterator(it) => {
365                let t = &it.iter_tag;
366                fn_decl(out, &it.launch, prefix);
367                fn_decl(out, &it.next, prefix);
368                let _ = writeln!(out, "{api} void {}({t}* iter);", it.destroy_symbol);
369            }
370            CallShape::Async(a) => {
371                fn_decl(out, &a.launch, prefix);
372            }
373            CallShape::Sync(abi) => {
374                fn_decl(out, abi, prefix);
375            }
376        }
377    }
378}
379
380/// Render the complete C ABI declaration surface for `modules` in
381/// dependency-safe order: all enum definitions, then all opaque type tags, then
382/// all callback typedefs, then per-module function prototypes. Emitting every
383/// type tag before any function lets a parent module's function reference a
384/// child module's struct: cross-module forward references the previous
385/// per-module interleaving could not express.
386///
387/// The runtime decls (`handle_t`, `error`, `free_*`, cancel token) are *not*
388/// emitted here; callers render those first (the C generator inserts its map
389/// convention comment in between).
390pub fn render_decls(
391    out: &mut String,
392    modules: &[ModuleBinding],
393    prefix: &str,
394    module_comments: bool,
395) {
396    for m in modules {
397        render_module_enum_defs(out, m);
398    }
399    for m in modules {
400        render_module_type_tags(out, m);
401    }
402    for m in modules {
403        render_module_callback_types(out, m, prefix);
404    }
405    out.push('\n');
406    for m in modules {
407        if module_comments {
408            let _ = writeln!(out, "// Module: {}", m.path);
409        }
410        render_module_fn_decls(out, m, prefix);
411        out.push('\n');
412    }
413}