use weaveffi_ir::ir::TypeRef;
use crate::abi::lower::split_qualified;
use crate::model::{AsyncBinding, FnBinding, IteratorBinding};
#[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, PartialEq, Eq)]
pub enum ElemFree {
None,
String,
Object {
destroy_symbol: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReturnFree {
None,
String,
Bytes,
BoxedScalar,
Array {
elem: ElemFree,
},
MapBuffers {
key: ElemFree,
value: ElemFree,
},
OwnedObject {
destroy_symbol: String,
},
}
pub fn elem_free(ty: &TypeRef, module: &str, prefix: &str) -> ElemFree {
match ty {
TypeRef::StringUtf8 | TypeRef::BorrowedStr => ElemFree::String,
TypeRef::Record(name) | TypeRef::RichEnum(name) => ElemFree::Object {
destroy_symbol: destroy_symbol(name, module, prefix),
},
TypeRef::Optional(inner) => elem_free(inner, module, prefix),
_ => ElemFree::None,
}
}
pub fn return_free(ty: Option<&TypeRef>, module: &str, prefix: &str) -> ReturnFree {
let Some(ty) = ty else {
return ReturnFree::None;
};
match ty {
TypeRef::StringUtf8 | TypeRef::BorrowedStr => ReturnFree::String,
TypeRef::Bytes | TypeRef::BorrowedBytes => ReturnFree::Bytes,
TypeRef::Record(name) | TypeRef::RichEnum(name) | TypeRef::Interface(name) => {
ReturnFree::OwnedObject {
destroy_symbol: destroy_symbol(name, module, prefix),
}
}
TypeRef::Optional(inner) => match inner.as_ref() {
t if crate::codegen::common::is_c_pointer_type(t) => {
return_free(Some(t), module, prefix)
}
_ => ReturnFree::BoxedScalar,
},
TypeRef::List(inner) => ReturnFree::Array {
elem: elem_free(inner, module, prefix),
},
TypeRef::Map(k, v) => ReturnFree::MapBuffers {
key: elem_free(k, module, prefix),
value: elem_free(v, module, prefix),
},
TypeRef::Iterator(_) => ReturnFree::None,
_ => ReturnFree::None,
}
}
fn destroy_symbol(name: &str, current_module: &str, prefix: &str) -> String {
let (module, name) = split_qualified(name, current_module);
format!("{prefix}_{module}_{name}_destroy")
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IteratorProtocol<'a> {
pub binding: &'a IteratorBinding,
pub elem_free: ElemFree,
pub error: ErrorStrategy,
}
impl IteratorBinding {
pub fn protocol<'a>(
&'a self,
f: &FnBinding,
module: &str,
prefix: &str,
) -> IteratorProtocol<'a> {
IteratorProtocol {
binding: self,
elem_free: elem_free(&self.elem, module, prefix),
error: f.error_strategy(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AsyncProtocol<'a> {
pub binding: &'a AsyncBinding,
pub cancellable: bool,
pub result_adopt: ReturnFree,
pub error: ErrorStrategy,
}
impl AsyncBinding {
pub fn protocol<'a>(&'a self, f: &FnBinding, module: &str, prefix: &str) -> AsyncProtocol<'a> {
fn adoptable(ty: &TypeRef) -> Option<&TypeRef> {
match ty {
TypeRef::Record(_) | TypeRef::RichEnum(_) | TypeRef::Interface(_) => Some(ty),
TypeRef::Optional(inner) => adoptable(inner),
_ => None,
}
}
let result_adopt = match f.ret.as_ref().and_then(|ty| adoptable(ty)) {
Some(ty) => return_free(Some(ty), module, prefix),
None => ReturnFree::None,
};
AsyncProtocol {
binding: self,
cancellable: f.cancellable,
result_adopt,
error: f.error_strategy(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strings_and_bytes_have_runtime_frees() {
assert_eq!(
return_free(Some(&TypeRef::StringUtf8), "m", "weaveffi"),
ReturnFree::String
);
assert_eq!(
return_free(Some(&TypeRef::Bytes), "m", "weaveffi"),
ReturnFree::Bytes
);
assert_eq!(return_free(None, "m", "weaveffi"), ReturnFree::None);
}
#[test]
fn object_returns_are_adopted_with_destroy_symbols() {
assert_eq!(
return_free(
Some(&TypeRef::Record("Contact".into())),
"contacts",
"weaveffi"
),
ReturnFree::OwnedObject {
destroy_symbol: "weaveffi_contacts_Contact_destroy".into()
}
);
assert_eq!(
return_free(
Some(&TypeRef::Interface("kv.Store".into())),
"kv_stats",
"weaveffi"
),
ReturnFree::OwnedObject {
destroy_symbol: "weaveffi_kv_Store_destroy".into()
}
);
}
#[test]
fn optional_returns_split_boxed_scalar_from_pointer() {
assert_eq!(
return_free(
Some(&TypeRef::Optional(Box::new(TypeRef::I64))),
"m",
"weaveffi"
),
ReturnFree::BoxedScalar
);
assert_eq!(
return_free(
Some(&TypeRef::Optional(Box::new(TypeRef::StringUtf8))),
"m",
"weaveffi"
),
ReturnFree::String
);
}
#[test]
fn array_and_map_returns_carry_element_plans() {
assert_eq!(
return_free(
Some(&TypeRef::List(Box::new(TypeRef::StringUtf8))),
"m",
"weaveffi"
),
ReturnFree::Array {
elem: ElemFree::String
}
);
assert_eq!(
return_free(
Some(&TypeRef::Map(
Box::new(TypeRef::StringUtf8),
Box::new(TypeRef::I32)
)),
"m",
"weaveffi"
),
ReturnFree::MapBuffers {
key: ElemFree::String,
value: ElemFree::None
}
);
}
#[test]
fn list_of_records_frees_each_object() {
assert_eq!(
return_free(
Some(&TypeRef::List(Box::new(TypeRef::Record("Entry".into())))),
"kv",
"weaveffi"
),
ReturnFree::Array {
elem: ElemFree::Object {
destroy_symbol: "weaveffi_kv_Entry_destroy".into()
}
}
);
}
}