pub mod ctype;
pub mod lower;
pub use ctype::{CType, ConstPos};
pub use lower::{
callback_result_params, lower_param, lower_return, split_qualified, struct_tag, vtable_tag,
AbiParam, AbiReturn,
};
use crate::model::{Family, ParamBinding, Ty};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbiSig {
pub params: Vec<AbiParam>,
pub ret: CType,
}
pub fn error_out_param() -> AbiParam {
AbiParam::new("out_err", CType::ptr(CType::Error))
}
pub fn context_param() -> AbiParam {
AbiParam::new("context", CType::ptr(CType::Void))
}
pub fn ctx_param() -> AbiParam {
AbiParam::new("ctx", CType::ptr(CType::Void))
}
pub fn callback_method_signature(
params: &[ParamBinding],
returns: Option<&Ty>,
module: &str,
) -> AbiSig {
let mut out = vec![ctx_param()];
for p in params {
if matches!(p.ty.family(), Family::Object { .. }) {
let [slot] = p.abi.as_slice() else {
unreachable!("object parameter '{}' has one slot", p.name);
};
let CType::Ptr { pointee, .. } = &slot.ty else {
unreachable!("object slot '{}' is a pointer", p.name);
};
out.push(AbiParam::new(&slot.name, CType::ptr((**pointee).clone())));
} else {
out.extend(p.abi.iter().cloned());
}
}
let ret = match returns {
Some(ty) => {
let r = lower_return(ty, module);
debug_assert!(
r.out_params.is_empty(),
"callback method returns are direct-family only"
);
r.ret
}
None => CType::Void,
};
out.push(error_out_param());
AbiSig { params: out, ret }
}
pub fn cancel_token_param() -> AbiParam {
AbiParam::new("cancel_token", CType::ptr(CType::CancelToken))
}
pub fn sync_signature(params: &[ParamBinding], returns: Option<&Ty>, module: &str) -> AbiSig {
let mut out: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.iter().cloned()).collect();
let ret = match returns {
Some(ty) => {
let r = lower_return(ty, module);
out.extend(r.out_params);
r.ret
}
None => CType::Void,
};
out.push(error_out_param());
AbiSig { params: out, ret }
}
pub fn async_callback_params(returns: Option<&Ty>, module: &str) -> Vec<AbiParam> {
let mut params = vec![
context_param(),
AbiParam::new("err", CType::ptr(CType::Error)),
];
if let Some(ret) = returns {
params.extend(callback_result_params(ret, module));
}
params
}
pub fn async_input_params(params: &[ParamBinding], cancellable: bool) -> Vec<AbiParam> {
let mut out: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.iter().cloned()).collect();
if cancellable {
out.push(cancel_token_param());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn param(name: &str, ty: Ty) -> ParamBinding {
ParamBinding::new(name, ty, None, "math")
}
fn render(params: &[AbiParam]) -> Vec<String> {
params
.iter()
.map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
.collect()
}
#[test]
fn sync_signature_appends_out_err_last() {
let sig = sync_signature(
&[param("a", Ty::I32), param("b", Ty::I32)],
Some(&Ty::I32),
"math",
);
assert_eq!(sig.ret, CType::Int32);
assert_eq!(
render(&sig.params),
["int32_t a", "int32_t b", "weaveffi_error* out_err"]
);
let sig = sync_signature(&[], Some(&Ty::Bytes), "m");
assert_eq!(sig.ret.render_c("weaveffi"), "const uint8_t*");
assert_eq!(
render(&sig.params),
["size_t* out_len", "weaveffi_error* out_err"]
);
let sig = sync_signature(&[param("x", Ty::I32)], None, "m");
assert_eq!(sig.ret, CType::Void);
}
#[test]
fn async_shapes() {
let params = async_callback_params(Some(&Ty::I32), "m");
assert_eq!(
render(¶ms),
["void* context", "weaveffi_error* err", "int32_t result"]
);
let inputs = async_input_params(&[param("id", Ty::I64)], true);
assert_eq!(
render(&inputs),
["int64_t id", "weaveffi_cancel_token* cancel_token"]
);
}
#[test]
fn callback_method_signature_wraps_ctx_and_out_err() {
let sig =
callback_method_signature(&[param("text", Ty::StringUtf8)], Some(&Ty::Bool), "events");
assert_eq!(sig.ret, CType::Bool);
assert_eq!(
render(&sig.params),
[
"void* ctx",
"const uint8_t* text_ptr",
"size_t text_len",
"weaveffi_error* out_err"
]
);
let sig = callback_method_signature(&[], None, "events");
assert_eq!(sig.ret, CType::Void);
assert_eq!(
render(&sig.params),
["void* ctx", "weaveffi_error* out_err"]
);
}
}