use std::{
collections::{btree_map::Entry, BTreeMap, BTreeSet, HashSet},
iter,
};
use anyhow::{anyhow, bail, ensure, Context, Result};
pub mod universe;
pub use uniffi_meta::{AsType, EnumShape, ObjectImpl, Type};
use universe::{TypeIterator, TypeUniverse};
mod callbacks;
pub use callbacks::CallbackInterface;
mod enum_;
pub use enum_::{Enum, Variant};
mod function;
pub use function::{Argument, Callable, Function, ResultType};
mod object;
pub use object::{Constructor, Method, Object, UniffiTrait};
mod record;
pub use record::{Field, Record};
pub mod ffi;
mod visit_mut;
pub use ffi::{
FfiArgument, FfiCallbackFunction, FfiDefinition, FfiField, FfiFunction, FfiStruct, FfiType,
};
pub use uniffi_meta::Radix;
use uniffi_meta::{
ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata,
ObjectTraitImplMetadata, TraitMethodMetadata, UniffiTraitMetadata, UNIFFI_CONTRACT_VERSION,
};
pub type Literal = LiteralMetadata;
#[derive(Clone, Debug, Default)]
pub struct ComponentInterface {
pub(super) types: TypeUniverse,
enums: BTreeMap<String, Enum>,
records: BTreeMap<String, Record>,
functions: Vec<Function>,
objects: Vec<Object>,
callback_interfaces: Vec<CallbackInterface>,
errors: HashSet<String>,
callback_interface_throws_types: BTreeSet<Type>,
crate_to_namespace: BTreeMap<String, NamespaceMetadata>,
}
impl ComponentInterface {
pub fn new(crate_name: &str) -> Self {
assert!(!crate_name.is_empty());
Self {
types: TypeUniverse::new(NamespaceMetadata {
crate_name: crate_name.to_string(),
..Default::default()
}),
..Default::default()
}
}
pub fn from_webidl(idl: &str, module_path: &str) -> Result<Self> {
ensure!(
!module_path.is_empty(),
"you must specify a valid crate name"
);
let group = uniffi_udl::parse_udl(idl, module_path)?;
Self::from_metadata(group)
}
pub fn from_metadata(group: uniffi_meta::MetadataGroup) -> Result<Self> {
let mut ci = Self {
types: TypeUniverse::new(group.namespace.clone()),
..Default::default()
};
ci.add_metadata(group)?;
Ok(ci)
}
pub fn add_metadata(&mut self, group: uniffi_meta::MetadataGroup) -> Result<()> {
if self.types.namespace.name.is_empty() {
self.types.namespace = group.namespace.clone();
} else if self.types.namespace != group.namespace {
bail!(
"Namespace mismatch: {:?} - {:?}",
group.namespace,
self.types.namespace
);
}
if group.namespace_docstring.is_some() {
self.types.namespace_docstring = group.namespace_docstring.clone();
}
self.types.add_known_type(&Type::String)?;
crate::macro_metadata::add_group_to_ci(self, group)?;
Ok(())
}
pub fn set_crate_to_namespace_map(&mut self, namespaces: BTreeMap<String, NamespaceMetadata>) {
assert_eq!(
namespaces.get(self.crate_name()).unwrap().name,
self.namespace()
);
self.crate_to_namespace = namespaces
}
pub fn namespace(&self) -> &str {
&self.types.namespace.name
}
pub fn namespace_docstring(&self) -> Option<&str> {
self.types.namespace_docstring.as_deref()
}
pub fn crate_name(&self) -> &str {
&self.types.namespace.crate_name
}
pub fn uniffi_contract_version(&self) -> u32 {
let force_version = std::env::var("UNIFFI_FORCE_CONTRACT_VERSION");
match force_version {
Ok(v) if !v.is_empty() => v.parse().unwrap(),
_ => UNIFFI_CONTRACT_VERSION,
}
}
pub fn enum_definitions(&self) -> impl Iterator<Item = &Enum> {
self.enums.values()
}
pub fn get_enum_definition(&self, name: &str) -> Option<&Enum> {
self.enums.get(name)
}
pub fn record_definitions(&self) -> impl Iterator<Item = &Record> {
self.records.values()
}
pub fn get_record_definition(&self, name: &str) -> Option<&Record> {
self.records.get(name)
}
pub fn function_definitions(&self) -> &[Function] {
&self.functions
}
pub fn get_function_definition(&self, name: &str) -> Option<&Function> {
self.functions.iter().find(|f| f.name == name)
}
pub fn object_definitions(&self) -> &[Object] {
&self.objects
}
pub fn get_object_definition(&self, name: &str) -> Option<&Object> {
self.objects.iter().find(|o| o.name == name)
}
fn callback_interface_callback_definitions(
&self,
) -> impl IntoIterator<Item = FfiCallbackFunction> + '_ {
self.callback_interfaces
.iter()
.flat_map(|cbi| cbi.ffi_callbacks())
.chain(self.objects.iter().flat_map(|o| o.ffi_callbacks()))
}
fn callback_interface_vtable_definitions(&self) -> impl IntoIterator<Item = FfiStruct> + '_ {
self.callback_interface_definitions()
.iter()
.map(|cbi| cbi.vtable_definition())
.chain(
self.object_definitions()
.iter()
.flat_map(|o| o.vtable_definition()),
)
}
pub fn callback_interface_definitions(&self) -> &[CallbackInterface] {
&self.callback_interfaces
}
pub fn get_callback_interface_definition(&self, name: &str) -> Option<&CallbackInterface> {
self.callback_interfaces.iter().find(|o| o.name == name)
}
pub fn has_async_callback_interface_definition(&self) -> bool {
self.callback_interfaces
.iter()
.any(|cbi| cbi.has_async_method())
|| self
.objects
.iter()
.any(|o| o.has_callback_interface() && o.has_async_method())
}
pub fn iter_callables(&self) -> impl Iterator<Item = &dyn Callable> {
#[allow(trivial_casts)]
self.function_definitions()
.iter()
.map(|f| f as &dyn Callable)
.chain(self.objects.iter().flat_map(|o| {
o.constructors()
.into_iter()
.map(|c| c as &dyn Callable)
.chain(o.methods().into_iter().map(|m| m as &dyn Callable))
}))
}
pub fn should_generate_error_read(&self, e: &Enum) -> bool {
let fielded = !e.is_flat();
let used_in_foreign_interface = self
.callback_interface_definitions()
.iter()
.flat_map(|cb| cb.methods())
.chain(
self.object_definitions()
.iter()
.filter(|o| o.has_callback_interface())
.flat_map(|o| o.methods()),
)
.any(|m| m.throws_type() == Some(&e.as_type()));
self.is_name_used_as_error(&e.name) && (fielded || used_in_foreign_interface)
}
pub fn iter_local_types(&self) -> impl Iterator<Item = &Type> {
self.types.iter_local_types()
}
pub fn iter_external_types(&self) -> impl Iterator<Item = &Type> {
self.types.iter_external_types()
}
pub fn filter_local_types<'a>(
&'a self,
types: impl Iterator<Item = &'a Type>,
) -> impl Iterator<Item = &'a Type> {
self.types.filter_local_types(types)
}
pub fn is_external(&self, t: &Type) -> bool {
self.types.is_external(t)
}
pub fn get_type(&self, name: &str) -> Option<Type> {
self.types.get_type_definition(name)
}
pub fn namespace_for_type(&self, ty: &Type) -> Result<&str> {
let mod_path = ty
.module_path()
.ok_or_else(|| anyhow!("type {ty:?} has no module path"))?;
self.namespace_for_module_path(mod_path)
}
pub fn namespace_for_module_path(&self, module_path: &str) -> Result<&str> {
self.crate_to_namespace
.get(module_path)
.map(|n| n.name.as_ref())
.or_else(|| (module_path == self.crate_name()).then(|| self.namespace()))
.ok_or_else(|| anyhow!("unresolved module path {module_path}"))
}
fn iter_types_in_item<'a>(&'a self, item: &'a Type) -> impl Iterator<Item = &'a Type> + 'a {
RecursiveTypeIterator::new(self, item)
}
pub fn item_contains_object_references(&self, item: &Type) -> bool {
self.iter_types_in_item(item)
.any(|t| matches!(t, Type::Object { .. }))
}
pub fn item_contains_unsigned_types(&self, item: &Type) -> bool {
self.iter_types_in_item(item)
.any(|t| matches!(t, Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64))
}
pub fn contains_optional_types(&self) -> bool {
self.types
.iter_local_types()
.any(|t| matches!(t, Type::Optional { .. }))
}
pub fn contains_sequence_types(&self) -> bool {
self.types
.iter_local_types()
.any(|t| matches!(t, Type::Sequence { .. }))
}
pub fn contains_map_types(&self) -> bool {
self.types
.iter_local_types()
.any(|t| matches!(t, Type::Map { .. }))
}
pub fn contains_object_types(&self) -> bool {
self.types
.iter_local_types()
.any(|t| matches!(t, Type::Object { .. }))
}
fn ffi_namespace(&self) -> &str {
&self.types.namespace.crate_name
}
pub fn ffi_uniffi_contract_version(&self) -> FfiFunction {
FfiFunction {
name: format!("ffi_{}_uniffi_contract_version", self.ffi_namespace()),
is_async: false,
arguments: vec![],
return_type: Some(FfiType::UInt32),
has_rust_call_status_arg: false,
is_object_free_function: false,
}
}
pub fn ffi_rustbuffer_alloc(&self) -> FfiFunction {
FfiFunction {
name: format!("ffi_{}_rustbuffer_alloc", self.ffi_namespace()),
is_async: false,
arguments: vec![FfiArgument {
name: "size".to_string(),
type_: FfiType::UInt64,
}],
return_type: Some(FfiType::RustBuffer(None)),
has_rust_call_status_arg: true,
is_object_free_function: false,
}
}
pub fn ffi_rustbuffer_from_bytes(&self) -> FfiFunction {
FfiFunction {
name: format!("ffi_{}_rustbuffer_from_bytes", self.ffi_namespace()),
is_async: false,
arguments: vec![FfiArgument {
name: "bytes".to_string(),
type_: FfiType::ForeignBytes,
}],
return_type: Some(FfiType::RustBuffer(None)),
has_rust_call_status_arg: true,
is_object_free_function: false,
}
}
pub fn ffi_rustbuffer_free(&self) -> FfiFunction {
FfiFunction {
name: format!("ffi_{}_rustbuffer_free", self.ffi_namespace()),
is_async: false,
arguments: vec![FfiArgument {
name: "buf".to_string(),
type_: FfiType::RustBuffer(None),
}],
return_type: None,
has_rust_call_status_arg: true,
is_object_free_function: false,
}
}
pub fn ffi_rustbuffer_reserve(&self) -> FfiFunction {
FfiFunction {
name: format!("ffi_{}_rustbuffer_reserve", self.ffi_namespace()),
is_async: false,
arguments: vec![
FfiArgument {
name: "buf".to_string(),
type_: FfiType::RustBuffer(None),
},
FfiArgument {
name: "additional".to_string(),
type_: FfiType::UInt64,
},
],
return_type: Some(FfiType::RustBuffer(None)),
has_rust_call_status_arg: true,
is_object_free_function: false,
}
}
pub fn ffi_rust_future_poll(&self, return_ffi_type: Option<FfiType>) -> FfiFunction {
FfiFunction {
name: self.rust_future_ffi_fn_name("rust_future_poll", return_ffi_type),
is_async: false,
arguments: vec![
FfiArgument {
name: "handle".to_owned(),
type_: FfiType::Handle,
},
FfiArgument {
name: "callback".to_owned(),
type_: FfiType::Callback("RustFutureContinuationCallback".to_owned()),
},
FfiArgument {
name: "callback_data".to_owned(),
type_: FfiType::Handle,
},
],
return_type: None,
has_rust_call_status_arg: false,
is_object_free_function: false,
}
}
pub fn ffi_rust_future_complete(&self, return_ffi_type: Option<FfiType>) -> FfiFunction {
FfiFunction {
name: self.rust_future_ffi_fn_name("rust_future_complete", return_ffi_type.clone()),
is_async: false,
arguments: vec![FfiArgument {
name: "handle".to_owned(),
type_: FfiType::Handle,
}],
return_type: return_ffi_type,
has_rust_call_status_arg: true,
is_object_free_function: false,
}
}
pub fn ffi_rust_future_cancel(&self, return_ffi_type: Option<FfiType>) -> FfiFunction {
FfiFunction {
name: self.rust_future_ffi_fn_name("rust_future_cancel", return_ffi_type),
is_async: false,
arguments: vec![FfiArgument {
name: "handle".to_owned(),
type_: FfiType::Handle,
}],
return_type: None,
has_rust_call_status_arg: false,
is_object_free_function: false,
}
}
pub fn ffi_rust_future_free(&self, return_ffi_type: Option<FfiType>) -> FfiFunction {
FfiFunction {
name: self.rust_future_ffi_fn_name("rust_future_free", return_ffi_type),
is_async: false,
arguments: vec![FfiArgument {
name: "handle".to_owned(),
type_: FfiType::Handle,
}],
return_type: None,
has_rust_call_status_arg: false,
is_object_free_function: false,
}
}
fn rust_future_ffi_fn_name(&self, base_name: &str, return_ffi_type: Option<FfiType>) -> String {
let namespace = self.ffi_namespace();
let return_type_name = FfiType::return_type_name(return_ffi_type.as_ref());
format!("ffi_{namespace}_{base_name}_{return_type_name}")
}
pub fn has_async_fns(&self) -> bool {
self.iter_ffi_function_definitions().any(|f| f.is_async())
|| self
.callback_interfaces
.iter()
.any(CallbackInterface::has_async_method)
}
pub fn iter_future_callback_params(&self) -> impl Iterator<Item = FfiType> {
let unique_results = self
.iter_callables()
.map(|c| c.result_type().future_callback_param())
.collect::<BTreeSet<_>>();
unique_results.into_iter()
}
pub fn iter_async_result_types(&self) -> impl Iterator<Item = ResultType<'_>> {
let unique_results = self
.iter_callables()
.map(|c| c.result_type())
.collect::<BTreeSet<_>>();
unique_results.into_iter()
}
pub fn ffi_definitions(&self) -> impl Iterator<Item = FfiDefinition> + '_ {
self.builtin_ffi_definitions()
.into_iter()
.chain(
self.callback_interface_callback_definitions()
.into_iter()
.map(Into::into),
)
.chain(
self.callback_interface_vtable_definitions()
.into_iter()
.map(Into::into),
)
.chain(self.iter_ffi_function_definitions().map(Into::into))
}
fn builtin_ffi_definitions(&self) -> impl IntoIterator<Item = FfiDefinition> + '_ {
[
FfiCallbackFunction {
name: "RustFutureContinuationCallback".to_owned(),
arguments: vec![
FfiArgument::new("data", FfiType::UInt64),
FfiArgument::new("poll_result", FfiType::Int8),
],
return_type: None,
has_rust_call_status_arg: false,
}
.into(),
FfiCallbackFunction {
name: "ForeignFutureFree".to_owned(),
arguments: vec![FfiArgument::new("handle", FfiType::UInt64)],
return_type: None,
has_rust_call_status_arg: false,
}
.into(),
FfiCallbackFunction {
name: "CallbackInterfaceFree".to_owned(),
arguments: vec![FfiArgument::new("handle", FfiType::UInt64)],
return_type: None,
has_rust_call_status_arg: false,
}
.into(),
FfiStruct {
name: "ForeignFuture".to_owned(),
fields: vec![
FfiField::new("handle", FfiType::UInt64),
FfiField::new("free", FfiType::Callback("ForeignFutureFree".to_owned())),
],
}
.into(),
]
.into_iter()
.chain(
self.all_possible_return_ffi_types()
.flat_map(|return_type| {
[
callbacks::foreign_future_ffi_result_struct(return_type.clone()).into(),
callbacks::ffi_foreign_future_complete(return_type).into(),
]
}),
)
}
pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ {
self.iter_ffi_function_definitions_conditionally_include_integrity_checks(true)
}
pub fn iter_ffi_function_definitions_excluding_integrity_checks(
&self,
) -> impl Iterator<Item = FfiFunction> + '_ {
self.iter_ffi_function_definitions_conditionally_include_integrity_checks(false)
}
fn iter_ffi_function_definitions_conditionally_include_integrity_checks(
&self,
include_checksums: bool,
) -> impl Iterator<Item = FfiFunction> + '_ {
let iterator = self
.iter_user_ffi_function_definitions()
.cloned()
.chain(self.iter_rust_buffer_ffi_function_definitions())
.chain(self.iter_futures_ffi_function_definitions());
if include_checksums {
Box::new(iterator.chain(self.iter_ffi_function_integrity_checks()))
as Box<dyn Iterator<Item = FfiFunction> + '_>
} else {
Box::new(iterator) as Box<dyn Iterator<Item = FfiFunction> + '_>
}
}
pub fn iter_ffi_function_integrity_checks(&self) -> impl Iterator<Item = FfiFunction> + '_ {
iter::empty()
.chain(self.iter_checksum_ffi_functions())
.chain([self.ffi_uniffi_contract_version()])
}
pub fn iter_ffi_function_definitions_non_async(
&self,
) -> impl Iterator<Item = FfiFunction> + '_ {
self.iter_user_ffi_function_definitions()
.cloned()
.chain(self.iter_rust_buffer_ffi_function_definitions())
.chain(self.iter_ffi_function_integrity_checks())
}
pub fn iter_user_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> + '_ {
iter::empty()
.chain(
self.objects
.iter()
.flat_map(|obj| obj.iter_ffi_function_definitions()),
)
.chain(
self.callback_interfaces
.iter()
.map(|cb| cb.ffi_init_callback()),
)
.chain(self.functions.iter().map(|f| &f.ffi_func))
}
pub fn iter_rust_buffer_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> {
[
self.ffi_rustbuffer_alloc(),
self.ffi_rustbuffer_from_bytes(),
self.ffi_rustbuffer_free(),
self.ffi_rustbuffer_reserve(),
]
.into_iter()
}
fn all_possible_return_ffi_types(&self) -> impl Iterator<Item = Option<FfiType>> {
[
Some(FfiType::UInt8),
Some(FfiType::Int8),
Some(FfiType::UInt16),
Some(FfiType::Int16),
Some(FfiType::UInt32),
Some(FfiType::Int32),
Some(FfiType::UInt64),
Some(FfiType::Int64),
Some(FfiType::Float32),
Some(FfiType::Float64),
Some(FfiType::RustArcPtr("".to_owned())),
Some(FfiType::RustBuffer(None)),
None,
]
.into_iter()
}
pub fn iter_futures_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ {
self.all_possible_return_ffi_types()
.flat_map(|return_type| {
[
self.ffi_rust_future_poll(return_type.clone()),
self.ffi_rust_future_cancel(return_type.clone()),
self.ffi_rust_future_free(return_type.clone()),
self.ffi_rust_future_complete(return_type),
]
})
}
pub fn iter_checksums(&self) -> impl Iterator<Item = (String, u16)> + '_ {
let func_checksums = self
.functions
.iter()
.map(|f| (f.checksum_fn_name(), f.checksum()));
let method_checksums = self.objects.iter().flat_map(|o| {
o.methods()
.into_iter()
.map(|m| (m.checksum_fn_name(), m.checksum()))
});
let constructor_checksums = self.objects.iter().flat_map(|o| {
o.constructors()
.into_iter()
.map(|c| (c.checksum_fn_name(), c.checksum()))
});
let callback_method_checksums = self.callback_interfaces.iter().flat_map(|cbi| {
cbi.methods().into_iter().filter_map(|m| {
if m.checksum_fn_name().is_empty() {
None
} else {
Some((m.checksum_fn_name(), m.checksum()))
}
})
});
func_checksums
.chain(method_checksums)
.chain(constructor_checksums)
.chain(callback_method_checksums)
.map(|(fn_name, checksum)| (fn_name.to_string(), checksum))
}
pub fn iter_checksum_ffi_functions(&self) -> impl Iterator<Item = FfiFunction> + '_ {
self.iter_checksums().map(|(name, _)| FfiFunction {
name,
is_async: false,
arguments: vec![],
return_type: Some(FfiType::UInt16),
has_rust_call_status_arg: false,
is_object_free_function: false,
})
}
pub(super) fn add_enum_definition(&mut self, defn: Enum) -> Result<()> {
match self.enums.entry(defn.name().to_owned()) {
Entry::Vacant(v) => {
if matches!(defn.shape, EnumShape::Error { .. }) {
self.errors.insert(defn.name.clone());
}
self.types
.add_known_types(defn.iter_types())
.with_context(|| format!("adding enum {defn:?}"))?;
v.insert(defn);
}
Entry::Occupied(o) => {
let existing_def = o.get();
if defn != *existing_def {
bail!(
"Mismatching definition for enum `{}`!\n\
existing definition: {existing_def:#?},\n\
new definition: {defn:#?}",
defn.name(),
);
}
}
}
Ok(())
}
pub(super) fn add_record_definition(&mut self, defn: Record) -> Result<()> {
match self.records.entry(defn.name().to_owned()) {
Entry::Vacant(v) => {
self.types
.add_known_types(defn.iter_types())
.with_context(|| format!("adding record {defn:?}"))?;
v.insert(defn);
}
Entry::Occupied(o) => {
let existing_def = o.get();
if defn != *existing_def {
bail!(
"Mismatching definition for record `{}`!\n\
existing definition: {existing_def:#?},\n\
new definition: {defn:#?}",
defn.name(),
);
}
}
}
Ok(())
}
pub(super) fn add_function_definition(&mut self, defn: Function) -> Result<()> {
if self.functions.iter().any(|f| f.name == defn.name) {
bail!("duplicate function definition: \"{}\"", defn.name);
}
if self.types.get_type_definition(defn.name()).is_some() {
bail!("Conflicting type definition for \"{}\"", defn.name());
}
self.types
.add_known_types(defn.iter_types())
.with_context(|| format!("adding function {defn:?}"))?;
defn.throws_name()
.map(|n| self.errors.insert(n.to_string()));
self.functions.push(defn);
Ok(())
}
pub(super) fn add_constructor_meta(&mut self, meta: ConstructorMetadata) -> Result<()> {
let object = get_object(&mut self.objects, &meta.self_name)
.ok_or_else(|| anyhow!("add_constructor_meta: object {} not found", &meta.self_name))?;
let defn: Constructor = meta.into();
self.types
.add_known_types(defn.iter_types())
.with_context(|| format!("adding constructor {defn:?}"))?;
defn.throws_name()
.map(|n| self.errors.insert(n.to_string()));
object.constructors.push(defn);
Ok(())
}
pub(super) fn add_method_meta(&mut self, meta: impl Into<Method>) -> Result<()> {
let mut method: Method = meta.into();
let object = get_object(&mut self.objects, &method.object_name)
.ok_or_else(|| anyhow!("add_method_meta: object {} not found", &method.object_name))?;
self.types
.add_known_types(method.iter_types())
.with_context(|| format!("adding method {method:?}"))?;
method
.throws_name()
.map(|n| self.errors.insert(n.to_string()));
method.object_impl = object.imp;
object.methods.push(method);
Ok(())
}
pub(super) fn add_uniffitrait_meta(&mut self, meta: UniffiTraitMetadata) -> Result<()> {
let object = get_object(&mut self.objects, meta.self_name())
.ok_or_else(|| anyhow!("add_uniffitrait_meta: object not found"))?;
let ut: UniffiTrait = meta.into();
self.types
.add_known_types(ut.iter_types())
.with_context(|| format!("adding builtin trait {ut:?}"))?;
object.uniffi_traits.push(ut);
Ok(())
}
pub(super) fn add_object_meta(&mut self, meta: ObjectMetadata) -> Result<()> {
self.add_object_definition(meta.into())
}
fn add_object_definition(&mut self, defn: Object) -> Result<()> {
self.types.add_known_type(&defn.as_type())?;
self.types
.add_known_types(defn.iter_types())
.with_context(|| format!("adding object {defn:?}'"))?;
self.objects.push(defn);
Ok(())
}
pub fn is_name_used_as_error(&self, name: &str) -> bool {
self.errors.contains(name)
}
pub(super) fn add_callback_interface_definition(
&mut self,
defn: CallbackInterface,
) -> Result<()> {
self.types.add_known_type(&defn.as_type())?;
self.types
.add_known_types(defn.iter_types())
.with_context(|| format!("adding callback {defn:?}'"))?;
self.callback_interfaces.push(defn);
Ok(())
}
pub(super) fn add_trait_method_meta(&mut self, meta: TraitMethodMetadata) -> Result<()> {
if let Some(cbi) = get_callback_interface(&mut self.callback_interfaces, &meta.trait_name) {
if cbi.methods.len() != meta.index as usize {
bail!(
"UniFFI internal error: callback interface method index mismatch for {}::{} (expected {}, saw {})",
meta.trait_name,
meta.name,
cbi.methods.len(),
meta.index,
);
}
let method: Method = meta.into();
if let Some(error) = method.throws_type() {
self.callback_interface_throws_types.insert(error.clone());
}
self.types
.add_known_types(method.iter_types())
.with_context(|| format!("adding trait method {method:?}"))?;
method
.throws_name()
.map(|n| self.errors.insert(n.to_string()));
cbi.methods.push(method);
} else {
self.add_method_meta(meta)?;
}
Ok(())
}
pub(super) fn add_object_trait_impl(
&mut self,
trait_impl: ObjectTraitImplMetadata,
) -> Result<()> {
let object = trait_impl
.ty
.name()
.and_then(|n| get_object(&mut self.objects, n))
.ok_or_else(|| {
anyhow!(
"add_object_trait_impl: object {:?} not found",
&trait_impl.ty
)
})?;
object.trait_impls.push(trait_impl);
Ok(())
}
pub fn check_consistency(&self) -> Result<()> {
if self.namespace().is_empty() {
bail!("missing namespace definition");
}
for f in self.functions.iter() {
if self.types.get_type_definition(f.name()).is_some() {
bail!("Conflicting type definition for \"{}\"", f.name());
}
}
Ok(())
}
pub fn derive_ffi_funcs(&mut self) -> Result<()> {
for func in self.functions.iter_mut() {
func.derive_ffi_func()?;
}
for obj in self.objects.iter_mut() {
obj.derive_ffi_funcs()?;
}
for callback in self.callback_interfaces.iter_mut() {
callback.derive_ffi_funcs();
}
Ok(())
}
}
fn get_object<'a>(objects: &'a mut [Object], name: &str) -> Option<&'a mut Object> {
objects.iter_mut().find(|o| o.name == name)
}
fn get_callback_interface<'a>(
callback_interfaces: &'a mut [CallbackInterface],
name: &str,
) -> Option<&'a mut CallbackInterface> {
callback_interfaces.iter_mut().find(|o| o.name == name)
}
struct RecursiveTypeIterator<'a> {
ci: &'a ComponentInterface,
current: TypeIterator<'a>,
seen: HashSet<&'a str>,
pending: Vec<&'a Type>,
}
impl<'a> RecursiveTypeIterator<'a> {
fn new(ci: &'a ComponentInterface, item: &'a Type) -> RecursiveTypeIterator<'a> {
RecursiveTypeIterator {
ci,
current: item.iter_types(),
seen: Default::default(),
pending: Default::default(),
}
}
fn add_pending_type(&mut self, type_: &'a Type) {
match type_ {
Type::Record { name, .. }
| Type::Enum { name, .. }
| Type::Object { name, .. }
| Type::CallbackInterface { name, .. } => {
if !self.seen.contains(name.as_str()) {
self.pending.push(type_);
self.seen.insert(name.as_str());
}
}
_ => (),
}
}
fn advance_to_next_type(&mut self) -> Option<&'a Type> {
if let Some(next_type) = self.pending.pop() {
let next_iter = match next_type {
Type::Record { name, .. } => {
self.ci.get_record_definition(name).map(Record::iter_types)
}
Type::Enum { name, .. } => self.ci.get_enum_definition(name).map(Enum::iter_types),
Type::Object { name, .. } => {
self.ci.get_object_definition(name).map(Object::iter_types)
}
Type::CallbackInterface { name, .. } => self
.ci
.get_callback_interface_definition(name)
.map(CallbackInterface::iter_types),
_ => None,
};
if let Some(next_iter) = next_iter {
self.current = next_iter;
}
self.next()
} else {
None
}
}
}
impl<'a> Iterator for RecursiveTypeIterator<'a> {
type Item = &'a Type;
fn next(&mut self) -> Option<Self::Item> {
if let Some(type_) = self.current.next() {
self.add_pending_type(type_);
Some(type_)
} else {
self.advance_to_next_type()
}
}
}
fn throws_name(throws: &Option<Type>) -> Option<&str> {
match throws {
None => None,
Some(Type::Enum { name, .. }) | Some(Type::Object { name, .. }) => Some(name),
_ => panic!("unknown throw type: {throws:?}"),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_duplicate_type_names_are_an_error() {
const UDL: &str = r#"
namespace test{};
interface Testing {
constructor();
};
dictionary Testing {
u32 field;
};
"#;
let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err();
assert_eq!(
err.to_string(),
"Conflicting type definition for `Testing`! \
existing definition: Object { module_path: \"crate_name\", name: \"Testing\", imp: Struct }, \
new definition: Record { module_path: \"crate_name\", name: \"Testing\" }"
);
const UDL2: &str = r#"
namespace test{};
enum Testing {
"one", "two"
};
[Error]
enum Testing { "three", "four" };
"#;
let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err();
assert_eq!(
err.to_string(),
"Mismatching definition for enum `Testing`!
existing definition: Enum {
name: \"Testing\",
module_path: \"crate_name\",
remote: false,
discr_type: None,
variants: [
Variant {
name: \"one\",
discr: None,
fields: [],
docstring: None,
},
Variant {
name: \"two\",
discr: None,
fields: [],
docstring: None,
},
],
shape: Enum,
non_exhaustive: false,
docstring: None,
},
new definition: Enum {
name: \"Testing\",
module_path: \"crate_name\",
remote: false,
discr_type: None,
variants: [
Variant {
name: \"three\",
discr: None,
fields: [],
docstring: None,
},
Variant {
name: \"four\",
discr: None,
fields: [],
docstring: None,
},
],
shape: Error {
flat: true,
},
non_exhaustive: false,
docstring: None,
}",
);
const UDL3: &str = r#"
namespace test{
u32 Testing();
};
enum Testing {
"one", "two"
};
"#;
let err = ComponentInterface::from_webidl(UDL3, "crate_name").unwrap_err();
assert!(format!("{err:#}").contains("Conflicting type definition for \"Testing\""));
}
#[test]
fn test_contains_optional_types() {
let mut ci = ComponentInterface::default();
assert!(!ci.contains_optional_types());
assert!(ci
.types
.add_known_type(&Type::Optional {
inner_type: Box::new(Type::String)
})
.is_ok());
assert!(ci.contains_optional_types());
}
#[test]
fn test_contains_sequence_types() {
let mut ci = ComponentInterface {
..Default::default()
};
assert!(!ci.contains_sequence_types());
assert!(ci
.types
.add_known_type(&Type::Sequence {
inner_type: Box::new(Type::UInt64)
})
.is_ok());
assert!(ci.contains_sequence_types());
assert!(ci.types.contains(&Type::UInt64));
}
#[test]
fn test_contains_map_types() {
let mut ci = ComponentInterface {
..Default::default()
};
assert!(!ci.contains_map_types());
assert!(ci
.types
.add_known_type(&Type::Map {
key_type: Box::new(Type::String),
value_type: Box::new(Type::Boolean)
})
.is_ok());
assert!(ci.contains_map_types());
assert!(ci.types.contains(&Type::String));
assert!(ci.types.contains(&Type::Boolean));
}
#[test]
fn test_no_infinite_recursion_when_walking_types() {
const UDL: &str = r#"
namespace test{};
interface Testing {
void tester(Testing foo);
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert!(!ci.item_contains_unsigned_types(&Type::Object {
name: "Testing".into(),
module_path: "".into(),
imp: ObjectImpl::Struct,
}));
}
#[test]
fn test_correct_recursion_when_walking_types() {
const UDL: &str = r#"
namespace test{};
interface TestObj {
void tester(TestRecord foo);
};
dictionary TestRecord {
NestedRecord bar;
};
dictionary NestedRecord {
u64 baz;
};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert!(ci.item_contains_unsigned_types(&Type::Object {
name: "TestObj".into(),
module_path: "".into(),
imp: ObjectImpl::Struct,
}));
}
#[test]
fn test_docstring_namespace() {
const UDL: &str = r#"
/// informative docstring
namespace test{};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(ci.namespace_docstring().unwrap(), "informative docstring");
}
#[test]
fn test_multiline_docstring() {
const UDL: &str = r#"
/// informative
/// docstring
namespace test{};
"#;
let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
assert_eq!(ci.namespace_docstring().unwrap(), "informative\ndocstring");
}
#[test]
fn test_names() {
let mut ci = ComponentInterface::default();
let ob = Object {
name: "ob".to_string(),
module_path: "mp".to_string(),
imp: ObjectImpl::Struct,
remote: false,
constructors: Default::default(),
methods: Default::default(),
uniffi_traits: Default::default(),
ffi_func_clone: Default::default(),
trait_impls: Default::default(),
ffi_func_free: Default::default(),
ffi_init_callback: Default::default(),
docstring: Default::default(),
};
ci.add_object_definition(ob).unwrap();
assert!(ci.get_object_definition("ob").is_some());
assert_eq!(
ci.types.get_type_definition("ob"),
Some(Type::Object {
module_path: "mp".to_string(),
name: "ob".to_string(),
imp: ObjectImpl::Struct
})
);
let cb = CallbackInterface {
name: "cb".to_string(),
module_path: "mp".to_string(),
methods: vec![],
ffi_init_callback: FfiFunction::default(),
docstring: None,
};
ci.add_callback_interface_definition(cb).unwrap();
assert_eq!(
ci.types.get_type_definition("cb"),
Some(Type::CallbackInterface {
module_path: "mp".to_string(),
name: "cb".to_string()
})
);
assert!(ci.get_callback_interface_definition("cb").is_some());
}
#[test]
fn test_namespaces() {
let mut ci = ComponentInterface::new("crate");
ci.types.namespace.name = "ns".to_string();
assert_eq!(ci.namespace_for_module_path("crate").unwrap(), "ns");
assert!(ci.namespace_for_module_path("oops").is_err());
}
}