/* 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/. */ use std::collections::{BTreeSet, HashMap}; use crate::*; use anyhow::{bail, Result}; type MetadataGroupMap = HashMap; // Create empty metadata groups based on the metadata items. pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap { // Map crate names to MetadataGroup instances items .iter() .filter_map(|i| match i { Metadata::Namespace(namespace) => { let group = MetadataGroup { namespace: namespace.clone(), namespace_docstring: None, items: BTreeSet::new(), }; Some((namespace.crate_name.clone(), group)) } Metadata::UdlFile(udl) => { let namespace = NamespaceMetadata { crate_name: udl.module_path.clone(), name: udl.namespace.clone(), }; let group = MetadataGroup { namespace, namespace_docstring: None, items: BTreeSet::new(), }; Some((udl.module_path.clone(), group)) } _ => None, }) .collect::>() } /// Consume the items into the previously created metadata groups. pub fn group_metadata(group_map: &mut MetadataGroupMap, items: Vec) -> Result<()> { for item in items { if matches!(&item, Metadata::Namespace(_)) { continue; } let crate_name = calc_crate_name(item.module_path()).to_owned(); // XXX - kill clone? let item = fixup_external_type(item, group_map); let group = match group_map.get_mut(&crate_name) { Some(ns) => ns, None => bail!("Unknown namespace for {item:?} ({crate_name})"), }; if group.items.contains(&item) { bail!("Duplicate metadata item: {item:?}"); } group.add_item(item); } Ok(()) } #[derive(Debug)] pub struct MetadataGroup { pub namespace: NamespaceMetadata, pub namespace_docstring: Option, pub items: BTreeSet, } impl MetadataGroup { pub fn add_item(&mut self, item: Metadata) { self.items.insert(item); } } pub fn fixup_external_type(item: Metadata, group_map: &MetadataGroupMap) -> Metadata { let crate_name = calc_crate_name(item.module_path()).to_owned(); let converter = ExternalTypeConverter { crate_name: &crate_name, crate_to_namespace: group_map, }; converter.convert_item(item) } /// Convert metadata items by replacing types from external crates with Type::External struct ExternalTypeConverter<'a> { crate_name: &'a str, crate_to_namespace: &'a MetadataGroupMap, } impl<'a> ExternalTypeConverter<'a> { fn crate_to_namespace(&self, crate_name: &str) -> String { self.crate_to_namespace .get(crate_name) .unwrap_or_else(|| panic!("Can't find namespace for module {crate_name}")) .namespace .name .clone() } fn convert_item(&self, item: Metadata) -> Metadata { match item { Metadata::Func(meta) => Metadata::Func(FnMetadata { inputs: self.convert_params(meta.inputs), return_type: self.convert_optional(meta.return_type), throws: self.convert_optional(meta.throws), ..meta }), Metadata::Method(meta) => Metadata::Method(MethodMetadata { inputs: self.convert_params(meta.inputs), return_type: self.convert_optional(meta.return_type), throws: self.convert_optional(meta.throws), ..meta }), Metadata::TraitMethod(meta) => Metadata::TraitMethod(TraitMethodMetadata { inputs: self.convert_params(meta.inputs), return_type: self.convert_optional(meta.return_type), throws: self.convert_optional(meta.throws), ..meta }), Metadata::Constructor(meta) => Metadata::Constructor(ConstructorMetadata { inputs: self.convert_params(meta.inputs), throws: self.convert_optional(meta.throws), ..meta }), Metadata::Record(meta) => Metadata::Record(RecordMetadata { fields: self.convert_fields(meta.fields), ..meta }), Metadata::Enum(meta) => Metadata::Enum(self.convert_enum(meta)), _ => item, } } fn convert_params(&self, params: Vec) -> Vec { params .into_iter() .map(|param| FnParamMetadata { ty: self.convert_type(param.ty), ..param }) .collect() } fn convert_fields(&self, fields: Vec) -> Vec { fields .into_iter() .map(|field| FieldMetadata { ty: self.convert_type(field.ty), ..field }) .collect() } fn convert_enum(&self, enum_: EnumMetadata) -> EnumMetadata { EnumMetadata { variants: enum_ .variants .into_iter() .map(|variant| VariantMetadata { fields: self.convert_fields(variant.fields), ..variant }) .collect(), ..enum_ } } fn convert_optional(&self, ty: Option) -> Option { ty.map(|ty| self.convert_type(ty)) } fn convert_type(&self, ty: Type) -> Type { match ty { // Convert `ty` if it's external Type::Enum { module_path, name } | Type::Record { module_path, name } if self.is_module_path_external(&module_path) => { Type::External { namespace: self.crate_to_namespace(&module_path), module_path, name, kind: ExternalKind::DataClass, tagged: false, } } Type::Custom { module_path, name, .. } if self.is_module_path_external(&module_path) => { // For now, it's safe to assume that all custom types are data classes. // There's no reason to use a custom type with an interface. Type::External { namespace: self.crate_to_namespace(&module_path), module_path, name, kind: ExternalKind::DataClass, tagged: false, } } Type::Object { module_path, name, .. } if self.is_module_path_external(&module_path) => Type::External { namespace: self.crate_to_namespace(&module_path), module_path, name, kind: ExternalKind::Interface, tagged: false, }, Type::CallbackInterface { module_path, name } if self.is_module_path_external(&module_path) => { panic!("External callback interfaces not supported ({name})") } // Convert child types Type::Custom { module_path, name, builtin, .. } => Type::Custom { module_path, name, builtin: Box::new(self.convert_type(*builtin)), }, Type::Optional { inner_type } => Type::Optional { inner_type: Box::new(self.convert_type(*inner_type)), }, Type::Sequence { inner_type } => Type::Sequence { inner_type: Box::new(self.convert_type(*inner_type)), }, Type::Map { key_type, value_type, } => Type::Map { key_type: Box::new(self.convert_type(*key_type)), value_type: Box::new(self.convert_type(*value_type)), }, // Existing External types probably need namespace fixed. Type::External { namespace, module_path, name, kind, tagged, } => { assert!(namespace.is_empty()); Type::External { namespace: self.crate_to_namespace(&module_path), module_path, name, kind, tagged, } } // Otherwise, just return the type unchanged _ => ty, } } fn is_module_path_external(&self, module_path: &str) -> bool { calc_crate_name(module_path) != self.crate_name } } fn calc_crate_name(module_path: &str) -> &str { module_path.split("::").next().unwrap() }