/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! Organize the metadata, transforming it from a simple list to a more tree-like structure. use anyhow::{anyhow, bail, Context, Result}; use camino::Utf8Path; use std::{collections::BTreeMap, fs}; use super::nodes::*; use uniffi_pipeline::Node; /// Converts `uniffi_meta` items into the initial IR. /// /// Usage: /// * Call [Self::add_metadata_item] with all `uniffi_meta` items in the interface (also [Self::add_module_docstring]) /// * Call [Self::try_into_initial_ir] to construct a `initial::Root` node. #[derive(Default)] pub struct UniffiMetaConverter { // Map module_path to `Module::name` // // This is needed because UDL and proc-macro code can generate different module paths. To // normalize things, we find the `NamespaceMetadata::name` that matches the module path. // // Use BTreeMap for each of these so that things stay consistent, regardless of how the // metadata is ordered. module_path_map: BTreeMap, // Top level modules modules: BTreeMap, // Top-level type definitions and functions, keyed by module name module_docstrings: BTreeMap, module_toml: BTreeMap, functions: BTreeMap>, records: BTreeMap>, callback_interfaces: BTreeMap>, enums: BTreeMap>, custom_types: BTreeMap>, interfaces: BTreeMap>, // Child items, keyed by module name + parent name constructors: BTreeMap<(String, String), BTreeMap>, methods: BTreeMap<(String, String), BTreeMap>, trait_methods: BTreeMap<(String, String), BTreeMap>, uniffi_traits: BTreeMap<(String, String), BTreeMap>, trait_impls: BTreeMap<(String, String), BTreeMap>, } impl UniffiMetaConverter { /// Add a [uniffi_meta::Metadata] item to be converted pub fn add_metadata_item(&mut self, meta: uniffi_meta::Metadata) -> Result<()> { match meta { uniffi_meta::Metadata::Namespace(namespace) => { // Map both the crate name and module name to the module name. This simplifies the // code in `get_module` self.module_path_map .insert(namespace.crate_name.clone(), namespace.name.clone()); self.module_path_map .insert(namespace.name.clone(), namespace.name.clone()); // Insert a new module self.modules .entry(namespace.name.clone()) .or_insert(Module { // TODO: Is this the correct crate name in all cases? I'm pretty sure // it is for proc-macro based generation, since the proc-macro // namespace is added to the metadata list before the UDL one. // However, I'm not so sure what happens if we're generating from a UDL // file. crate_name: namespace.crate_name, docstring: None, config_toml: None, name: namespace.name, functions: vec![], type_definitions: vec![], }); } uniffi_meta::Metadata::Func(func) => { self.functions .entry(func.module_path.clone()) .or_default() .insert(func.name.clone(), Function::try_from_node(func)?); } uniffi_meta::Metadata::Record(rec) => { self.records .entry(rec.module_path.clone()) .or_default() .insert(rec.name.clone(), Record::try_from_node(rec)?); } uniffi_meta::Metadata::Enum(en) => { self.enums .entry(en.module_path.clone()) .or_default() .insert(en.name.clone(), Enum::try_from_node(en)?); } uniffi_meta::Metadata::Object(int) => { self.interfaces .entry(int.module_path.clone()) .or_default() .insert(int.name.clone(), Interface::try_from_node(int)?); } uniffi_meta::Metadata::CallbackInterface(cbi) => { self.callback_interfaces .entry(cbi.module_path.clone()) .or_default() .insert(cbi.name.clone(), CallbackInterface::try_from_node(cbi)?); } uniffi_meta::Metadata::CustomType(custom) => { self.custom_types .entry(custom.module_path.clone()) .or_default() .insert(custom.name.clone(), CustomType::try_from_node(custom)?); } uniffi_meta::Metadata::Constructor(cons) => { self.constructors .entry((cons.module_path.clone(), cons.self_name.clone())) .or_default() .insert(cons.name.clone(), Constructor::try_from_node(cons)?); } uniffi_meta::Metadata::Method(meth) => { self.methods .entry((meth.module_path.clone(), meth.self_name.clone())) .or_default() .insert(meth.name.clone(), Method::try_from_node(meth)?); } uniffi_meta::Metadata::TraitMethod(meth) => { self.trait_methods .entry((meth.module_path.clone(), meth.trait_name.clone())) .or_default() .insert(meth.name.clone(), TraitMethod::try_from_node(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, }; self.uniffi_traits .entry((meth.module_path.clone(), meth.self_name.clone())) .or_default() .insert(ut.name().clone(), UniffiTrait::try_from_node(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_string(), name.to_string())) .or_default() .insert( imp.trait_name.clone(), ObjectTraitImpl { tr_module_name: match &imp.tr_module_path { None => None, Some(module_path) => Some( get_module_name(&self.module_path_map, module_path)? .to_string(), ), }, ..ObjectTraitImpl::try_from_node(imp)? }, ); } uniffi_meta::Metadata::UdlFile(_) => (), } Ok(()) } pub fn add_module_config_toml(&mut self, module_name: String, path: &Utf8Path) -> Result<()> { if !path.exists() { return Ok(()); } let contents = fs::read_to_string(path).with_context(|| format!("read file: {:?}", path))?; self.module_toml.insert(module_name, contents); Ok(()) } /// Add a docstring for a module, /// /// This is currently UDL-specific. Eventually, we should probably make this another metadata /// items pub fn add_module_docstring(&mut self, module_name: String, docstring: String) { self.module_docstrings.insert(module_name, docstring); } pub fn try_into_initial_ir(mut self) -> Result { let mut root = Root { modules: self.modules.into_iter().collect(), cdylib: None, }; // Move child items into their parents for (module_path, docstring) in self.module_docstrings { get_module(&self.module_path_map, &mut root, &module_path)?.docstring = Some(docstring); } for (module_path, toml) in self.module_toml { get_module(&self.module_path_map, &mut root, &module_path)?.config_toml = Some(toml); } for (module_path, funcs) in self.functions { get_module(&self.module_path_map, &mut root, &module_path)? .functions .extend(funcs.into_values()); } for (module_path, list) in self.records { get_module(&self.module_path_map, &mut root, &module_path)? .type_definitions .extend(list.into_values().map(TypeDefinition::Record)); } for (module_path, list) in self.enums { get_module(&self.module_path_map, &mut root, &module_path)? .type_definitions .extend(list.into_values().map(TypeDefinition::Enum)); } for (module_path, list) in self.custom_types { get_module(&self.module_path_map, &mut root, &module_path)? .type_definitions .extend(list.into_values().map(TypeDefinition::Custom)); } // Collect child items for interfaces and callback interfaces for (module_path, list) in self.interfaces { get_module(&self.module_path_map, &mut root, &module_path)? .type_definitions .extend( list.into_values() .map(|mut int| { let key = (module_path.clone(), int.name.clone()); if let Some(methods) = self.methods.remove(&key) { if self.trait_methods.contains_key(&key) { // Trait methods have an explicit index, so mixing them with // regular methods won't work. bail!("{} contains both methods and trait methods", int.name) } int.methods.extend(methods.into_values()); } else if let Some(trait_methods) = self.trait_methods.remove(&key) { int.methods.extend(Self::convert_trait_methods( trait_methods.into_values().collect(), )); } if let Some(constructors) = self.constructors.remove(&key) { int.constructors.extend(constructors.into_values()) } if let Some(uniffi_traits) = self.uniffi_traits.remove(&key) { int.uniffi_traits.extend(uniffi_traits.into_values()) } if let Some(trait_impls) = self.trait_impls.remove(&key) { int.trait_impls.extend(trait_impls.into_values()) } Ok(TypeDefinition::Interface(int)) }) .collect::>>()?, ) } for (module_path, list) in self.callback_interfaces { get_module(&self.module_path_map, &mut root, &module_path)? .type_definitions .extend(list.into_values().map(|mut cbi| { let key = (module_path.clone(), cbi.name.clone()); if let Some(trait_methods) = self.trait_methods.remove(&key) { cbi.methods.extend(Self::convert_trait_methods( trait_methods.into_values().collect(), )); } TypeDefinition::CallbackInterface(cbi) })) } if !self.constructors.is_empty() { bail!("Leftover constructors: {:?}", self.constructors) } if !self.methods.is_empty() { bail!("Leftover methods: {:?}", self.methods) } if !self.trait_methods.is_empty() { bail!("Leftover trait_methods: {:?}", self.trait_methods) } if !self.uniffi_traits.is_empty() { bail!("Leftover uniffi_traits: {:?}", self.uniffi_traits) } if !self.trait_impls.is_empty() { bail!("Leftover trait_impls: {:?}", self.trait_impls) } // normalize type module names root.try_visit_mut(|ty: &mut Type| match ty { Type::Interface { module_name, .. } | Type::Record { module_name, .. } | Type::Enum { module_name, .. } | Type::CallbackInterface { module_name, .. } | Type::Custom { module_name, .. } => { *module_name = get_module_name(&self.module_path_map, module_name)?.to_string(); Ok(()) } _ => Ok(()), })?; Ok(root) } fn convert_trait_methods(mut trait_methods: Vec) -> impl Iterator { trait_methods.sort_by_key(|tm| tm.index); trait_methods.into_iter().map(Self::convert_trait_method) } fn convert_trait_method(trait_method: TraitMethod) -> Method { Method { name: trait_method.name, is_async: trait_method.is_async, inputs: trait_method.inputs, return_type: trait_method.return_type, throws: trait_method.throws, checksum: trait_method.checksum, docstring: trait_method.docstring, } } } fn get_module_name<'a>( module_path_map: &'a BTreeMap, module_path: &str, ) -> Result<&'a str> { module_path_map .get(module_path) .map(String::as_str) .ok_or_else(|| anyhow!("module lookup failed: {module_path:?}")) } fn get_module<'a>( module_path_map: &BTreeMap, root: &'a mut Root, module_path: &str, ) -> Result<&'a mut Module> { let module_name = get_module_name(module_path_map, module_path)?; root.modules .get_mut(module_name) .ok_or_else(|| anyhow!("root module lookup failed: {module_path:?}")) }