use anyhow::{anyhow, bail, Result};
use std::collections::{btree_map::Entry, BTreeMap};
use super::*;
#[derive(Default)]
pub struct UniffiMetaConverter {
module_path_map: BTreeMap<String, String>,
namespaces: BTreeMap<String, Namespace>,
module_docstrings: BTreeMap<String, String>,
module_toml: BTreeMap<String, toml::Table>,
functions: BTreeMap<String, BTreeMap<String, uniffi_meta::FnMetadata>>,
records: BTreeMap<String, BTreeMap<String, uniffi_meta::RecordMetadata>>,
callback_interfaces: BTreeMap<String, BTreeMap<String, uniffi_meta::CallbackInterfaceMetadata>>,
enums: BTreeMap<String, BTreeMap<String, uniffi_meta::EnumMetadata>>,
custom_types: BTreeMap<String, BTreeMap<String, uniffi_meta::CustomTypeMetadata>>,
interfaces: BTreeMap<String, BTreeMap<String, uniffi_meta::ObjectMetadata>>,
constructors: BTreeMap<(String, String), BTreeMap<String, uniffi_meta::ConstructorMetadata>>,
methods: BTreeMap<(String, String), BTreeMap<String, uniffi_meta::MethodMetadata>>,
trait_methods: BTreeMap<(String, String), BTreeMap<String, uniffi_meta::TraitMethodMetadata>>,
uniffi_traits: BTreeMap<(String, String), BTreeMap<String, uniffi_meta::UniffiTraitMetadata>>,
trait_impls: BTreeMap<
(String, String),
BTreeMap<uniffi_meta::Type, uniffi_meta::ObjectTraitImplMetadata>,
>,
orig_names: BTreeMap<(String, String), String>,
}
trait InsertUnique<K, V> {
fn insert_unique(&mut self, k: K, v: V) -> Result<()>;
}
impl<K, V> InsertUnique<K, V> for BTreeMap<K, V>
where
K: std::fmt::Debug + Ord,
V: std::fmt::Debug + PartialEq,
{
fn insert_unique(&mut self, k: K, v: V) -> Result<()> {
match self.entry(k) {
Entry::Vacant(e) => {
e.insert(v);
Ok(())
}
Entry::Occupied(e) => {
if e.get() != &v {
bail!(
"Conflicting metadata types:\nold: {:?}\nnew: {v:?}",
e.get()
);
}
Ok(())
}
}
}
}
impl UniffiMetaConverter {
pub fn add_metadata_item(&mut self, meta: uniffi_meta::Metadata) -> Result<()> {
match meta {
uniffi_meta::Metadata::Namespace(namespace) => {
self.module_path_map
.insert(namespace.crate_name.clone(), namespace.name.clone());
self.namespaces.insert_unique(
namespace.name.clone(),
Namespace {
crate_name: namespace.crate_name,
docstring: None,
config_toml: None,
name: namespace.name,
functions: vec![],
type_definitions: vec![],
},
)?;
}
uniffi_meta::Metadata::Func(func) => {
self.update_orig_names(&func.module_path, &func.orig_name, &func.name);
self.functions
.entry(module_path_to_crate_name(&func.module_path))
.or_default()
.insert_unique(func.name.clone(), func)?;
}
uniffi_meta::Metadata::Record(rec) => {
self.update_orig_names(&rec.module_path, &rec.orig_name, &rec.name);
self.records
.entry(module_path_to_crate_name(&rec.module_path))
.or_default()
.insert_unique(rec.name.clone(), rec)?;
}
uniffi_meta::Metadata::Enum(en) => {
self.update_orig_names(&en.module_path, &en.orig_name, &en.name);
self.enums
.entry(module_path_to_crate_name(&en.module_path))
.or_default()
.insert_unique(en.name.clone(), en)?;
}
uniffi_meta::Metadata::Object(int) => {
self.update_orig_names(&int.module_path, &int.orig_name, &int.name);
self.interfaces
.entry(module_path_to_crate_name(&int.module_path))
.or_default()
.insert_unique(int.name.clone(), int)?;
}
uniffi_meta::Metadata::CallbackInterface(cbi) => {
self.callback_interfaces
.entry(module_path_to_crate_name(&cbi.module_path))
.or_default()
.insert_unique(cbi.name.clone(), cbi)?;
}
uniffi_meta::Metadata::CustomType(custom) => {
let by_crate = self
.custom_types
.entry(module_path_to_crate_name(&custom.module_path))
.or_default();
match by_crate.entry(custom.name.clone()) {
Entry::Vacant(e) => {
e.insert(custom);
}
Entry::Occupied(mut e) => {
let existing = e.get();
if existing.builtin != custom.builtin
|| existing.module_path != custom.module_path
{
bail!(
"Conflicting metadata types:\nold: {:?}\nnew: {:?}",
existing,
custom
);
}
if custom.docstring.is_some() && existing.docstring.is_none() {
e.insert(custom);
}
}
}
}
uniffi_meta::Metadata::Constructor(cons) => {
self.constructors
.entry((
module_path_to_crate_name(&cons.module_path),
cons.self_name.to_string(),
))
.or_default()
.insert_unique(cons.name.clone(), cons)?;
}
uniffi_meta::Metadata::Method(meth) => {
self.methods
.entry((
module_path_to_crate_name(&meth.module_path),
meth.self_name.to_string(),
))
.or_default()
.insert_unique(meth.name.clone(), meth)?;
}
uniffi_meta::Metadata::TraitMethod(meth) => {
self.trait_methods
.entry((
module_path_to_crate_name(&meth.module_path),
meth.trait_name.to_string(),
))
.or_default()
.insert_unique(meth.name.clone(), meth)?;
}
uniffi_meta::Metadata::UniffiTrait(ut) => {
let meth = match &ut {
uniffi_meta::UniffiTraitMetadata::Debug { fmt } => fmt,
uniffi_meta::UniffiTraitMetadata::Display { fmt } => fmt,
uniffi_meta::UniffiTraitMetadata::Eq { eq, .. } => eq,
uniffi_meta::UniffiTraitMetadata::Hash { hash } => hash,
uniffi_meta::UniffiTraitMetadata::Ord { cmp } => cmp,
};
self.uniffi_traits
.entry((
module_path_to_crate_name(&meth.module_path),
meth.self_name.to_string(),
))
.or_default()
.insert_unique(ut.name().to_string(), ut)?;
}
uniffi_meta::Metadata::ObjectTraitImpl(imp) => {
let (module_path, name) = match &imp.ty {
uniffi_meta::Type::Object {
module_path, name, ..
}
| uniffi_meta::Type::Record {
module_path, name, ..
}
| uniffi_meta::Type::Enum {
module_path, name, ..
}
| uniffi_meta::Type::Custom {
module_path, name, ..
}
| uniffi_meta::Type::CallbackInterface {
module_path, name, ..
} => (module_path, name),
_ => bail!("Invalid ObjectTraitImpl type: {:?}", imp.ty),
};
self.trait_impls
.entry((module_path_to_crate_name(module_path), name.to_string()))
.or_default()
.insert_unique(imp.trait_ty.clone(), imp)?;
}
uniffi_meta::Metadata::UdlFile(_) => (),
}
Ok(())
}
fn update_orig_names(&mut self, module_path: &str, orig_name: &Option<String>, name: &str) {
if let Some(orig_name) = orig_name {
self.orig_names.insert(
(module_path_to_crate_name(module_path), name.to_string()),
orig_name.clone(),
);
}
}
pub fn add_module_config_toml(
&mut self,
module_name: String,
table: toml::Table,
) -> Result<()> {
self.module_toml.insert_unique(module_name, table)?;
Ok(())
}
pub fn add_module_docstring(&mut self, namespace: String, docstring: String) -> Result<()> {
self.module_docstrings.insert_unique(namespace, docstring)
}
pub fn try_into_initial_ir(self) -> Result<Root> {
let context = Context {
module_path_map: self.module_path_map.clone(),
constructors: self.constructors,
methods: self.methods,
trait_methods: self.trait_methods,
uniffi_traits: self.uniffi_traits,
trait_impls: self.trait_impls,
orig_names: self.orig_names,
..Context::default()
};
let mut root = Root {
namespaces: self.namespaces.into_iter().collect(),
cdylib: None,
};
for (namespace_name, docstring) in self.module_docstrings {
let namespace = root.namespaces.get_mut(&namespace_name).ok_or_else(|| {
anyhow!("namespace specified in toml doesn't exist: {namespace_name:?}")
})?;
namespace.docstring = Some(docstring);
}
for (namespace_name, table) in self.module_toml {
let namespace = root.namespaces.get_mut(&namespace_name).ok_or_else(|| {
anyhow!("namespace specified in toml doesn't exist: {namespace_name:?}")
})?;
namespace.config_toml = Some(toml::to_string(&table)?);
}
for (module_path, funcs) in self.functions {
let namespace = get_namespace(&self.module_path_map, &mut root, &module_path)?;
for func in funcs.into_values() {
namespace.functions.push(func.map_node(&context)?);
}
}
for (module_path, list) in self.records {
let namespace = get_namespace(&self.module_path_map, &mut root, &module_path)?;
for rec in list.into_values() {
namespace
.type_definitions
.push(TypeDefinition::Record(rec.map_node(&context)?));
}
}
for (module_path, list) in self.enums {
let namespace = get_namespace(&self.module_path_map, &mut root, &module_path)?;
for en in list.into_values() {
namespace
.type_definitions
.push(TypeDefinition::Enum(en.map_node(&context)?));
}
}
for (module_path, list) in self.custom_types {
let namespace = get_namespace(&self.module_path_map, &mut root, &module_path)?;
for custom in list.into_values() {
namespace
.type_definitions
.push(TypeDefinition::Custom(custom.map_node(&context)?));
}
}
for (module_path, list) in self.interfaces {
let namespace = get_namespace(&self.module_path_map, &mut root, &module_path)?;
for int in list.into_values() {
namespace
.type_definitions
.push(TypeDefinition::Interface(int.map_node(&context)?));
}
}
for (module_path, list) in self.callback_interfaces {
let namespace = get_namespace(&self.module_path_map, &mut root, &module_path)?;
for cbi in list.into_values() {
namespace
.type_definitions
.push(TypeDefinition::CallbackInterface(cbi.map_node(&context)?));
}
}
Ok(root)
}
}
fn module_path_to_crate_name(module_path: &str) -> String {
module_path.split("::").next().unwrap().to_string()
}
fn get_namespace<'a>(
module_path_map: &BTreeMap<String, String>,
root: &'a mut Root,
module_path: &str,
) -> Result<&'a mut Namespace> {
let crate_name = module_path.split("::").next().unwrap();
let namespace_name = module_path_map
.get(crate_name)
.map(String::as_str)
.ok_or_else(|| anyhow!("module lookup failed: {module_path:?}"))?;
root.namespaces
.get_mut(namespace_name)
.ok_or_else(|| anyhow!("root module lookup failed: {module_path:?}"))
}