mod async_fns;
mod callbacks;
mod enums;
mod helpers;
mod interfaces;
mod iterators;
mod marshal;
mod records;
mod sync;
use std::collections::HashMap;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use weaveffi_core::model::{BindingModel, ErrorBinding, ModuleBinding};
use weaveffi_core::plan::ErrorStrategy;
use weaveffi_ir::ir::{Api, Module, TypeRef, CURRENT_SCHEMA_VERSION};
use self::helpers::{ident, CallTarget};
use self::sync::gen_function;
pub(crate) const PREFIX: &str = "weaveffi";
pub fn expand_module(item_mod: &syn::ItemMod) -> syn::Result<TokenStream> {
if item_mod.content.is_none() {
return Err(syn::Error::new_spanned(
item_mod,
"#[weaveffi::module] requires an inline module body (`mod foo { ... }`)",
));
}
let module_ir = weaveffi_bridge::module_from_item_mod(item_mod)?;
let mut api = Api {
version: CURRENT_SCHEMA_VERSION.to_string(),
package: None,
modules: vec![module_ir],
generators: None,
};
weaveffi_core::validate::resolve_type_refs(&mut api);
resolve_sibling_named_refs(&mut api);
let model = BindingModel::build(&api, PREFIX);
let by_path: HashMap<Vec<String>, &ModuleBinding> = model
.modules
.iter()
.map(|m| (m.segments.clone(), m))
.collect();
rebuild_module(item_mod, &[], &by_path)
}
fn resolve_sibling_named_refs(api: &mut Api) {
fn walk_type(ty: &mut TypeRef) {
match ty {
TypeRef::Named(name) => {
let name = std::mem::take(name);
*ty = TypeRef::Record(name);
}
TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
walk_type(inner);
}
TypeRef::Map(k, v) => {
walk_type(k);
walk_type(v);
}
_ => {}
}
}
fn walk_module(module: &mut Module) {
let callables = module
.functions
.iter_mut()
.chain(module.interfaces.iter_mut().flat_map(|i| {
i.constructors
.iter_mut()
.chain(i.methods.iter_mut())
.chain(i.statics.iter_mut())
}));
for f in callables {
for p in &mut f.params {
walk_type(&mut p.ty);
}
if let Some(ret) = &mut f.returns {
walk_type(ret);
}
}
for s in &mut module.structs {
for field in &mut s.fields {
walk_type(&mut field.ty);
}
}
for e in &mut module.enums {
for v in &mut e.variants {
for field in &mut v.fields {
walk_type(&mut field.ty);
}
}
}
for cb in &mut module.callbacks {
for p in &mut cb.params {
walk_type(&mut p.ty);
}
}
for child in &mut module.modules {
walk_module(child);
}
}
for module in &mut api.modules {
walk_module(module);
}
}
fn rebuild_module(
item_mod: &syn::ItemMod,
parent_segments: &[String],
by_path: &HashMap<Vec<String>, &ModuleBinding>,
) -> syn::Result<TokenStream> {
let Some((_, items)) = &item_mod.content else {
return Err(syn::Error::new_spanned(
item_mod,
"#[weaveffi::module] requires an inline module body (`mod foo { ... }`)",
));
};
let mut segments = parent_segments.to_vec();
segments.push(item_mod.ident.to_string());
let mb = by_path.get(&segments).ok_or_else(|| {
syn::Error::new_spanned(
&item_mod.ident,
"internal error: module has no lowered binding",
)
})?;
let generated = render_symbols(mb, items, &item_mod.ident)?;
let mut body = TokenStream::new();
for item in items {
if let syn::Item::Mod(child) = item {
if weaveffi_bridge::has_marker(&child.attrs, "module") && child.content.is_some() {
body.extend(rebuild_module(child, &segments, by_path)?);
continue;
}
}
body.extend(quote!(#item));
}
let attrs = item_mod
.attrs
.iter()
.filter(|a| !is_module_marker(a))
.collect::<Vec<_>>();
let vis = &item_mod.vis;
let mod_token = &item_mod.mod_token;
let name = &item_mod.ident;
Ok(quote! {
#(#attrs)*
#vis #mod_token #name {
#body
#generated
}
})
}
fn is_module_marker(attr: &syn::Attribute) -> bool {
attr.path()
.segments
.last()
.is_some_and(|s| s.ident == "module")
}
fn render_symbols(
mb: &ModuleBinding,
items: &[syn::Item],
mod_ident: &syn::Ident,
) -> syn::Result<TokenStream> {
let mut fns: HashMap<String, &syn::ItemFn> = HashMap::new();
let mut structs: HashMap<String, &syn::ItemStruct> = HashMap::new();
let mut enums: HashMap<String, &syn::ItemEnum> = HashMap::new();
let mut member_sigs: HashMap<(String, String), &syn::Signature> = HashMap::new();
for item in items {
match item {
syn::Item::Fn(f) => {
fns.insert(f.sig.ident.to_string(), f);
}
syn::Item::Struct(s) => {
structs.insert(s.ident.to_string(), s);
}
syn::Item::Enum(e) => {
enums.insert(e.ident.to_string(), e);
}
syn::Item::Impl(i) if i.trait_.is_none() => {
let Some(ty_name) = impl_type_name(i) else {
continue;
};
for impl_item in &i.items {
if let syn::ImplItem::Fn(f) = impl_item {
member_sigs.insert((ty_name.clone(), f.sig.ident.to_string()), &f.sig);
}
}
}
_ => {}
}
}
let missing = |what: &str, name: &str| {
syn::Error::new_spanned(
mod_ident,
format!("internal error: no source for exported {what} `{name}`"),
)
};
if mb.error.is_none() {
if let Some(t) = mb
.callables()
.find(|f| f.error_strategy() == ErrorStrategy::Throws)
{
return Err(syn::Error::new_spanned(
mod_ident,
format!(
"weaveffi: `{}` returns a Result but no error domain is in scope; declare \
a #[weaveffi::error] enum in this module (or a parent module)",
t.name
),
));
}
}
let mut generated = TokenStream::new();
if let Some(eb) = mb.error.as_ref().filter(|e| e.declared_here) {
generated.extend(gen_error_report(eb));
}
for e in &mb.enums {
generated.extend(enums::gen_enum(e, enums.get(&e.name).copied())?);
}
for s in &mb.structs {
generated.extend(records::gen_record(s, structs.get(&s.name).copied())?);
}
for c in &mb.callbacks {
generated.extend(callbacks::gen_callback_type(c)?);
}
for l in &mb.listeners {
generated.extend(callbacks::gen_listener(mb, l)?);
}
for i in &mb.interfaces {
let ty = ident(&i.name);
for (members, target) in [
(&i.constructors, CallTarget::Static(ty.clone())),
(&i.statics, CallTarget::Static(ty.clone())),
(&i.methods, CallTarget::Method(ty.clone())),
] {
for m in members {
let sig = member_sigs
.get(&(i.name.clone(), m.name.clone()))
.ok_or_else(|| missing("interface member", &m.name))?;
generated.extend(gen_function(m, sig, &target)?);
}
}
generated.extend(interfaces::gen_interface_destroy(i));
}
for f in &mb.functions {
let sfn = fns
.get(&f.name)
.ok_or_else(|| missing("function", &f.name))?;
generated.extend(gen_function(f, &sfn.sig, &CallTarget::Free)?);
}
Ok(generated)
}
fn impl_type_name(item_impl: &syn::ItemImpl) -> Option<String> {
let syn::Type::Path(p) = item_impl.self_ty.as_ref() else {
return None;
};
p.path.segments.last().map(|s| s.ident.to_string())
}
fn gen_error_report(eb: &ErrorBinding) -> TokenStream {
let ty = ident(&eb.name);
let code_arms = eb.codes.iter().map(|c| {
let v = ident(&c.name);
let value = c.value;
quote!(Self::#v => #value,)
});
let msg_arms = eb.codes.iter().map(|c| {
let v = ident(&c.name);
let msg = &c.message;
quote!(Self::#v => #msg.to_string(),)
});
quote! {
impl ::weaveffi::abi::ErrorReport for #ty {
fn code(&self) -> i32 {
match self {
#(#code_arms)*
}
}
fn message(&self) -> String {
match self {
#(#msg_arms)*
}
}
}
}
}
fn unsupported(what: &str, kind: &str) -> syn::Error {
syn::Error::new(
Span::call_site(),
format!("weaveffi: unsupported {kind} for `{what}` (not yet implemented by #[weaveffi::module])"),
)
}