use crate::abi::split_qualified;
use crate::abi::AbiParam;
use crate::model::{
AsyncBinding, CallbackInterfaceBinding, Family, FnBinding, IteratorBinding, ParamBinding, Ty,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorStrategy {
Throws,
Trap,
}
impl FnBinding {
pub fn error_strategy(&self) -> ErrorStrategy {
if self.throws {
ErrorStrategy::Throws
} else {
ErrorStrategy::Trap
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgPass<'a> {
Direct {
slot: &'a AbiParam,
},
String {
ptr: &'a AbiParam,
len: &'a AbiParam,
},
Bytes {
ptr: &'a AbiParam,
len: &'a AbiParam,
},
Buffer {
ptr: &'a AbiParam,
len: &'a AbiParam,
},
Object {
slot: &'a AbiParam,
nullable: bool,
},
Callback {
ctx: &'a AbiParam,
vtable: &'a AbiParam,
},
}
impl ParamBinding {
pub fn arg_pass(&self) -> ArgPass<'_> {
let pair = || {
assert!(
self.abi.len() == 2,
"two-slot parameter '{}' must have exactly two ABI slots",
self.name
);
(&self.abi[0], &self.abi[1])
};
let single = || {
assert!(
self.abi.len() == 1,
"single-slot parameter '{}' must have exactly one ABI slot",
self.name
);
&self.abi[0]
};
match self.ty.family() {
Family::Direct => ArgPass::Direct { slot: single() },
Family::String => {
let (ptr, len) = pair();
ArgPass::String { ptr, len }
}
Family::Bytes => {
let (ptr, len) = pair();
ArgPass::Bytes { ptr, len }
}
Family::Buffer => {
let (ptr, len) = pair();
ArgPass::Buffer { ptr, len }
}
Family::Object { nullable } => ArgPass::Object {
slot: single(),
nullable,
},
Family::Callback => {
let (ctx, vtable) = pair();
ArgPass::Callback { ctx, vtable }
}
Family::Iterator => unreachable!("iterators are never parameters"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RetPass {
Void,
Direct,
String,
Bytes,
Buffer,
Object {
destroy_symbol: String,
clone_symbol: String,
nullable: bool,
},
}
impl RetPass {
#[must_use]
pub fn frees_bytes(&self) -> bool {
matches!(self, RetPass::String | RetPass::Bytes | RetPass::Buffer)
}
}
pub fn ret_pass(ty: Option<&Ty>, prefix: &str) -> RetPass {
let Some(ty) = ty else {
return RetPass::Void;
};
match ty.family() {
Family::Direct => RetPass::Direct,
Family::String => RetPass::String,
Family::Bytes => RetPass::Bytes,
Family::Buffer => RetPass::Buffer,
Family::Object { nullable } => {
let iface = ty
.interface_name()
.expect("object family names an interface");
RetPass::Object {
destroy_symbol: destroy_symbol(iface, prefix),
clone_symbol: clone_symbol(iface, prefix),
nullable,
}
}
Family::Callback => panic!("callback interfaces are never returned"),
Family::Iterator => panic!("iterator returns follow IteratorProtocol, not a RetPass"),
}
}
pub fn destroy_symbol(name: &str, prefix: &str) -> String {
let (module, name) = split_qualified(name, "");
format!("{prefix}_{module}_{name}_destroy")
}
pub fn clone_symbol(name: &str, prefix: &str) -> String {
let (module, name) = split_qualified(name, "");
format!("{prefix}_{module}_{name}_clone")
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IteratorProtocol<'a> {
pub binding: &'a IteratorBinding,
pub elem: RetPass,
pub error: ErrorStrategy,
}
impl IteratorBinding {
pub fn protocol<'a>(&'a self, f: &FnBinding, prefix: &str) -> IteratorProtocol<'a> {
let elem = ret_pass(Some(&self.elem), prefix);
IteratorProtocol {
binding: self,
elem,
error: f.error_strategy(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AsyncProtocol<'a> {
pub binding: &'a AsyncBinding,
pub cancellable: bool,
pub result: RetPass,
pub error: ErrorStrategy,
}
impl AsyncBinding {
pub fn protocol<'a>(&'a self, f: &FnBinding, prefix: &str) -> AsyncProtocol<'a> {
AsyncProtocol {
binding: self,
cancellable: f.cancellable,
result: ret_pass(f.ret.as_ref(), prefix),
error: f.error_strategy(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallbackProtocol<'a> {
pub binding: &'a CallbackInterfaceBinding,
pub method_args: Vec<Vec<RetPass>>,
}
impl CallbackInterfaceBinding {
pub fn protocol<'a>(&'a self, prefix: &str) -> CallbackProtocol<'a> {
CallbackProtocol {
binding: self,
method_args: self
.methods
.iter()
.map(|m| {
m.params
.iter()
.map(|p| ret_pass(Some(&p.ty), prefix))
.collect()
})
.collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arg_pass_classifies_every_family() {
let pb = |name: &str, ty: Ty| ParamBinding::new(name, ty, None, "m");
assert!(matches!(
pb("x", Ty::I32).arg_pass(),
ArgPass::Direct { slot } if slot.name == "x"
));
assert!(matches!(
pb("s", Ty::StringUtf8).arg_pass(),
ArgPass::String { ptr, .. } if ptr.name == "s_ptr"
));
assert!(matches!(
pb("data", Ty::Bytes).arg_pass(),
ArgPass::Bytes { ptr, len } if ptr.name == "data_ptr" && len.name == "data_len"
));
assert!(matches!(
pb("c", Ty::Record("Contact".into())).arg_pass(),
ArgPass::Buffer { ptr, len } if ptr.name == "c_ptr" && len.name == "c_len"
));
assert!(matches!(
pb("o", Ty::Optional(Box::new(Ty::I32))).arg_pass(),
ArgPass::Buffer { .. }
));
assert!(matches!(
pb("store", Ty::Interface("Store".into())).arg_pass(),
ArgPass::Object {
nullable: false,
..
}
));
assert!(matches!(
pb(
"store",
Ty::Optional(Box::new(Ty::Interface("Store".into())))
)
.arg_pass(),
ArgPass::Object { nullable: true, .. }
));
assert!(matches!(
pb("l", Ty::CallbackInterface("Listener".into())).arg_pass(),
ArgPass::Callback { ctx, vtable } if ctx.name == "l_ctx" && vtable.name == "l_vtable"
));
}
#[test]
fn ret_pass_distinguishes_copy_decode_and_adopt() {
assert_eq!(ret_pass(None, "weaveffi"), RetPass::Void);
assert_eq!(ret_pass(Some(&Ty::I64), "weaveffi"), RetPass::Direct);
assert_eq!(ret_pass(Some(&Ty::StringUtf8), "weaveffi"), RetPass::String);
assert_eq!(ret_pass(Some(&Ty::Bytes), "weaveffi"), RetPass::Bytes);
for ty in [
Ty::Record("Contact".into()),
Ty::RichEnum("Shape".into()),
Ty::List(Box::new(Ty::StringUtf8)),
Ty::List(Box::new(Ty::Interface("Store".into()))),
Ty::Optional(Box::new(Ty::I64)),
] {
assert_eq!(ret_pass(Some(&ty), "weaveffi"), RetPass::Buffer, "{ty}");
}
assert_eq!(
ret_pass(Some(&Ty::Interface("kv.Store".into())), "weaveffi"),
RetPass::Object {
destroy_symbol: "weaveffi_kv_Store_destroy".into(),
clone_symbol: "weaveffi_kv_Store_clone".into(),
nullable: false,
}
);
assert_eq!(
ret_pass(
Some(&Ty::Optional(Box::new(Ty::Interface("kv.Store".into())))),
"weaveffi"
),
RetPass::Object {
destroy_symbol: "weaveffi_kv_Store_destroy".into(),
clone_symbol: "weaveffi_kv_Store_clone".into(),
nullable: true,
}
);
assert!(RetPass::String.frees_bytes());
assert!(RetPass::Buffer.frees_bytes());
assert!(!RetPass::Direct.frees_bytes());
}
}