Skip to main content

weaveffi_model/
rust.rs

1//! Extract the IR of a `#[weaveffi::module]` tree from its Rust syntax.
2//!
3//! This is the `#[weaveffi::module]` proc-macro's reader: it lowers a module
4//! tree to its IR ([`extract_module`]), which the macro validates, builds
5//! the C ABI scaffolding from, and embeds in the library as metadata (see
6//! [`meta`](crate::meta)). The CLI never reads Rust source; it reads that
7//! metadata back out of the built library, so the API it generates bindings
8//! for is, by construction, the one the library was compiled with.
9//!
10//! The extraction reports only what syntax alone rules out (a raw pointer,
11//! a tuple variant, a `#[cfg]` on a member); every other rule is the
12//! validator's. Alongside the IR, [`extract_module`] returns a [`SourceMap`]
13//! recording where each declaration came from and the `#[cfg]` attributes on
14//! it, so the macro can point a validation error at the offending item and
15//! apply each item's `#[cfg]` to its generated code.
16//!
17//! # The annotation scheme
18//!
19//! A `#[weaveffi::module]` on an inline `mod` marks an exported namespace.
20//! Inside it:
21//!
22//! * `#[weaveffi::export]` on a `fn` exports a function. An `async fn` lowers to
23//!   an asynchronous symbol; a `fn -> Result<T, E>` is fallible (`throws` in
24//!   the IDL; the return type is `T` and errors are reported through the ABI's
25//!   `out_err`).
26//! * `#[weaveffi::interface]` on a `struct` declares an interface (opaque,
27//!   reference-counted object type). Its `impl` block's `pub fn`s become the
28//!   interface's members: an associated function returning `Self` (or
29//!   `Arc<Self>`) is a constructor, a `&self` or `self: Arc<Self>` function is
30//!   a method, and any other associated function is a static.
31//! * `#[weaveffi::record]` on a `struct` declares a by-value record.
32//! * `#[weaveffi::enumeration]` on a `#[repr(i32)]` `enum` declares a C-style
33//!   enum; an enum with data-carrying variants declares a rich enum.
34//! * `#[weaveffi::error]` on an enum declares the module's error domain.
35//! * `#[weaveffi::callback_interface]` on a `trait` declares a callback
36//!   interface: a method set the consumer implements. Producers accept one as
37//!   `Arc<dyn Trait>` (or `Option<Arc<dyn Trait>>`). Every method returns
38//!   `Result<T, weaveffi::ForeignError>`, which maps to a `T` return; a
39//!   method marked `#[weaveffi::throws]` throws.
40//! * `pub type Name = T;` in the tree is an alias: every use of `Name` is
41//!   extracted as `T`.
42//!
43//! The type mapping mirrors the IDL: `String` (or `&str`) is a string, `Vec<u8>`
44//! (or `&[u8]`) is a byte buffer, every integer primitive (including `u64`) is
45//! the matching scalar, `Vec<T>`, `Option<T>`, and `HashMap`/`BTreeMap` are the
46//! list, optional, and map types, `weaveffi::Iter<T>` is an `iter<T>` return,
47//! `Arc<T>` names the interface `T` (an object reference), `Arc<dyn Trait>`
48//! names the callback interface `Trait`, and any other named path is a record,
49//! enum, or interface resolved later.
50
51use std::collections::{BTreeMap, HashMap};
52
53use crate::ir::{
54    CallbackInterfaceDef, EnumDef, EnumVariant, ErrorCode, ErrorDomain, Function, InterfaceDef,
55    Module, Param, StructDef, StructField, TypeRef,
56};
57use crate::ty::Prim;
58use proc_macro2::Span;
59use syn::spanned::Spanned;
60
61/// Where each extracted declaration came from, keyed by its declaration
62/// path: module segments from the tree's root, then the declaration name,
63/// then a member name (an interface's constructor, method, or static; a
64/// callback interface's method; an enum's variant; an error domain's code),
65/// then a parameter or field name. These are the paths validation reports
66/// its errors under.
67///
68/// A parameter's or field's path extended with [`TYPE`](Self::TYPE) locates
69/// its written type, and a callable's path extended with
70/// [`RETURN`](Self::RETURN) its written return type.
71#[derive(Clone, Default)]
72pub struct SourceMap {
73    spans: BTreeMap<Vec<String>, Vec<Span>>,
74    cfgs: BTreeMap<Vec<String>, Vec<syn::Attribute>>,
75}
76
77impl std::fmt::Debug for SourceMap {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.debug_struct("SourceMap")
80            .field("paths", &self.spans.keys().collect::<Vec<_>>())
81            .finish_non_exhaustive()
82    }
83}
84
85impl SourceMap {
86    /// The key segment that, appended to a parameter's or field's path,
87    /// names its written type.
88    pub const TYPE: &'static str = "#type";
89    /// The key segment that, appended to a callable's path, names its
90    /// written return type.
91    pub const RETURN: &'static str = "#return";
92
93    fn record(&mut self, path: Vec<String>, span: Span) {
94        self.spans.entry(path).or_default().push(span);
95    }
96
97    fn record_cfg(&mut self, path: Vec<String>, attrs: &[syn::Attribute]) {
98        let cfg: Vec<syn::Attribute> = attrs.iter().filter(|a| is_cfg(a)).cloned().collect();
99        if !cfg.is_empty() {
100            self.cfgs.entry(path).or_default().extend(cfg);
101        }
102    }
103
104    /// Every span recorded for `path`, in source order (a duplicated name
105    /// has several).
106    #[must_use]
107    pub fn spans(&self, path: &[String]) -> &[Span] {
108        self.spans.get(path).map_or(&[], Vec::as_slice)
109    }
110
111    /// The `#[cfg]` attributes written on the declaration at `path` itself
112    /// (for an interface member, on its `impl` block), not including its
113    /// enclosing modules' or interface's.
114    #[must_use]
115    pub fn cfg(&self, path: &[String]) -> &[syn::Attribute] {
116        self.cfgs.get(path).map_or(&[], Vec::as_slice)
117    }
118}
119
120/// Whether `attr` is a `#[cfg(...)]`.
121fn is_cfg(attr: &syn::Attribute) -> bool {
122    attr.path().is_ident("cfg")
123}
124
125/// Reject a `#[cfg]` on a member of an exported item.
126fn no_member_cfg(attrs: &[syn::Attribute], what: &str, instead: &str) -> syn::Result<()> {
127    match attrs.iter().find(|a| is_cfg(a)) {
128        Some(attr) => Err(syn::Error::new(
129            attr.span(),
130            format!(
131                "weaveffi: `#[cfg]` on {what} isn't supported, because the generated bindings \
132                 can't follow it; {instead}"
133            ),
134        )),
135        None => Ok(()),
136    }
137}
138
139/// The type aliases declared in a module tree (`pub type Id = u64;`), by
140/// name. Generic aliases are left alone.
141type Aliases = HashMap<String, syn::Type>;
142
143fn collect_aliases(item_mod: &syn::ItemMod, out: &mut Aliases) {
144    let Some((_, items)) = &item_mod.content else {
145        return;
146    };
147    for item in items {
148        match item {
149            syn::Item::Type(t) if t.generics.params.is_empty() => {
150                out.insert(t.ident.to_string(), (*t.ty).clone());
151            }
152            syn::Item::Mod(m) if has_marker(&m.attrs, "module") => collect_aliases(m, out),
153            _ => {}
154        }
155    }
156}
157
158/// The extraction context: the tree's aliases and the source map being
159/// built.
160struct Extractor<'a> {
161    aliases: &'a Aliases,
162    map: SourceMap,
163}
164
165/// Match a WeaveFFI marker attribute by its final path segment.
166///
167/// Accepts both the namespaced form (`#[weaveffi::record]`) and the bare form
168/// (`#[record]`), so the macro's stripped re-emit and a hand-written attribute
169/// resolve identically.
170fn is_marker(attr: &syn::Attribute, name: &str) -> bool {
171    attr.path().segments.last().is_some_and(|s| s.ident == name)
172}
173
174/// Whether any attribute in `attrs` is the WeaveFFI marker `name`.
175pub fn has_marker(attrs: &[syn::Attribute], name: &str) -> bool {
176    attrs.iter().any(|a| is_marker(a, name))
177}
178
179fn has_repr_i32(attrs: &[syn::Attribute]) -> bool {
180    attrs.iter().any(|a| {
181        a.path().is_ident("repr") && a.parse_args::<syn::Ident>().is_ok_and(|id| id == "i32")
182    })
183}
184
185/// Parse `#[deprecated(since = "...", note = "...")]` into `(since, note)`.
186fn parse_deprecated(attrs: &[syn::Attribute]) -> (Option<String>, Option<String>) {
187    let Some(attr) = attrs.iter().find(|a| a.path().is_ident("deprecated")) else {
188        return (None, None);
189    };
190    let mut since = None;
191    let mut note = None;
192    if matches!(attr.meta, syn::Meta::Path(_)) {
193        return (None, Some("deprecated".to_string()));
194    }
195    let _ = attr.parse_nested_meta(|meta| {
196        let Some(ident) = meta.path.get_ident() else {
197            return Ok(());
198        };
199        let value = meta.value()?;
200        let lit: syn::LitStr = value.parse()?;
201        match ident.to_string().as_str() {
202            "since" => since = Some(lit.value()),
203            "note" => note = Some(lit.value()),
204            _ => {}
205        }
206        Ok(())
207    });
208    if note.is_none() && since.is_none() {
209        note = Some("deprecated".to_string());
210    }
211    (since, note)
212}
213
214/// The doc comment of an item, one line per `///` line with the leading
215/// space removed, and with rustdoc intra-doc links unwrapped to the inline
216/// code they display (see [`unwrap_doc_links`]), since no binding resolves
217/// Rust paths.
218fn extract_doc(attrs: &[syn::Attribute]) -> Option<String> {
219    let lines: Vec<String> = attrs
220        .iter()
221        .filter_map(|attr| {
222            let syn::Meta::NameValue(nv) = &attr.meta else {
223                return None;
224            };
225            if !nv.path.is_ident("doc") {
226                return None;
227            }
228            let syn::Expr::Lit(syn::ExprLit {
229                lit: syn::Lit::Str(s),
230                ..
231            }) = &nv.value
232            else {
233                return None;
234            };
235            let val = s.value();
236            Some(match val.strip_prefix(' ') {
237                Some(stripped) => stripped.to_string(),
238                None => val,
239            })
240        })
241        .collect();
242    if lines.is_empty() {
243        return None;
244    }
245    let mut fenced = false;
246    let lines: Vec<String> = lines
247        .into_iter()
248        .map(|line| {
249            if line.trim_start().starts_with("```") {
250                fenced = !fenced;
251                line
252            } else if fenced {
253                line
254            } else {
255                unwrap_doc_links(&line)
256            }
257        })
258        .collect();
259    Some(lines.join("\n"))
260}
261
262/// Unwrap every rustdoc intra-doc link written as inline code in brackets
263/// (`` [`Store`] ``, `` [`KvError::KeyNotFound`] ``) to the inline code
264/// alone. A bracketed span followed by `(`, `[`, or `:` is an ordinary
265/// Markdown link or link definition, and stays as written.
266fn unwrap_doc_links(line: &str) -> String {
267    let mut out = String::with_capacity(line.len());
268    let mut rest = line;
269    while let Some(start) = rest.find("[`") {
270        let after = &rest[start + 2..];
271        let Some(end) = after.find('`') else {
272            break;
273        };
274        let code = &after[..end];
275        let tail = &after[end + 1..];
276        let link = tail.starts_with(']')
277            && !code.is_empty()
278            && !matches!(tail[1..].chars().next(), Some('(' | '[' | ':'));
279        if link {
280            out.push_str(&rest[..start]);
281            out.push('`');
282            out.push_str(code);
283            out.push('`');
284            rest = &tail[1..];
285        } else {
286            out.push_str(&rest[..start + 2]);
287            rest = after;
288        }
289    }
290    out.push_str(rest);
291    out
292}
293
294fn is_ident(ty: &syn::Type, name: &str) -> bool {
295    matches!(ty, syn::Type::Path(p) if p.path.is_ident(name))
296}
297
298fn single_generic_arg(seg: &syn::PathSegment) -> syn::Result<&syn::Type> {
299    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
300        return Err(syn::Error::new(seg.span(), "expected generic arguments"));
301    };
302    if args.args.len() != 1 {
303        return Err(syn::Error::new(
304            seg.span(),
305            "expected exactly 1 generic argument",
306        ));
307    }
308    let syn::GenericArgument::Type(ty) = &args.args[0] else {
309        return Err(syn::Error::new(seg.span(), "expected a type argument"));
310    };
311    Ok(ty)
312}
313
314fn two_generic_args(seg: &syn::PathSegment) -> syn::Result<(&syn::Type, &syn::Type)> {
315    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
316        return Err(syn::Error::new(seg.span(), "expected generic arguments"));
317    };
318    if args.args.len() != 2 {
319        return Err(syn::Error::new(
320            seg.span(),
321            "expected exactly 2 generic arguments",
322        ));
323    }
324    let syn::GenericArgument::Type(k) = &args.args[0] else {
325        return Err(syn::Error::new(seg.span(), "expected a key type argument"));
326    };
327    let syn::GenericArgument::Type(v) = &args.args[1] else {
328        return Err(syn::Error::new(
329            seg.span(),
330            "expected a value type argument",
331        ));
332    };
333    Ok((k, v))
334}
335
336fn type_path_ident(ty: &syn::Type) -> Option<String> {
337    let syn::Type::Path(p) = ty else { return None };
338    p.path.segments.last().map(|s| s.ident.to_string())
339}
340
341/// The bare name of the single trait a `dyn Trait` object names, ignoring
342/// auto-trait and lifetime bounds (`dyn Listener + Send + Sync + 'static`).
343fn trait_object_name(ty: &syn::Type) -> Option<String> {
344    let syn::Type::TraitObject(obj) = ty else {
345        return None;
346    };
347    let mut names = obj.bounds.iter().filter_map(|b| match b {
348        syn::TypeParamBound::Trait(t) => {
349            let name = t.path.segments.last()?.ident.to_string();
350            (!matches!(name.as_str(), "Send" | "Sync" | "Unpin")).then_some(name)
351        }
352        _ => None,
353    });
354    let first = names.next()?;
355    names.next().is_none().then_some(first)
356}
357
358/// Peel `Arc<T>` to its `T`, returning any other type unchanged.
359///
360/// `Arc` is how a producer spells an interface object it holds or returns
361/// (`Arc<Store>`, `Arc<Self>`) and a callback interface it accepts
362/// (`Arc<dyn Listener>`); the IR names the pointee type in both cases.
363pub fn peel_arc(ty: &syn::Type) -> &syn::Type {
364    if let syn::Type::Path(p) = ty {
365        if let Some(seg) = p.path.segments.last() {
366            if seg.ident == "Arc" {
367                if let Ok(inner) = single_generic_arg(seg) {
368                    return inner;
369                }
370            }
371        }
372    }
373    ty
374}
375
376/// Whether a parameter's type is the reserved `weaveffi::CancelToken`.
377///
378/// A `#[weaveffi::cancellable]` `async fn` accepts a `CancelToken` as its final
379/// parameter, but the token is part of the async *calling convention* (the
380/// launcher's `cancel_token` slot), not the function's logical signature, so it
381/// is filtered out of the extracted parameter list. Matching on the final path
382/// segment accepts both the bare and `weaveffi::`-qualified spellings.
383fn is_cancel_token(ty: &syn::Type) -> bool {
384    type_path_ident(ty).as_deref() == Some("CancelToken")
385}
386
387/// Map a Rust [`syn::Type`] onto the WeaveFFI [`TypeRef`] it represents,
388/// substituting the tree's `aliases`.
389///
390/// This is the canonical mapping every caller shares. Notable conventions:
391///
392/// * `String` and `&str` are both the `string` type; `Vec<u8>` and `&[u8]`
393///   are both `bytes`. A reference is a producer-side calling convention (the
394///   thunk lifts an owned value and lends it), not an IDL distinction.
395/// * every integer primitive maps to its matching scalar (`u64` included).
396/// * `Vec<T>` and `&[T]` are lists, `Option<T>` is an optional, and
397///   `HashMap`/`BTreeMap` are maps.
398/// * `weaveffi::Iter<T>` is an `iter<T>` return.
399/// * `Arc<T>` and `&T` name the interface `T`; `Arc<dyn Trait>` names the
400///   callback interface `Trait`. Any other named path is a record, enum,
401///   interface, or callback interface reference resolved by the validator.
402///
403/// # Errors
404///
405/// Returns a spanned error for type syntax WeaveFFI cannot express across the
406/// FFI boundary (raw pointers, `Box<dyn Trait>`, tuples, a generic with the
407/// wrong arity, and so on).
408fn type_ref_from_syn(ty: &syn::Type, aliases: &Aliases) -> syn::Result<TypeRef> {
409    type_ref_at(ty, aliases, 0)
410}
411
412fn type_ref_at(ty: &syn::Type, aliases: &Aliases, depth: usize) -> syn::Result<TypeRef> {
413    let recurse = |t: &syn::Type| type_ref_at(t, aliases, depth);
414    match ty {
415        syn::Type::Reference(r) => {
416            if let syn::Type::Path(p) = r.elem.as_ref() {
417                if p.path.is_ident("str") {
418                    return Ok(TypeRef::Prim(Prim::String));
419                }
420            }
421            if let syn::Type::Slice(slice) = r.elem.as_ref() {
422                if is_ident(&slice.elem, "u8") {
423                    return Ok(TypeRef::Prim(Prim::Bytes));
424                }
425                return Ok(TypeRef::List(Box::new(recurse(&slice.elem)?)));
426            }
427            recurse(&r.elem)
428        }
429        syn::Type::Ptr(_) => Err(syn::Error::new(
430            ty.span(),
431            "weaveffi: raw pointers cannot cross the FFI boundary; declare the pointee as a \
432             #[weaveffi::interface] and pass it as `&T` or `Arc<T>`",
433        )),
434        syn::Type::TraitObject(_) => {
435            let name = trait_object_name(ty).ok_or_else(|| {
436                syn::Error::new(
437                    ty.span(),
438                    "weaveffi: a trait object must name exactly one #[weaveffi::callback_interface] \
439                     trait (plus optional `Send`/`Sync` bounds)",
440                )
441            })?;
442            Ok(TypeRef::Named(name))
443        }
444        syn::Type::Path(type_path) => {
445            let seg = type_path
446                .path
447                .segments
448                .last()
449                .ok_or_else(|| syn::Error::new(ty.span(), "empty type path"))?;
450            let ident = seg.ident.to_string();
451            // A Rust scalar (`i32`, `bool`, ...) is the primitive of the same
452            // name; `string` and `bytes` are IDL spellings, not Rust types.
453            if let Some(p) =
454                Prim::from_name(&ident).filter(|p| !matches!(p, Prim::String | Prim::Bytes))
455            {
456                return Ok(TypeRef::Prim(p));
457            }
458            match ident.as_str() {
459                "String" => Ok(TypeRef::Prim(Prim::String)),
460                // `Arc<T>` is a reference to the interface object `T`, and
461                // `Arc<dyn Trait>` a consumer-implemented callback interface;
462                // the IR names the pointee in both cases.
463                "Arc" => recurse(single_generic_arg(seg)?),
464                "Box" | "Rc" => Err(syn::Error::new(
465                    ty.span(),
466                    format!(
467                        "weaveffi: `{ident}` cannot cross the FFI boundary; objects and callback \
468                         interfaces are shared, so spell them as `Arc<T>` / `Arc<dyn Trait>`"
469                    ),
470                )),
471                "Vec" => {
472                    let inner = single_generic_arg(seg)?;
473                    if is_ident(inner, "u8") {
474                        return Ok(TypeRef::Prim(Prim::Bytes));
475                    }
476                    Ok(TypeRef::List(Box::new(recurse(inner)?)))
477                }
478                "Option" => {
479                    let inner = single_generic_arg(seg)?;
480                    Ok(TypeRef::Optional(Box::new(recurse(inner)?)))
481                }
482                // `weaveffi::Iter<T>` is the producer spelling of an `iter<T>`
483                // return: a lazily-pulled stream rather than a materialized list.
484                "Iter" => {
485                    let inner = single_generic_arg(seg)?;
486                    Ok(TypeRef::Iterator(Box::new(recurse(inner)?)))
487                }
488                "HashMap" | "BTreeMap" => {
489                    let (k, v) = two_generic_args(seg)?;
490                    Ok(TypeRef::Map(Box::new(recurse(k)?), Box::new(recurse(v)?)))
491                }
492                other => match aliases.get(other) {
493                    Some(target) if matches!(seg.arguments, syn::PathArguments::None) => {
494                        if depth > 16 {
495                            return Err(syn::Error::new(
496                                ty.span(),
497                                format!("weaveffi: type alias `{other}` refers to itself"),
498                            ));
499                        }
500                        type_ref_at(target, aliases, depth + 1)
501                    }
502                    _ => Ok(TypeRef::Named(other.to_string())),
503                },
504            }
505        }
506        _ => Err(syn::Error::new(ty.span(), "unsupported type syntax")),
507    }
508}
509
510/// Peel `Result<T, E>` to its `T`, returning any other type unchanged. A
511/// `Result` return marks the function `throws` in the IDL; the error type
512/// itself carries no extra IR shape (it reports through the ABI's `out_err`).
513pub fn peel_result(ty: &syn::Type) -> &syn::Type {
514    if let syn::Type::Path(p) = ty {
515        if let Some(seg) = p.path.segments.last() {
516            if seg.ident == "Result" {
517                if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
518                    if let Some(syn::GenericArgument::Type(ok)) = args.args.first() {
519                        return ok;
520                    }
521                }
522            }
523        }
524    }
525    ty
526}
527
528/// Whether a function's return type is a `Result<_, _>`, which marks the
529/// function as `throws` in the IDL.
530pub fn output_is_result(output: &syn::ReturnType) -> bool {
531    let syn::ReturnType::Type(_, ty) = output else {
532        return false;
533    };
534    matches!(ty.as_ref(), syn::Type::Path(p)
535        if p.path.segments.last().is_some_and(|s| s.ident == "Result"))
536}
537
538fn is_unit(ty: &syn::Type) -> bool {
539    matches!(ty, syn::Type::Tuple(t) if t.elems.is_empty())
540}
541
542fn parse_discriminant(expr: &syn::Expr) -> syn::Result<i32> {
543    match expr {
544        syn::Expr::Lit(lit) => {
545            let syn::Lit::Int(int_lit) = &lit.lit else {
546                return Err(syn::Error::new(
547                    expr.span(),
548                    "expected an integer literal discriminant",
549                ));
550            };
551            int_lit.base10_parse::<i32>()
552        }
553        syn::Expr::Unary(unary) if matches!(unary.op, syn::UnOp::Neg(_)) => {
554            Ok(-parse_discriminant(&unary.expr)?)
555        }
556        _ => Err(syn::Error::new(
557            expr.span(),
558            "unsupported discriminant expression",
559        )),
560    }
561}
562
563/// `path` extended with `names`.
564fn at(path: &[String], names: &[&str]) -> Vec<String> {
565    let mut out = path.to_vec();
566    out.extend(names.iter().map(|n| (*n).to_string()));
567    out
568}
569
570/// Whether a return type is `Result<T, ForeignError>`, the return type every
571/// callback-interface method must have.
572pub fn returns_foreign_result(output: &syn::ReturnType) -> bool {
573    let syn::ReturnType::Type(_, ty) = output else {
574        return false;
575    };
576    let syn::Type::Path(p) = ty.as_ref() else {
577        return false;
578    };
579    let Some(seg) = p.path.segments.last() else {
580        return false;
581    };
582    if seg.ident != "Result" {
583        return false;
584    }
585    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
586        return false;
587    };
588    match args.args.iter().nth(1) {
589        Some(syn::GenericArgument::Type(err)) => {
590            type_path_ident(err).as_deref() == Some("ForeignError")
591        }
592        _ => false,
593    }
594}
595
596/// Whether a method receiver is one WeaveFFI can lift: `&self` (a borrow for
597/// the call) or `self: Arc<Self>` (a retained reference).
598///
599/// `&mut self` and by-value `self` are rejected: the object is shared across
600/// the FFI boundary (and may be in use on other threads), so mutable state
601/// needs interior mutability.
602pub fn receiver_is_supported(recv: &syn::Receiver) -> bool {
603    if recv.mutability.is_some() {
604        return false;
605    }
606    if recv.reference.is_some() {
607        return true;
608    }
609    recv.colon_token.is_some() && type_path_ident(&recv.ty).as_deref() == Some("Arc")
610}
611
612/// Whether the (peeled) return type names the interface itself (`Self`,
613/// `Arc<Self>`, or the interface's own name), which classifies a
614/// synchronous associated function as a constructor.
615fn returns_self(output: &syn::ReturnType, iface: &str) -> bool {
616    let syn::ReturnType::Type(_, ty) = output else {
617        return false;
618    };
619    match type_path_ident(peel_arc(peel_result(ty))) {
620        Some(name) => name == "Self" || name == iface,
621        None => false,
622    }
623}
624
625impl Extractor<'_> {
626    fn ty(&self, ty: &syn::Type) -> syn::Result<TypeRef> {
627        type_ref_from_syn(ty, self.aliases)
628    }
629
630    /// Map a function's return type to its IDL return [`TypeRef`], peeling
631    /// `Result<T, E>` and treating `()` (and `Result<(), E>`) as no return.
632    fn return_type(&self, output: &syn::ReturnType) -> syn::Result<Option<TypeRef>> {
633        match output {
634            syn::ReturnType::Default => Ok(None),
635            syn::ReturnType::Type(_, ty) => {
636                let inner = peel_result(ty);
637                if is_unit(inner) {
638                    Ok(None)
639                } else {
640                    Ok(Some(self.ty(inner)?))
641                }
642            }
643        }
644    }
645
646    fn params(
647        &mut self,
648        path: &[String],
649        inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
650    ) -> syn::Result<Vec<Param>> {
651        let mut out = Vec::new();
652        for pt in inputs.iter().filter_map(|arg| match arg {
653            syn::FnArg::Typed(pt) => Some(pt),
654            syn::FnArg::Receiver(_) => None,
655        }) {
656            // The cancellation token is part of the async calling
657            // convention, not a logical parameter, so it never appears in
658            // the IDL.
659            if is_cancel_token(&pt.ty) {
660                continue;
661            }
662            let name = match pt.pat.as_ref() {
663                syn::Pat::Ident(id) => id.ident.to_string(),
664                _ => return Err(syn::Error::new(pt.span(), "unsupported parameter pattern")),
665            };
666            if matches!(pt.ty.as_ref(), syn::Type::Reference(r) if r.mutability.is_some()) {
667                return Err(syn::Error::new(
668                    pt.ty.span(),
669                    "weaveffi: `&mut` parameters cannot cross the FFI boundary; take the value \
670                     by `&T` or by value and return the updated result",
671                ));
672            }
673            self.map.record(at(path, &[&name]), pt.pat.span());
674            self.map
675                .record(at(path, &[&name, SourceMap::TYPE]), pt.ty.span());
676            out.push(Param {
677                ty: self.ty(&pt.ty)?,
678                name,
679                doc: extract_doc(&pt.attrs),
680            });
681        }
682        Ok(out)
683    }
684
685    /// Map one `fn` signature (free function, interface member, or callback
686    /// method) declared at `path` to the IR [`Function`]. `returns` is
687    /// supplied by the caller because constructors, `Self` returns, and
688    /// callback methods need their own handling.
689    fn function(
690        &mut self,
691        path: &[String],
692        sig: &syn::Signature,
693        attrs: &[syn::Attribute],
694        returns: Option<TypeRef>,
695    ) -> syn::Result<Function> {
696        let ret_span = match &sig.output {
697            syn::ReturnType::Type(_, ty) => ty.span(),
698            syn::ReturnType::Default => sig.ident.span(),
699        };
700        self.map.record(at(path, &[SourceMap::RETURN]), ret_span);
701        Ok(Function {
702            name: sig.ident.to_string(),
703            params: self.params(path, &sig.inputs)?,
704            returns,
705            doc: extract_doc(attrs),
706            throws: output_is_result(&sig.output),
707            r#async: sig.asyncness.is_some(),
708            cancellable: has_marker(attrs, "cancellable"),
709            deprecated: parse_deprecated(attrs).1,
710        })
711    }
712
713    /// Extract one interface member from an `impl` block function. A `Self`
714    /// (or interface-named) return on a constructor is dropped: the IR
715    /// leaves a constructor's `return` empty because the instance is
716    /// implicit.
717    fn member(
718        &mut self,
719        path: &[String],
720        item: &syn::ImplItemFn,
721        iface: &str,
722        is_ctor: bool,
723    ) -> syn::Result<Function> {
724        let returns = if is_ctor {
725            None
726        } else {
727            match self.return_type(&item.sig.output)? {
728                // `fn hand(&self) -> Arc<Self>` style returns name the
729                // interface.
730                Some(TypeRef::Named(name)) if name == "Self" => {
731                    Some(TypeRef::Named(iface.to_string()))
732                }
733                other => other,
734            }
735        };
736        let member = at(path, &[&item.sig.ident.to_string()]);
737        self.map.record(member.clone(), item.sig.ident.span());
738        self.function(&member, &item.sig, &item.attrs, returns)
739    }
740
741    /// Classify and extract every `pub fn` of an interface's `impl` block
742    /// into the interface's constructors, methods, and statics:
743    ///
744    /// * a function with a `&self` or `self: Arc<Self>` receiver is a
745    ///   **method**;
746    /// * a synchronous associated function returning `Self` or `Arc<Self>`
747    ///   (or the interface type, optionally inside `Result`) is a
748    ///   **constructor**;
749    /// * any other associated function is a **static**, including an
750    ///   `async fn` returning the interface (constructors are synchronous,
751    ///   so that's an async factory).
752    ///
753    /// Non-`pub` items are private helpers and stay unexported. The block's
754    /// `#[cfg]` is recorded on each member it declares.
755    fn members(
756        &mut self,
757        path: &[String],
758        item_impl: &syn::ItemImpl,
759        iface: &mut InterfaceDef,
760    ) -> syn::Result<()> {
761        for impl_item in &item_impl.items {
762            let syn::ImplItem::Fn(f) = impl_item else {
763                continue;
764            };
765            if !matches!(f.vis, syn::Visibility::Public(_)) {
766                continue;
767            }
768            no_member_cfg(
769                &f.attrs,
770                "an interface member",
771                "move the member into its own `impl` block and put the `#[cfg]` on that block",
772            )?;
773            let member = at(path, &[&f.sig.ident.to_string()]);
774            self.map.record_cfg(member, &item_impl.attrs);
775            match f.sig.receiver() {
776                Some(recv) => {
777                    if !receiver_is_supported(recv) {
778                        return Err(syn::Error::new(
779                            recv.span(),
780                            "weaveffi: interface methods must take `&self` or `self: Arc<Self>`; \
781                             use interior mutability (Mutex, RwLock, atomics) for mutable state, \
782                             because the object is shared across the FFI boundary",
783                        ));
784                    }
785                    let m = self.member(path, f, &iface.name, false)?;
786                    iface.methods.push(m);
787                }
788                None if f.sig.asyncness.is_none() && returns_self(&f.sig.output, &iface.name) => {
789                    let m = self.member(path, f, &iface.name, true)?;
790                    iface.constructors.push(m);
791                }
792                None => {
793                    let m = self.member(path, f, &iface.name, false)?;
794                    iface.statics.push(m);
795                }
796            }
797        }
798        Ok(())
799    }
800
801    /// Extract a `#[weaveffi::callback_interface]` trait into a
802    /// [`CallbackInterfaceDef`].
803    ///
804    /// Every trait method is a callback method the consumer implements. A
805    /// method takes `&self` and returns `Result<T, weaveffi::ForeignError>`,
806    /// which maps to a `T` return; `#[weaveffi::throws]` marks it throwing.
807    fn callback_interface(
808        &mut self,
809        path: &[String],
810        item: &syn::ItemTrait,
811    ) -> syn::Result<CallbackInterfaceDef> {
812        let mut methods = Vec::new();
813        for trait_item in &item.items {
814            let syn::TraitItem::Fn(f) = trait_item else {
815                continue;
816            };
817            no_member_cfg(
818                &f.attrs,
819                "a callback interface method",
820                "put the `#[cfg]` on the whole trait",
821            )?;
822            match f.sig.receiver() {
823                Some(recv) if recv.reference.is_some() && recv.mutability.is_none() => {}
824                Some(recv) => {
825                    return Err(syn::Error::new(
826                        recv.span(),
827                        "weaveffi: callback interface methods must take `&self`",
828                    ));
829                }
830                None => {
831                    return Err(syn::Error::new(
832                        f.sig.span(),
833                        "weaveffi: callback interface methods must take `&self` (associated \
834                         functions can't be implemented by the consumer)",
835                    ));
836                }
837            }
838            if !returns_foreign_result(&f.sig.output) {
839                let span = match &f.sig.output {
840                    syn::ReturnType::Type(_, ty) => ty.span(),
841                    syn::ReturnType::Default => f.sig.ident.span(),
842                };
843                return Err(syn::Error::new(
844                    span,
845                    "weaveffi: a callback interface method must return \
846                     `Result<T, weaveffi::ForeignError>` (or `Result<(), weaveffi::ForeignError>` \
847                     for no value), because the consumer's implementation can fail and the \
848                     `Err` is how its failure reaches you",
849                ));
850            }
851            let returns = self.return_type(&f.sig.output)?;
852            let method_path = at(path, &[&f.sig.ident.to_string()]);
853            self.map.record(method_path.clone(), f.sig.ident.span());
854            let mut method = self.function(&method_path, &f.sig, &f.attrs, returns)?;
855            method.throws = has_marker(&f.attrs, "throws");
856            methods.push(method);
857        }
858        Ok(CallbackInterfaceDef {
859            name: item.ident.to_string(),
860            doc: extract_doc(&item.attrs),
861            deprecated: parse_deprecated(&item.attrs).1,
862            methods,
863        })
864    }
865
866    /// The named fields of a record, a rich enum variant, or an error code,
867    /// declared under `path`.
868    fn fields(
869        &mut self,
870        path: &[String],
871        fields: &syn::FieldsNamed,
872        what: &str,
873    ) -> syn::Result<Vec<StructField>> {
874        let mut out = Vec::new();
875        for f in &fields.named {
876            no_member_cfg(&f.attrs, what, "put the `#[cfg]` on the whole type")?;
877            let name = f
878                .ident
879                .as_ref()
880                .ok_or_else(|| syn::Error::new(f.span(), "unnamed field"))?
881                .to_string();
882            self.map.record(at(path, &[&name]), f.span());
883            self.map
884                .record(at(path, &[&name, SourceMap::TYPE]), f.ty.span());
885            out.push(StructField {
886                ty: self.ty(&f.ty)?,
887                name,
888                doc: extract_doc(&f.attrs),
889            });
890        }
891        Ok(out)
892    }
893
894    fn record(&mut self, path: &[String], item: &syn::ItemStruct) -> syn::Result<StructDef> {
895        let syn::Fields::Named(named) = &item.fields else {
896            return Err(syn::Error::new(
897                item.span(),
898                "only named fields are supported for #[weaveffi::record]",
899            ));
900        };
901        Ok(StructDef {
902            name: item.ident.to_string(),
903            doc: extract_doc(&item.attrs),
904            deprecated: parse_deprecated(&item.attrs).1,
905            fields: self.fields(path, named, "a record field")?,
906        })
907    }
908
909    fn enumeration(&mut self, path: &[String], item: &syn::ItemEnum) -> syn::Result<EnumDef> {
910        let name = item.ident.to_string();
911
912        // A *rich* (algebraic) enum has at least one variant carrying data.
913        // Rust forbids explicit discriminants on such an enum, so its tags
914        // are the declaration-order positions (0, 1, 2, ...), exactly what
915        // the IDL records. A *C-style* enum (every variant fieldless) keeps
916        // the stricter contract: it must be `#[repr(i32)]` with an explicit
917        // discriminant on each variant.
918        let is_rich = item
919            .variants
920            .iter()
921            .any(|v| !matches!(v.fields, syn::Fields::Unit));
922
923        if !is_rich && !has_repr_i32(&item.attrs) {
924            return Err(syn::Error::new(
925                item.span(),
926                format!(
927                    "enum `{}` must have #[repr(i32)] to be a #[weaveffi::enumeration]",
928                    item.ident
929                ),
930            ));
931        }
932
933        let mut next_value: i32 = 0;
934        let mut variants = Vec::new();
935        for v in &item.variants {
936            no_member_cfg(
937                &v.attrs,
938                "an enum variant",
939                "put the `#[cfg]` on the whole enum",
940            )?;
941            let value = match v.discriminant.as_ref() {
942                Some((_, expr)) => parse_discriminant(expr)?,
943                None if is_rich => next_value,
944                None => {
945                    return Err(syn::Error::new(
946                        v.span(),
947                        format!(
948                            "enum `{name}` variant `{}` must have an explicit discriminant",
949                            v.ident
950                        ),
951                    ))
952                }
953            };
954            next_value = value.wrapping_add(1);
955            let variant = v.ident.to_string();
956            let vpath = at(path, &[&variant]);
957            self.map.record(vpath.clone(), v.ident.span());
958            let fields = match &v.fields {
959                syn::Fields::Unit => vec![],
960                syn::Fields::Named(named) => self.fields(&vpath, named, "an enum variant field")?,
961                syn::Fields::Unnamed(_) => {
962                    return Err(syn::Error::new(
963                        v.span(),
964                        format!(
965                            "enum `{name}` variant `{variant}`: tuple-style variants are not \
966                             supported; use named fields"
967                        ),
968                    ))
969                }
970            };
971            variants.push(EnumVariant {
972                name: variant,
973                value,
974                doc: extract_doc(&v.attrs),
975                fields,
976            });
977        }
978        Ok(EnumDef {
979            name,
980            doc: extract_doc(&item.attrs),
981            deprecated: parse_deprecated(&item.attrs).1,
982            variants,
983        })
984    }
985
986    /// Extract a `#[weaveffi::error]` enum into the module's
987    /// [`ErrorDomain`].
988    ///
989    /// Every variant needs an explicit integer discriminant: the code's
990    /// stable ABI value. A variant may be a unit variant or carry named
991    /// fields, which become the code's structured payload (serialized in the
992    /// value-buffer format alongside the `(code, message)` pair). Note that
993    /// Rust requires a primitive representation such as `#[repr(i32)]` on an
994    /// enum that mixes explicit discriminants with data-carrying variants. A
995    /// variant's doc comment becomes the code's default message (falling
996    /// back to the variant name). The enum's name is the domain name, and
997    /// the macro generates matching `ErrorReport` and `ErrorDomain`
998    /// implementations.
999    fn error_domain(&mut self, path: &[String], item: &syn::ItemEnum) -> syn::Result<ErrorDomain> {
1000        let mut codes = Vec::new();
1001        for v in &item.variants {
1002            no_member_cfg(
1003                &v.attrs,
1004                "an error code",
1005                "put the `#[cfg]` on the whole error enum",
1006            )?;
1007            let code = v.ident.to_string();
1008            let cpath = at(path, &[&code]);
1009            self.map.record(cpath.clone(), v.ident.span());
1010            let fields = match &v.fields {
1011                syn::Fields::Unit => vec![],
1012                syn::Fields::Named(named) => self.fields(&cpath, named, "an error code field")?,
1013                syn::Fields::Unnamed(_) => {
1014                    return Err(syn::Error::new(
1015                        v.span(),
1016                        "weaveffi: #[weaveffi::error] payload variants must use named \
1017                         fields (tuple-style variants are not supported)",
1018                    ));
1019                }
1020            };
1021            let Some((_, expr)) = v.discriminant.as_ref() else {
1022                return Err(syn::Error::new(
1023                    v.span(),
1024                    format!(
1025                        "weaveffi: error variant `{code}` must have an explicit discriminant \
1026                         (its stable ABI error code)"
1027                    ),
1028                ));
1029            };
1030            let doc = extract_doc(&v.attrs);
1031            let message = doc
1032                .as_deref()
1033                .and_then(|d| d.lines().next())
1034                .unwrap_or(&code)
1035                .to_string();
1036            codes.push(ErrorCode {
1037                code: parse_discriminant(expr)?,
1038                name: code,
1039                message,
1040                doc,
1041                fields,
1042            });
1043        }
1044        Ok(ErrorDomain {
1045            name: item.ident.to_string(),
1046            codes,
1047        })
1048    }
1049
1050    /// Extract one `#[weaveffi::module]`, whose parent path is `parent`.
1051    fn module(&mut self, parent: &[String], item_mod: &syn::ItemMod) -> syn::Result<Module> {
1052        let name = item_mod.ident.to_string();
1053        let path = at(parent, &[&name]);
1054        self.map.record(path.clone(), item_mod.ident.span());
1055        self.map.record_cfg(path.clone(), &item_mod.attrs);
1056        let mut module = Module {
1057            name,
1058            doc: extract_doc(&item_mod.attrs),
1059            functions: Vec::new(),
1060            interfaces: Vec::new(),
1061            callback_interfaces: Vec::new(),
1062            structs: Vec::new(),
1063            enums: Vec::new(),
1064            errors: None,
1065            modules: Vec::new(),
1066        };
1067        let Some((_, items)) = &item_mod.content else {
1068            return Err(syn::Error::new(
1069                item_mod.span(),
1070                "weaveffi: a #[weaveffi::module] must have an inline body (`mod name { ... }`); \
1071                 the macro can't read a module from another file",
1072            ));
1073        };
1074        // Records a declaration's span and `#[cfg]`.
1075        let declare = |map: &mut SourceMap, decl: &str, span: Span, attrs: &[syn::Attribute]| {
1076            let decl = at(&path, &[decl]);
1077            map.record(decl.clone(), span);
1078            map.record_cfg(decl, attrs);
1079        };
1080        for item in items {
1081            match item {
1082                syn::Item::Trait(t) if has_marker(&t.attrs, "callback_interface") => {
1083                    let decl = t.ident.to_string();
1084                    declare(&mut self.map, &decl, t.ident.span(), &t.attrs);
1085                    let cb = self.callback_interface(&at(&path, &[&decl]), t)?;
1086                    module.callback_interfaces.push(cb);
1087                }
1088                syn::Item::Fn(f) if has_marker(&f.attrs, "export") => {
1089                    let decl = f.sig.ident.to_string();
1090                    declare(&mut self.map, &decl, f.sig.ident.span(), &f.attrs);
1091                    let returns = self.return_type(&f.sig.output)?;
1092                    let func = self.function(&at(&path, &[&decl]), &f.sig, &f.attrs, returns)?;
1093                    module.functions.push(func);
1094                }
1095                syn::Item::Struct(s) if has_marker(&s.attrs, "interface") => {
1096                    let decl = s.ident.to_string();
1097                    declare(&mut self.map, &decl, s.ident.span(), &s.attrs);
1098                    module.interfaces.push(InterfaceDef {
1099                        name: decl,
1100                        doc: extract_doc(&s.attrs),
1101                        deprecated: parse_deprecated(&s.attrs).1,
1102                        constructors: vec![],
1103                        methods: vec![],
1104                        statics: vec![],
1105                    });
1106                }
1107                syn::Item::Struct(s) if has_marker(&s.attrs, "record") => {
1108                    let decl = s.ident.to_string();
1109                    declare(&mut self.map, &decl, s.ident.span(), &s.attrs);
1110                    let record = self.record(&at(&path, &[&decl]), s)?;
1111                    module.structs.push(record);
1112                }
1113                syn::Item::Enum(e) if has_marker(&e.attrs, "error") => {
1114                    if module.errors.is_some() {
1115                        return Err(syn::Error::new(
1116                            e.span(),
1117                            "weaveffi: a module may declare at most one #[weaveffi::error] \
1118                             domain",
1119                        ));
1120                    }
1121                    let decl = e.ident.to_string();
1122                    declare(&mut self.map, &decl, e.ident.span(), &e.attrs);
1123                    module.errors = Some(self.error_domain(&at(&path, &[&decl]), e)?);
1124                }
1125                syn::Item::Enum(e) if has_marker(&e.attrs, "enumeration") => {
1126                    let decl = e.ident.to_string();
1127                    declare(&mut self.map, &decl, e.ident.span(), &e.attrs);
1128                    let def = self.enumeration(&at(&path, &[&decl]), e)?;
1129                    module.enums.push(def);
1130                }
1131                syn::Item::Mod(m) if has_marker(&m.attrs, "module") => {
1132                    let child = self.module(&path, m)?;
1133                    module.modules.push(child);
1134                }
1135                _ => {}
1136            }
1137        }
1138
1139        // Second pass: attach `impl` block members to their interfaces. The
1140        // interface struct may be declared after its impl block, so members
1141        // are collected only once every interface name is known.
1142        for item in items {
1143            let syn::Item::Impl(item_impl) = item else {
1144                continue;
1145            };
1146            if item_impl.trait_.is_some() {
1147                continue;
1148            }
1149            let Some(self_name) = type_path_ident(&item_impl.self_ty) else {
1150                continue;
1151            };
1152            if let Some(i) = module.interfaces.iter().position(|i| i.name == self_name) {
1153                let mut iface = std::mem::replace(
1154                    &mut module.interfaces[i],
1155                    InterfaceDef {
1156                        name: String::new(),
1157                        doc: None,
1158                        deprecated: None,
1159                        constructors: vec![],
1160                        methods: vec![],
1161                        statics: vec![],
1162                    },
1163                );
1164                let result = self.members(&at(&path, &[&self_name]), item_impl, &mut iface);
1165                module.interfaces[i] = iface;
1166                result?;
1167            }
1168        }
1169        Ok(module)
1170    }
1171}
1172
1173/// Extract the module tree rooted at a `#[weaveffi::module]`-annotated
1174/// `mod`, together with its [`SourceMap`].
1175///
1176/// Only items carrying a WeaveFFI marker are exported; everything else
1177/// (private helpers, `use` items, free functions without
1178/// `#[weaveffi::export]`) is ignored, so a module can freely mix exported
1179/// surface and implementation. Type aliases declared anywhere in the tree are
1180/// substituted.
1181///
1182/// # Errors
1183///
1184/// Returns a spanned error when an annotated item cannot be mapped to the IR
1185/// (an unsupported type, an enum without `#[repr(i32)]`, a callback interface
1186/// method that doesn't return `Result<T, weaveffi::ForeignError>`, a
1187/// `#[cfg]` on a member, an out-of-line submodule, and so on).
1188pub fn extract_module(item_mod: &syn::ItemMod) -> syn::Result<(Module, SourceMap)> {
1189    let mut aliases = Aliases::new();
1190    collect_aliases(item_mod, &mut aliases);
1191    let mut extractor = Extractor {
1192        aliases: &aliases,
1193        map: SourceMap::default(),
1194    };
1195    let module = extractor.module(&[], item_mod)?;
1196    Ok((module, extractor.map))
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201    use super::*;
1202    use crate::ir::{Api, CURRENT_SCHEMA_VERSION};
1203
1204    /// Extract every top-level `#[weaveffi::module]` in `src`.
1205    fn api_from_src(src: &str) -> syn::Result<Api> {
1206        let file = syn::parse_file(src)?;
1207        let mut modules = Vec::new();
1208        for item in &file.items {
1209            if let syn::Item::Mod(item_mod) = item {
1210                if has_marker(&item_mod.attrs, "module") {
1211                    modules.push(extract_module(item_mod)?.0);
1212                }
1213            }
1214        }
1215        Ok(Api {
1216            version: CURRENT_SCHEMA_VERSION.to_string(),
1217            modules,
1218        })
1219    }
1220
1221    fn one_module(src: &str) -> Module {
1222        let api = api_from_src(src).unwrap();
1223        assert_eq!(api.modules.len(), 1, "expected exactly one module");
1224        api.modules.into_iter().next().unwrap()
1225    }
1226
1227    #[test]
1228    fn extracts_exported_function() {
1229        let m = one_module(
1230            r#"
1231            #[weaveffi::module]
1232            mod math {
1233                #[weaveffi::export]
1234                pub fn add(a: i32, b: i32) -> i32 { a + b }
1235            }
1236        "#,
1237        );
1238        assert_eq!(m.name, "math");
1239        assert_eq!(m.functions.len(), 1);
1240        let f = &m.functions[0];
1241        assert_eq!(f.name, "add");
1242        assert_eq!(f.params[0].ty, TypeRef::Prim(Prim::I32));
1243        assert_eq!(f.returns, Some(TypeRef::Prim(Prim::I32)));
1244        assert!(!f.r#async);
1245    }
1246
1247    #[test]
1248    fn unmarked_module_is_ignored() {
1249        let api = api_from_src(
1250            r#"
1251            mod plain {
1252                #[weaveffi::export]
1253                pub fn add(a: i32) -> i32 { a }
1254            }
1255        "#,
1256        )
1257        .unwrap();
1258        assert!(api.modules.is_empty());
1259    }
1260
1261    #[test]
1262    fn result_return_peels_to_ok_type() {
1263        let m = one_module(
1264            r#"
1265            #[weaveffi::module]
1266            mod m {
1267                #[weaveffi::export]
1268                pub fn get(id: u64) -> Result<Contact, MyError> { todo!() }
1269            }
1270        "#,
1271        );
1272        assert_eq!(
1273            m.functions[0].returns,
1274            Some(TypeRef::Named("Contact".into()))
1275        );
1276        assert_eq!(m.functions[0].params[0].ty, TypeRef::Prim(Prim::U64));
1277    }
1278
1279    #[test]
1280    fn result_unit_return_is_none() {
1281        let m = one_module(
1282            r#"
1283            #[weaveffi::module]
1284            mod m {
1285                #[weaveffi::export]
1286                pub fn run() -> Result<(), MyError> { Ok(()) }
1287            }
1288        "#,
1289        );
1290        assert_eq!(m.functions[0].returns, None);
1291    }
1292
1293    #[test]
1294    fn async_fn_is_async() {
1295        let m = one_module(
1296            r#"
1297            #[weaveffi::module]
1298            mod m {
1299                #[weaveffi::export]
1300                pub async fn fetch(url: String) -> String { url }
1301            }
1302        "#,
1303        );
1304        assert!(m.functions[0].r#async);
1305    }
1306
1307    #[test]
1308    fn record_and_enumeration() {
1309        let m = one_module(
1310            r#"
1311            #[weaveffi::module]
1312            mod contacts {
1313                #[weaveffi::enumeration]
1314                #[repr(i32)]
1315                pub enum ContactType { Personal = 0, Work = 1 }
1316
1317                #[weaveffi::record]
1318                pub struct Contact {
1319                    pub id: i64,
1320                    pub email: Option<String>,
1321                    pub kind: ContactType,
1322                }
1323            }
1324        "#,
1325        );
1326        assert_eq!(m.enums.len(), 1);
1327        assert_eq!(m.enums[0].variants.len(), 2);
1328        assert_eq!(m.structs.len(), 1);
1329        let s = &m.structs[0];
1330        assert_eq!(
1331            s.fields[1].ty,
1332            TypeRef::Optional(Box::new(TypeRef::Prim(Prim::String)))
1333        );
1334        assert_eq!(s.fields[2].ty, TypeRef::Named("ContactType".into()));
1335    }
1336
1337    #[test]
1338    fn enumeration_requires_repr_i32() {
1339        let err = api_from_src(
1340            r#"
1341            #[weaveffi::module]
1342            mod m {
1343                #[weaveffi::enumeration]
1344                pub enum Bad { A = 0 }
1345            }
1346        "#,
1347        )
1348        .unwrap_err();
1349        assert!(err.to_string().contains("repr(i32)"));
1350    }
1351
1352    #[test]
1353    fn references_and_arcs_name_the_pointee() {
1354        let m = one_module(
1355            r#"
1356            #[weaveffi::module]
1357            mod m {
1358                #[weaveffi::export]
1359                pub fn open(name: &str, data: &[u8], tags: &[String]) -> Arc<Store> { todo!() }
1360                #[weaveffi::export]
1361                pub fn peek(store: &Store, other: Option<Arc<Store>>) -> Option<Arc<Store>> { None }
1362            }
1363        "#,
1364        );
1365        let open = &m.functions[0];
1366        assert_eq!(open.params[0].ty, TypeRef::Prim(Prim::String));
1367        assert_eq!(open.params[1].ty, TypeRef::Prim(Prim::Bytes));
1368        assert_eq!(
1369            open.params[2].ty,
1370            TypeRef::List(Box::new(TypeRef::Prim(Prim::String)))
1371        );
1372        assert_eq!(open.returns, Some(TypeRef::Named("Store".into())));
1373        let peek = &m.functions[1];
1374        assert_eq!(peek.params[0].ty, TypeRef::Named("Store".into()));
1375        let opt_store = TypeRef::Optional(Box::new(TypeRef::Named("Store".into())));
1376        assert_eq!(peek.params[1].ty, opt_store);
1377        assert_eq!(peek.returns, Some(opt_store));
1378    }
1379
1380    #[test]
1381    fn raw_pointers_and_boxes_are_rejected() {
1382        let err = api_from_src(
1383            r#"
1384            #[weaveffi::module]
1385            mod m {
1386                #[weaveffi::export]
1387                pub fn close(token: *mut Token) {}
1388            }
1389        "#,
1390        )
1391        .unwrap_err();
1392        assert!(err.to_string().contains("raw pointers"));
1393        let err = api_from_src(
1394            r#"
1395            #[weaveffi::module]
1396            mod m {
1397                #[weaveffi::export]
1398                pub fn listen(l: Box<dyn Listener>) {}
1399            }
1400        "#,
1401        )
1402        .unwrap_err();
1403        assert!(err.to_string().contains("Arc<dyn Trait>"));
1404        let err = api_from_src(
1405            r#"
1406            #[weaveffi::module]
1407            mod m {
1408                #[weaveffi::export]
1409                pub fn bump(counter: &mut i32) {}
1410            }
1411        "#,
1412        )
1413        .unwrap_err();
1414        assert!(err.to_string().contains("&mut"));
1415    }
1416
1417    #[test]
1418    fn cancel_token_param_is_skipped() {
1419        let m = one_module(
1420            r#"
1421            #[weaveffi::module]
1422            mod m {
1423                #[weaveffi::export]
1424                #[weaveffi::cancellable]
1425                pub async fn compact(store: Arc<Store>, cancel: weaveffi::CancelToken) -> i64 { 0 }
1426            }
1427        "#,
1428        );
1429        let f = &m.functions[0];
1430        assert!(f.r#async);
1431        assert!(f.cancellable);
1432        // Only `store` survives; the `CancelToken` is dropped from the IDL.
1433        assert_eq!(f.params.len(), 1);
1434        assert_eq!(f.params[0].name, "store");
1435        assert_eq!(f.params[0].ty, TypeRef::Named("Store".into()));
1436    }
1437
1438    #[test]
1439    fn interface_members_and_receivers() {
1440        let m = one_module(
1441            r#"
1442            #[weaveffi::module]
1443            mod m {
1444                #[weaveffi::interface]
1445                pub struct Store;
1446                impl Store {
1447                    pub fn open(path: String) -> Result<Arc<Self>, StoreError> { todo!() }
1448                    pub fn new() -> Self { Store }
1449                    pub fn len(&self) -> u64 { 0 }
1450                    pub fn share(self: Arc<Self>) -> Arc<Self> { self }
1451                    pub fn default_path() -> String { String::new() }
1452                    fn private(&self) {}
1453                }
1454            }
1455        "#,
1456        );
1457        let s = &m.interfaces[0];
1458        assert_eq!(s.constructors.len(), 2);
1459        assert!(s.constructors[0].throws);
1460        assert_eq!(s.constructors[0].returns, None);
1461        assert_eq!(s.methods.len(), 2);
1462        assert_eq!(s.methods[1].returns, Some(TypeRef::Named("Store".into())));
1463        assert_eq!(s.statics.len(), 1);
1464    }
1465
1466    #[test]
1467    fn doc_links_unwrap_to_inline_code() {
1468        assert_eq!(
1469            unwrap_doc_links("Fails with [`KvError::KeyNotFound`] for [`Store`]."),
1470            "Fails with `KvError::KeyNotFound` for `Store`."
1471        );
1472        assert_eq!(
1473            unwrap_doc_links("See [`docs`](https://x.dev) and [`a`][b]; [`c`]: d"),
1474            "See [`docs`](https://x.dev) and [`a`][b]; [`c`]: d"
1475        );
1476        assert_eq!(
1477            unwrap_doc_links("A `[string]`, [`]`, [x]."),
1478            "A `[string]`, [`]`, [x]."
1479        );
1480        assert_eq!(unwrap_doc_links("open [`"), "open [`");
1481
1482        let m = one_module(
1483            r#"
1484            /// Uses [`Store`].
1485            #[weaveffi::module]
1486            mod m {
1487                /// Returns [`Store::size`] items.
1488                ///
1489                /// ```
1490                /// let v = [`x`];
1491                /// ```
1492                #[weaveffi::export]
1493                pub fn count() -> i32 { 0 }
1494            }
1495        "#,
1496        );
1497        assert_eq!(
1498            m.functions[0].doc.as_deref(),
1499            Some("Returns `Store::size` items.\n\n```\nlet v = [`x`];\n```")
1500        );
1501    }
1502
1503    #[test]
1504    fn async_associated_functions_returning_the_interface_are_statics() {
1505        let m = one_module(
1506            r#"
1507            #[weaveffi::module]
1508            mod m {
1509                #[weaveffi::interface]
1510                pub struct Store;
1511                impl Store {
1512                    pub async fn open_async(path: String) -> Result<Arc<Self>, StoreError> { todo!() }
1513                    pub async fn fresh() -> Self { Store }
1514                    pub async fn named() -> Store { Store }
1515                }
1516            }
1517        "#,
1518        );
1519        let s = &m.interfaces[0];
1520        assert!(s.constructors.is_empty());
1521        assert_eq!(s.statics.len(), 3);
1522        for f in &s.statics {
1523            assert!(f.r#async, "{} is async", f.name);
1524            assert_eq!(
1525                f.returns,
1526                Some(TypeRef::Named("Store".into())),
1527                "{}",
1528                f.name
1529            );
1530        }
1531        assert!(s.statics[0].throws);
1532    }
1533
1534    #[test]
1535    fn mut_self_receiver_is_rejected() {
1536        let err = api_from_src(
1537            r#"
1538            #[weaveffi::module]
1539            mod m {
1540                #[weaveffi::interface]
1541                pub struct Store;
1542                impl Store {
1543                    pub fn bump(&mut self) {}
1544                }
1545            }
1546        "#,
1547        )
1548        .unwrap_err();
1549        assert!(err.to_string().contains("&self"));
1550    }
1551
1552    #[test]
1553    fn callback_interface_trait() {
1554        let m = one_module(
1555            r#"
1556            #[weaveffi::module]
1557            mod m {
1558                /// Receives messages.
1559                #[weaveffi::callback_interface]
1560                pub trait Listener: Send + Sync {
1561                    fn on_message(&self, text: String, meta: Option<Meta>) -> Result<(), ForeignError>;
1562                    fn level(&self) -> Result<i32, weaveffi::ForeignError>;
1563                    #[weaveffi::throws]
1564                    fn label(&self) -> Result<String, ForeignError>;
1565                }
1566                #[weaveffi::export]
1567                pub fn subscribe(listener: Arc<dyn Listener>) {}
1568                #[weaveffi::export]
1569                pub fn subscribe_bounded(listener: Option<Arc<dyn Listener + Send + Sync>>) {}
1570            }
1571        "#,
1572        );
1573        assert_eq!(m.callback_interfaces.len(), 1);
1574        let cb = &m.callback_interfaces[0];
1575        assert_eq!(cb.name, "Listener");
1576        assert_eq!(cb.doc.as_deref(), Some("Receives messages."));
1577        let methods = &cb.methods;
1578        assert_eq!(methods[0].params.len(), 2);
1579        assert_eq!(methods[0].returns, None);
1580        assert!(!methods[0].throws);
1581        assert_eq!(methods[1].returns, Some(TypeRef::Prim(Prim::I32)));
1582        assert!(!methods[1].throws);
1583        assert_eq!(methods[2].returns, Some(TypeRef::Prim(Prim::String)));
1584        assert!(methods[2].throws);
1585        assert_eq!(
1586            m.functions[0].params[0].ty,
1587            TypeRef::Named("Listener".into())
1588        );
1589        assert_eq!(
1590            m.functions[1].params[0].ty,
1591            TypeRef::Optional(Box::new(TypeRef::Named("Listener".into())))
1592        );
1593    }
1594
1595    #[test]
1596    fn callback_methods_must_return_foreign_results() {
1597        for ret in ["", "-> i32", "-> Result<i32, String>"] {
1598            let src = format!(
1599                "#[weaveffi::module] mod m {{ #[weaveffi::callback_interface] \
1600                 pub trait L {{ fn f(&self) {ret}; }} }}"
1601            );
1602            let err = api_from_src(&src).unwrap_err();
1603            assert!(err.to_string().contains("ForeignError"), "{ret}: {err}");
1604        }
1605    }
1606
1607    #[test]
1608    fn aliases_are_substituted() {
1609        let m = one_module(
1610            r#"
1611            #[weaveffi::module]
1612            mod m {
1613                pub type Id = u64;
1614                pub type Ids = Vec<Id>;
1615                #[weaveffi::export]
1616                pub fn get(id: Id, all: Ids) -> Option<Id> { None }
1617                #[weaveffi::module]
1618                mod inner {
1619                    #[weaveffi::export]
1620                    pub fn deep(id: super::Id) {}
1621                }
1622            }
1623        "#,
1624        );
1625        let f = &m.functions[0];
1626        assert_eq!(f.params[0].ty, TypeRef::Prim(Prim::U64));
1627        assert_eq!(
1628            f.params[1].ty,
1629            TypeRef::List(Box::new(TypeRef::Prim(Prim::U64)))
1630        );
1631        assert_eq!(
1632            f.returns,
1633            Some(TypeRef::Optional(Box::new(TypeRef::Prim(Prim::U64))))
1634        );
1635        assert_eq!(
1636            m.modules[0].functions[0].params[0].ty,
1637            TypeRef::Prim(Prim::U64)
1638        );
1639    }
1640
1641    #[test]
1642    fn member_cfgs_and_out_of_line_modules_are_rejected() {
1643        for (src, needle) in [
1644            (
1645                "#[weaveffi::module] mod m { #[weaveffi::record] pub struct R { #[cfg(unix)] pub x: i32 } }",
1646                "record field",
1647            ),
1648            (
1649                "#[weaveffi::module] mod m { #[weaveffi::interface] pub struct S; impl S { #[cfg(unix)] pub fn f(&self) {} } }",
1650                "interface member",
1651            ),
1652            (
1653                "#[weaveffi::module] mod m { #[weaveffi::enumeration] #[repr(i32)] pub enum E { #[cfg(unix)] A = 0 } }",
1654                "enum variant",
1655            ),
1656            (
1657                "#[weaveffi::module] mod m { #[weaveffi::module] mod inner; }",
1658                "inline body",
1659            ),
1660        ] {
1661            let err = api_from_src(src).unwrap_err().to_string();
1662            assert!(err.contains(needle), "{src}: {err}");
1663        }
1664    }
1665
1666    #[test]
1667    fn the_source_map_records_spans_and_item_cfgs() {
1668        let file: syn::File = syn::parse_str(
1669            r#"
1670            #[weaveffi::module]
1671            mod m {
1672                #[cfg(unix)]
1673                #[weaveffi::export]
1674                pub fn f(x: usize) {}
1675                #[weaveffi::interface]
1676                pub struct S;
1677                #[cfg(feature = "s")]
1678                impl S {
1679                    pub fn get(&self) -> i32 { 0 }
1680                }
1681            }
1682        "#,
1683        )
1684        .unwrap();
1685        let syn::Item::Mod(item) = &file.items[0] else {
1686            panic!("a module")
1687        };
1688        let (_, map) = extract_module(item).unwrap();
1689        let path = |p: &[&str]| p.iter().map(ToString::to_string).collect::<Vec<_>>();
1690        assert_eq!(map.spans(&path(&["m", "f"])).len(), 1);
1691        assert_eq!(map.spans(&path(&["m", "f", "x", SourceMap::TYPE])).len(), 1);
1692        assert_eq!(map.cfg(&path(&["m", "f"])).len(), 1);
1693        assert_eq!(map.cfg(&path(&["m", "S", "get"])).len(), 1);
1694        assert!(map.cfg(&path(&["m", "S"])).is_empty());
1695    }
1696
1697    #[test]
1698    fn callback_interface_methods_need_self() {
1699        let err = api_from_src(
1700            r#"
1701            #[weaveffi::module]
1702            mod m {
1703                #[weaveffi::callback_interface]
1704                pub trait Listener {
1705                    fn make() -> i32;
1706                }
1707            }
1708        "#,
1709        )
1710        .unwrap_err();
1711        assert!(err.to_string().contains("&self"));
1712    }
1713
1714    #[test]
1715    fn nested_modules() {
1716        let m = one_module(
1717            r#"
1718            #[weaveffi::module]
1719            mod outer {
1720                #[weaveffi::export]
1721                pub fn top() -> i32 { 0 }
1722                #[weaveffi::module]
1723                mod inner {
1724                    #[weaveffi::export]
1725                    pub fn deep(x: bool) -> bool { x }
1726                }
1727            }
1728        "#,
1729        );
1730        assert_eq!(m.modules.len(), 1);
1731        assert_eq!(m.modules[0].name, "inner");
1732        assert_eq!(m.modules[0].functions[0].name, "deep");
1733    }
1734}