use anyhow::Result;
use uniffi_meta::Checksum;
use super::callbacks;
use super::ffi::{FfiArgument, FfiCallbackFunction, FfiFunction, FfiStruct, FfiType};
use super::function::{Argument, Callable};
use super::{AsType, ObjectImpl, Type, TypeIterator};
#[derive(Debug, Clone, Checksum)]
pub struct Object {
pub(super) name: String,
pub(super) imp: ObjectImpl,
pub(super) module_path: String,
pub(super) constructors: Vec<Constructor>,
pub(super) methods: Vec<Method>,
pub(super) uniffi_traits: Vec<UniffiTrait>,
#[checksum_ignore]
pub(super) ffi_func_clone: FfiFunction,
#[checksum_ignore]
pub(super) ffi_func_free: FfiFunction,
#[checksum_ignore]
pub(super) ffi_init_callback: Option<FfiFunction>,
#[checksum_ignore]
pub(super) docstring: Option<String>,
}
impl Object {
pub fn name(&self) -> &str {
&self.name
}
pub fn rust_name(&self) -> String {
self.imp.rust_name_for(&self.name)
}
pub fn imp(&self) -> &ObjectImpl {
&self.imp
}
pub fn is_trait_interface(&self) -> bool {
self.imp.is_trait_interface()
}
pub fn has_callback_interface(&self) -> bool {
self.imp.has_callback_interface()
}
pub fn has_async_method(&self) -> bool {
self.methods.iter().any(Method::is_async)
}
pub fn constructors(&self) -> Vec<&Constructor> {
self.constructors.iter().collect()
}
pub fn primary_constructor(&self) -> Option<&Constructor> {
self.constructors
.iter()
.find(|cons| cons.is_primary_constructor())
}
pub fn alternate_constructors(&self) -> Vec<&Constructor> {
self.constructors
.iter()
.filter(|cons| !cons.is_primary_constructor())
.collect()
}
pub fn methods(&self) -> Vec<&Method> {
self.methods.iter().collect()
}
pub fn get_method(&self, name: &str) -> Method {
let matches: Vec<_> = self.methods.iter().filter(|m| m.name() == name).collect();
match matches.len() {
1 => matches[0].clone(),
n => panic!("{n} methods named {name}"),
}
}
pub fn uniffi_traits(&self) -> Vec<&UniffiTrait> {
self.uniffi_traits.iter().collect()
}
pub fn ffi_object_clone(&self) -> &FfiFunction {
&self.ffi_func_clone
}
pub fn ffi_object_free(&self) -> &FfiFunction {
&self.ffi_func_free
}
pub fn ffi_init_callback(&self) -> &FfiFunction {
self.ffi_init_callback
.as_ref()
.unwrap_or_else(|| panic!("No ffi_init_callback set for {}", &self.name))
}
pub fn docstring(&self) -> Option<&str> {
self.docstring.as_deref()
}
pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> {
[&self.ffi_func_clone, &self.ffi_func_free]
.into_iter()
.chain(&self.ffi_init_callback)
.chain(self.constructors.iter().map(|f| &f.ffi_func))
.chain(self.methods.iter().map(|f| &f.ffi_func))
.chain(
self.uniffi_traits
.iter()
.flat_map(|ut| match ut {
UniffiTrait::Display { fmt: m }
| UniffiTrait::Debug { fmt: m }
| UniffiTrait::Hash { hash: m } => vec![m],
UniffiTrait::Eq { eq, ne } => vec![eq, ne],
})
.map(|m| &m.ffi_func),
)
}
pub fn derive_ffi_funcs(&mut self) -> Result<()> {
assert!(!self.ffi_func_clone.name().is_empty());
assert!(!self.ffi_func_free.name().is_empty());
self.ffi_func_clone.arguments = vec![FfiArgument {
name: "ptr".to_string(),
type_: FfiType::RustArcPtr(self.name.to_string()),
}];
self.ffi_func_clone.return_type = Some(FfiType::RustArcPtr(self.name.to_string()));
self.ffi_func_free.arguments = vec![FfiArgument {
name: "ptr".to_string(),
type_: FfiType::RustArcPtr(self.name.to_string()),
}];
self.ffi_func_free.return_type = None;
self.ffi_func_free.is_object_free_function = true;
if self.has_callback_interface() {
self.ffi_init_callback = Some(FfiFunction::callback_init(
&self.module_path,
&self.name,
callbacks::vtable_name(&self.name),
));
}
for cons in self.constructors.iter_mut() {
cons.derive_ffi_func();
}
for meth in self.methods.iter_mut() {
meth.derive_ffi_func()?;
}
for ut in self.uniffi_traits.iter_mut() {
ut.derive_ffi_func()?;
}
Ok(())
}
pub fn ffi_callbacks(&self) -> Vec<FfiCallbackFunction> {
if self.is_trait_interface() {
callbacks::ffi_callbacks(&self.name, &self.methods)
} else {
vec![]
}
}
pub fn vtable(&self) -> Option<FfiType> {
self.is_trait_interface()
.then(|| FfiType::Struct(callbacks::vtable_name(&self.name)))
}
pub fn vtable_definition(&self) -> Option<FfiStruct> {
self.is_trait_interface()
.then(|| callbacks::vtable_struct(&self.name, &self.methods))
}
pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> {
self.methods
.iter()
.enumerate()
.map(|(i, method)| {
(
callbacks::method_ffi_callback(&self.name, method, i),
method,
)
})
.collect()
}
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(
self.methods
.iter()
.map(Method::iter_types)
.chain(self.uniffi_traits.iter().map(UniffiTrait::iter_types))
.chain(self.constructors.iter().map(Constructor::iter_types))
.flatten(),
)
}
}
impl AsType for Object {
fn as_type(&self) -> Type {
Type::Object {
name: self.name.clone(),
module_path: self.module_path.clone(),
imp: self.imp,
}
}
}
impl From<uniffi_meta::ObjectMetadata> for Object {
fn from(meta: uniffi_meta::ObjectMetadata) -> Self {
let ffi_clone_name = meta.clone_ffi_symbol_name();
let ffi_free_name = meta.free_ffi_symbol_name();
Object {
module_path: meta.module_path,
name: meta.name,
imp: meta.imp,
constructors: Default::default(),
methods: Default::default(),
uniffi_traits: Default::default(),
ffi_func_clone: FfiFunction {
name: ffi_clone_name,
..Default::default()
},
ffi_func_free: FfiFunction {
name: ffi_free_name,
..Default::default()
},
ffi_init_callback: None,
docstring: meta.docstring.clone(),
}
}
}
impl From<uniffi_meta::UniffiTraitMetadata> for UniffiTrait {
fn from(meta: uniffi_meta::UniffiTraitMetadata) -> Self {
match meta {
uniffi_meta::UniffiTraitMetadata::Debug { fmt } => {
UniffiTrait::Debug { fmt: fmt.into() }
}
uniffi_meta::UniffiTraitMetadata::Display { fmt } => {
UniffiTrait::Display { fmt: fmt.into() }
}
uniffi_meta::UniffiTraitMetadata::Eq { eq, ne } => UniffiTrait::Eq {
eq: eq.into(),
ne: ne.into(),
},
uniffi_meta::UniffiTraitMetadata::Hash { hash } => {
UniffiTrait::Hash { hash: hash.into() }
}
}
}
}
#[derive(Debug, Clone, Checksum)]
pub struct Constructor {
pub(super) name: String,
pub(super) object_name: String,
pub(super) object_module_path: String,
pub(super) is_async: bool,
pub(super) arguments: Vec<Argument>,
#[checksum_ignore]
pub(super) ffi_func: FfiFunction,
#[checksum_ignore]
pub(super) docstring: Option<String>,
pub(super) throws: Option<Type>,
pub(super) checksum_fn_name: String,
#[checksum_ignore]
pub(super) checksum: Option<u16>,
}
impl Constructor {
pub fn name(&self) -> &str {
&self.name
}
pub fn arguments(&self) -> Vec<&Argument> {
self.arguments.iter().collect()
}
pub fn full_arguments(&self) -> Vec<Argument> {
self.arguments.to_vec()
}
pub fn ffi_func(&self) -> &FfiFunction {
&self.ffi_func
}
pub fn checksum_fn_name(&self) -> &str {
&self.checksum_fn_name
}
pub fn checksum(&self) -> u16 {
self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self))
}
pub fn throws(&self) -> bool {
self.throws.is_some()
}
pub fn throws_name(&self) -> Option<&str> {
super::throws_name(&self.throws)
}
pub fn throws_type(&self) -> Option<&Type> {
self.throws.as_ref()
}
pub fn docstring(&self) -> Option<&str> {
self.docstring.as_deref()
}
pub fn is_primary_constructor(&self) -> bool {
self.name == "new"
}
fn derive_ffi_func(&mut self) {
assert!(!self.ffi_func.name().is_empty());
self.ffi_func.init(
Some(FfiType::RustArcPtr(self.object_name.clone())),
self.arguments.iter().map(Into::into),
);
}
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(self.arguments.iter().flat_map(Argument::iter_types))
}
}
impl From<uniffi_meta::ConstructorMetadata> for Constructor {
fn from(meta: uniffi_meta::ConstructorMetadata) -> Self {
let ffi_name = meta.ffi_symbol_name();
let checksum_fn_name = meta.checksum_symbol_name();
let arguments = meta.inputs.into_iter().map(Into::into).collect();
let ffi_func = FfiFunction {
name: ffi_name,
is_async: meta.is_async,
..FfiFunction::default()
};
Self {
name: meta.name,
object_name: meta.self_name,
is_async: meta.is_async,
object_module_path: meta.module_path,
arguments,
ffi_func,
docstring: meta.docstring.clone(),
throws: meta.throws.map(Into::into),
checksum_fn_name,
checksum: meta.checksum,
}
}
}
#[derive(Debug, Clone, Checksum)]
pub struct Method {
pub(super) name: String,
pub(super) object_name: String,
pub(super) object_module_path: String,
pub(super) is_async: bool,
pub(super) object_impl: ObjectImpl,
pub(super) arguments: Vec<Argument>,
pub(super) return_type: Option<Type>,
#[checksum_ignore]
pub(super) ffi_func: FfiFunction,
#[checksum_ignore]
pub(super) docstring: Option<String>,
pub(super) throws: Option<Type>,
pub(super) takes_self_by_arc: bool,
pub(super) checksum_fn_name: String,
#[checksum_ignore]
pub(super) checksum: Option<u16>,
}
impl Method {
pub fn name(&self) -> &str {
&self.name
}
pub fn is_async(&self) -> bool {
self.is_async
}
pub fn arguments(&self) -> Vec<&Argument> {
self.arguments.iter().collect()
}
pub fn full_arguments(&self) -> Vec<Argument> {
vec![Argument {
name: "ptr".to_string(),
type_: Type::Object {
name: self.object_name.clone(),
module_path: self.object_module_path.clone(),
imp: self.object_impl,
},
by_ref: !self.takes_self_by_arc,
optional: false,
default: None,
}]
.into_iter()
.chain(self.arguments.iter().cloned())
.collect()
}
pub fn return_type(&self) -> Option<&Type> {
self.return_type.as_ref()
}
pub fn ffi_func(&self) -> &FfiFunction {
&self.ffi_func
}
pub fn checksum_fn_name(&self) -> &str {
&self.checksum_fn_name
}
pub fn checksum(&self) -> u16 {
self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self))
}
pub fn throws(&self) -> bool {
self.throws.is_some()
}
pub fn throws_name(&self) -> Option<&str> {
super::throws_name(&self.throws)
}
pub fn throws_type(&self) -> Option<&Type> {
self.throws.as_ref()
}
pub fn docstring(&self) -> Option<&str> {
self.docstring.as_deref()
}
pub fn takes_self_by_arc(&self) -> bool {
self.takes_self_by_arc
}
pub fn derive_ffi_func(&mut self) -> Result<()> {
assert!(!self.ffi_func.name().is_empty());
self.ffi_func.init(
self.return_type.as_ref().map(Into::into),
self.full_arguments().iter().map(Into::into),
);
Ok(())
}
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(
self.arguments
.iter()
.flat_map(Argument::iter_types)
.chain(self.return_type.iter().flat_map(Type::iter_types)),
)
}
pub fn foreign_future_ffi_result_struct(&self) -> FfiStruct {
callbacks::foreign_future_ffi_result_struct(self.return_type.as_ref().map(FfiType::from))
}
}
impl From<uniffi_meta::MethodMetadata> for Method {
fn from(meta: uniffi_meta::MethodMetadata) -> Self {
let ffi_name = meta.ffi_symbol_name();
let checksum_fn_name = meta.checksum_symbol_name();
let is_async = meta.is_async;
let return_type = meta.return_type.map(Into::into);
let arguments = meta.inputs.into_iter().map(Into::into).collect();
let ffi_func = FfiFunction {
name: ffi_name,
is_async,
..FfiFunction::default()
};
Self {
name: meta.name,
object_name: meta.self_name,
object_module_path: meta.module_path,
is_async,
object_impl: ObjectImpl::Struct, arguments,
return_type,
ffi_func,
docstring: meta.docstring.clone(),
throws: meta.throws.map(Into::into),
takes_self_by_arc: meta.takes_self_by_arc,
checksum_fn_name,
checksum: meta.checksum,
}
}
}
impl From<uniffi_meta::TraitMethodMetadata> for Method {
fn from(meta: uniffi_meta::TraitMethodMetadata) -> Self {
let ffi_name = meta.ffi_symbol_name();
let checksum_fn_name = meta.checksum_symbol_name();
let is_async = meta.is_async;
let return_type = meta.return_type.map(Into::into);
let arguments = meta.inputs.into_iter().map(Into::into).collect();
let ffi_func = FfiFunction {
name: ffi_name,
is_async,
..FfiFunction::default()
};
Self {
name: meta.name,
object_name: meta.trait_name,
object_module_path: meta.module_path,
is_async,
arguments,
return_type,
docstring: meta.docstring.clone(),
throws: meta.throws.map(Into::into),
takes_self_by_arc: meta.takes_self_by_arc,
checksum_fn_name,
checksum: meta.checksum,
ffi_func,
object_impl: ObjectImpl::Struct,
}
}
}
#[derive(Clone, Debug, Checksum)]
pub enum UniffiTrait {
Debug { fmt: Method },
Display { fmt: Method },
Eq { eq: Method, ne: Method },
Hash { hash: Method },
}
impl UniffiTrait {
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(
match self {
UniffiTrait::Display { fmt: m }
| UniffiTrait::Debug { fmt: m }
| UniffiTrait::Hash { hash: m } => vec![m.iter_types()],
UniffiTrait::Eq { eq, ne } => vec![eq.iter_types(), ne.iter_types()],
}
.into_iter()
.flatten(),
)
}
pub fn derive_ffi_func(&mut self) -> Result<()> {
match self {
UniffiTrait::Display { fmt: m }
| UniffiTrait::Debug { fmt: m }
| UniffiTrait::Hash { hash: m } => {
m.derive_ffi_func()?;
}
UniffiTrait::Eq { eq, ne } => {
eq.derive_ffi_func()?;
ne.derive_ffi_func()?;
}
}
Ok(())
}
}
impl Callable for Constructor {
fn arguments(&self) -> Vec<&Argument> {
self.arguments()
}
fn return_type(&self) -> Option<Type> {
Some(Type::Object {
name: self.object_name.clone(),
module_path: self.object_module_path.clone(),
imp: ObjectImpl::Struct,
})
}
fn throws_type(&self) -> Option<Type> {
self.throws_type().cloned()
}
fn is_async(&self) -> bool {
self.is_async
}
}
impl Callable for Method {
fn arguments(&self) -> Vec<&Argument> {
self.arguments()
}
fn return_type(&self) -> Option<Type> {
self.return_type().cloned()
}
fn throws_type(&self) -> Option<Type> {
self.throws_type().cloned()
}
fn is_async(&self) -> bool {
self.is_async
}
fn takes_self(&self) -> bool {
true
}
}
#[cfg(test)]
mod test {
use super::super::ComponentInterface;
use super::*;
#[test]
fn test_that_all_argument_and_return_types_become_known() {
const UDL: &str = r#"
namespace test{};
interface Testing {
constructor(string? name, u16 age);
sequence<u32> code_points_of_name();
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(ci.object_definitions().len(), 1);
ci.get_object_definition("Testing").unwrap();
assert_eq!(ci.iter_types().count(), 6);
assert!(ci.iter_types().any(|t| t == &Type::UInt16));
assert!(ci.iter_types().any(|t| t == &Type::UInt32));
assert!(ci.iter_types().any(|t| t
== &Type::Sequence {
inner_type: Box::new(Type::UInt32)
}));
assert!(ci.iter_types().any(|t| t == &Type::String));
assert!(ci.iter_types().any(|t| t
== &Type::Optional {
inner_type: Box::new(Type::String)
}));
assert!(ci
.iter_types()
.any(|t| matches!(t, Type::Object { name, ..} if name == "Testing")));
}
#[test]
fn test_alternate_constructors() {
const UDL: &str = r#"
namespace test{};
interface Testing {
constructor();
[Name=new_with_u32]
constructor(u32 v);
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(ci.object_definitions().len(), 1);
let obj = ci.get_object_definition("Testing").unwrap();
assert!(obj.primary_constructor().is_some());
assert_eq!(obj.alternate_constructors().len(), 1);
assert_eq!(obj.methods().len(), 0);
let cons = obj.primary_constructor().unwrap();
assert_eq!(cons.name(), "new");
assert_eq!(cons.arguments.len(), 0);
assert_eq!(cons.ffi_func.arguments.len(), 0);
let cons = obj.alternate_constructors()[0];
assert_eq!(cons.name(), "new_with_u32");
assert_eq!(cons.arguments.len(), 1);
assert_eq!(cons.ffi_func.arguments.len(), 1);
}
#[test]
fn test_the_name_new_identifies_the_primary_constructor() {
const UDL: &str = r#"
namespace test{};
interface Testing {
[Name=newish]
constructor();
[Name=new]
constructor(u32 v);
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(ci.object_definitions().len(), 1);
let obj = ci.get_object_definition("Testing").unwrap();
assert!(obj.primary_constructor().is_some());
assert_eq!(obj.alternate_constructors().len(), 1);
assert_eq!(obj.methods().len(), 0);
let cons = obj.primary_constructor().unwrap();
assert_eq!(cons.name(), "new");
assert_eq!(cons.arguments.len(), 1);
let cons = obj.alternate_constructors()[0];
assert_eq!(cons.name(), "newish");
assert_eq!(cons.arguments.len(), 0);
assert_eq!(cons.ffi_func.arguments.len(), 0);
}
#[test]
fn test_the_name_new_is_reserved_for_constructors() {
const UDL: &str = r#"
namespace test{};
interface Testing {
constructor();
void new(u32 v);
};
"#;
let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err();
assert_eq!(
err.to_string(),
"the method name \"new\" is reserved for the default constructor"
);
}
#[test]
fn test_duplicate_primary_constructors_not_allowed() {
const UDL: &str = r#"
namespace test{};
interface Testing {
constructor();
constructor(u32 v);
};
"#;
let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err();
assert_eq!(err.to_string(), "Duplicate interface member name: \"new\"");
const UDL2: &str = r#"
namespace test{};
interface Testing {
constructor();
[Name=new]
constructor(u32 v);
};
"#;
let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err();
assert_eq!(err.to_string(), "Duplicate interface member name: \"new\"");
}
#[test]
fn test_trait_attribute() {
const UDL: &str = r#"
namespace test{};
interface NotATrait {
};
[Trait]
interface ATrait {
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
let obj = ci.get_object_definition("NotATrait").unwrap();
assert_eq!(obj.imp.rust_name_for(&obj.name), "r#NotATrait");
let obj = ci.get_object_definition("ATrait").unwrap();
assert_eq!(obj.imp.rust_name_for(&obj.name), "dyn r#ATrait");
}
#[test]
fn test_trait_constructors_not_allowed() {
const UDL: &str = r#"
namespace test{};
[Trait]
interface Testing {
constructor();
};
"#;
let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err();
assert_eq!(
err.to_string(),
"Trait interfaces can not have constructors: \"new\""
);
}
#[test]
fn test_docstring_object() {
const UDL: &str = r#"
namespace test{};
/// informative docstring
interface Testing { };
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(
ci.get_object_definition("Testing")
.unwrap()
.docstring()
.unwrap(),
"informative docstring"
);
}
#[test]
fn test_docstring_constructor() {
const UDL: &str = r#"
namespace test{};
interface Testing {
/// informative docstring
constructor();
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(
ci.get_object_definition("Testing")
.unwrap()
.primary_constructor()
.unwrap()
.docstring()
.unwrap(),
"informative docstring"
);
}
#[test]
fn test_docstring_method() {
const UDL: &str = r#"
namespace test{};
interface Testing {
/// informative docstring
void testing();
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(
ci.get_object_definition("Testing")
.unwrap()
.get_method("testing")
.docstring()
.unwrap(),
"informative docstring"
);
}
}