| /* 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<String, MetadataGroup>; |
| |
| // 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::<HashMap<_, _>>() |
| } |
| |
| /// Consume the items into the previously created metadata groups. |
| pub fn group_metadata(group_map: &mut MetadataGroupMap, items: Vec<Metadata>) -> 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<String>, |
| pub items: BTreeSet<Metadata>, |
| } |
| |
| 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<FnParamMetadata>) -> Vec<FnParamMetadata> { |
| params |
| .into_iter() |
| .map(|param| FnParamMetadata { |
| ty: self.convert_type(param.ty), |
| ..param |
| }) |
| .collect() |
| } |
| |
| fn convert_fields(&self, fields: Vec<FieldMetadata>) -> Vec<FieldMetadata> { |
| 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<Type>) -> Option<Type> { |
| 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() |
| } |