use crate::abi::AbiParam;
use crate::model::{
AsyncBinding, CallbackInterfaceBinding, CallbackMethodBinding, FnBinding, IteratorBinding,
ParamBinding,
};
use crate::ty::{Family, 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,
nullable: bool,
},
}
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 { nullable } => {
let (ctx, vtable) = pair();
ArgPass::Callback {
ctx,
vtable,
nullable,
}
}
Family::Iterator => unreachable!("iterators are never parameters"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RetPass {
Void,
Direct,
String,
Bytes,
Buffer,
Object {
nullable: bool,
},
}
impl RetPass {
pub fn of(ty: Option<&Ty>) -> 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 } => RetPass::Object { nullable },
Family::Callback { .. } => panic!("callback interfaces are never returned"),
Family::Iterator => panic!("iterator returns follow IteratorProtocol, not a RetPass"),
}
}
}
#[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) -> IteratorProtocol<'a> {
IteratorProtocol {
binding: self,
elem: RetPass::of(Some(&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) -> AsyncProtocol<'a> {
AsyncProtocol {
binding: self,
cancellable: f.cancellable,
result: RetPass::of(f.ret.as_ref()),
error: f.error_strategy(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallbackProtocol<'a> {
pub binding: &'a CallbackInterfaceBinding,
pub method_args: Vec<Vec<RetPass>>,
pub method_returns: Vec<RetPass>,
pub method_errors: Vec<ErrorStrategy>,
}
impl CallbackMethodBinding {
pub fn error_strategy(&self) -> ErrorStrategy {
if self.throws {
ErrorStrategy::Throws
} else {
ErrorStrategy::Trap
}
}
}
impl CallbackInterfaceBinding {
pub fn protocol(&self) -> CallbackProtocol<'_> {
CallbackProtocol {
binding: self,
method_args: self
.methods
.iter()
.map(|m| m.params.iter().map(|p| RetPass::of(Some(&p.ty))).collect())
.collect(),
method_returns: self
.methods
.iter()
.map(|m| RetPass::of(m.ret.as_ref()))
.collect(),
method_errors: self
.methods
.iter()
.map(CallbackMethodBinding::error_strategy)
.collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{Api, Function, Param, TypeRef};
use crate::model::Model;
use crate::pkg::Identity;
use crate::ty::Prim;
fn param(name: &str, ty: &str) -> Param {
Param {
name: name.into(),
ty: crate::ir::parse_type_ref(ty).unwrap(),
doc: None,
}
}
fn model(params: Vec<Param>) -> Model {
let yaml = r#"
version: "0.11.0"
modules:
- name: kv
structs: [{ name: Contact, fields: [{ name: id, type: i64 }] }]
interfaces: [{ name: Store, methods: [{ name: get }] }]
callback_interfaces: [{ name: Listener, methods: [{ name: on }] }]
"#;
let mut api: Api = serde_yaml::from_str(yaml).unwrap();
api.modules[0].functions.push(Function {
name: "f".into(),
params,
returns: Some(TypeRef::Prim(Prim::I32)),
doc: None,
throws: false,
r#async: false,
cancellable: false,
deprecated: None,
});
Model::assume_valid(&api, Identity::named("weaveffi"))
}
#[test]
fn arg_pass_classifies_every_family() {
let m = model(vec![
param("x", "i32"),
param("s", "string"),
param("data", "bytes"),
param("c", "Contact"),
param("o", "i32?"),
param("store", "Store"),
param("maybe", "Store?"),
param("l", "Listener"),
param("ml", "Listener?"),
]);
let p = &m.modules[0].functions[0].params;
assert!(matches!(p[0].arg_pass(), ArgPass::Direct { slot } if slot.name == "x"));
assert!(matches!(p[1].arg_pass(), ArgPass::String { ptr, .. } if ptr.name == "s_ptr"));
assert!(matches!(
p[2].arg_pass(),
ArgPass::Bytes { ptr, len } if ptr.name == "data_ptr" && len.name == "data_len"
));
assert!(matches!(
p[3].arg_pass(),
ArgPass::Buffer { ptr, len } if ptr.name == "c_ptr" && len.name == "c_len"
));
assert!(matches!(p[4].arg_pass(), ArgPass::Buffer { .. }));
assert!(matches!(
p[5].arg_pass(),
ArgPass::Object {
nullable: false,
..
}
));
assert!(matches!(
p[6].arg_pass(),
ArgPass::Object { nullable: true, .. }
));
assert!(matches!(
p[7].arg_pass(),
ArgPass::Callback { ctx, vtable, nullable: false }
if ctx.name == "l_ctx" && vtable.name == "l_vtable"
));
assert!(matches!(
p[8].arg_pass(),
ArgPass::Callback { nullable: true, .. }
));
}
#[test]
fn ret_pass_distinguishes_copy_decode_and_adopt() {
assert_eq!(RetPass::of(None), RetPass::Void);
assert_eq!(RetPass::of(Some(&Ty::Prim(Prim::I64))), RetPass::Direct);
assert_eq!(RetPass::of(Some(&Ty::Prim(Prim::String))), RetPass::String);
assert_eq!(RetPass::of(Some(&Ty::Prim(Prim::Bytes))), RetPass::Bytes);
for ty in [
Ty::Record("Contact".into()),
Ty::RichEnum("Shape".into()),
Ty::List(Box::new(Ty::Prim(Prim::String))),
Ty::List(Box::new(Ty::Interface("Store".into()))),
Ty::Optional(Box::new(Ty::Prim(Prim::I64))),
] {
assert_eq!(RetPass::of(Some(&ty)), RetPass::Buffer, "{ty}");
}
assert_eq!(
RetPass::of(Some(&Ty::Interface("Store".into()))),
RetPass::Object { nullable: false }
);
assert_eq!(
RetPass::of(Some(&Ty::Optional(Box::new(Ty::Interface("Store".into()))))),
RetPass::Object { nullable: true }
);
}
}