//! The `MetadataPattern` matches functions annotated with `#[metadata(BindType)]` //! and extracts two types referenced by the metadata system: //! - `BindType` — the entry enum variant the metadata is bound to (attribute argument) //! - `DataType` — the function's return type, i.e. the metadata type //! //! Both types are tracked so that `pathf` can emit the `use` statements needed to //! bring them into scope for `gen_program!` generated code. //! //! Example: //! ```ignore //! #[metadata(EntryGreet)] // BindType = EntryGreet //! pub fn get_desc() -> Description { ... } // DataType = Description //! ``` use std::collections::HashMap; use syn::Item; use syn::UseTree; use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern}; /// Matches `#[metadata(BindType)]` functions, extracting the bound entry type /// and the metadata (return) type. pub struct MetadataPattern; impl AnalyzePattern for MetadataPattern { fn contains(&self, content: &str) -> bool { content.contains("[metadata(") || content.contains("[metadata]") } fn analyze(&self, content: &str) -> Vec { let Ok(syntax) = syn::parse_file(content) else { return Vec::new(); }; let imports = collect_use_imports(&syntax.items); let mut items = Vec::new(); for item in &syntax.items { collect_from_item(item, "", &imports, &mut items); } items } } fn collect_from_item( item: &Item, current_mod: &str, imports: &HashMap, items: &mut Vec, ) { match item { Item::Fn(f) => { let Some(bind_type) = extract_bind_type(&f.attrs) else { return; }; let data_type = extract_data_type(f); let Some(data_type) = data_type else { return; }; // BindType — always an in-crate entry type generated by dispatcher!/pack!. items.push(AnalyzeItem::local(current_mod.to_string(), bind_type)); // DataType — may be a local type or a `use`-imported foreign type. if let Some((module, _)) = imports.get(&data_type) { items.push(AnalyzeItem::foreign(module.clone(), data_type)); } else { items.push(AnalyzeItem::local(current_mod.to_string(), data_type)); } } Item::Mod(item_mod) => { if let Some((_, nested)) = &item_mod.content { let mod_name = &item_mod.ident.to_string(); let nested_mod = if current_mod.is_empty() { mod_name.clone() } else { format!("{current_mod}::{mod_name}") }; let inner_imports = collect_use_imports(nested); for n in nested { collect_from_item(n, &nested_mod, &inner_imports, items); } } } _ => {} } } /// Extracts the `BindType` (the ident argument of `#[metadata(...)]`). fn extract_bind_type(attrs: &[syn::Attribute]) -> Option { for attr in attrs { let path_ident = attr.path().segments.last()?.ident.to_string(); if path_ident != "metadata" { continue; } if let syn::Meta::List(meta_list) = &attr.meta { for token in meta_list.tokens.clone().into_iter() { if let proc_macro2::TokenTree::Ident(ident) = token { return Some(ident.to_string()); } } } } None } /// Extracts the `DataType` (the function's return type path last segment). fn extract_data_type(f: &syn::ItemFn) -> Option { let syn::ReturnType::Type(_, ty) = &f.sig.output else { return None; }; match ty.as_ref() { syn::Type::Path(type_path) => type_path.path.segments.last().map(|s| s.ident.to_string()), _ => None, } } /// Collect `use` imports from a list of top-level items. /// /// Returns a map of `short_name → (module_path, short_name)`. fn collect_use_imports(items: &[syn::Item]) -> HashMap { let mut map = HashMap::new(); for item in items { if let Item::Use(use_item) = item { collect_from_use_tree(&use_item.tree, "", &mut map); } } map } /// Recursively traverse a `UseTree` and collect named imports. fn collect_from_use_tree( tree: &UseTree, prefix: &str, map: &mut HashMap, ) { match tree { UseTree::Name(name) => { let module = prefix.to_string(); let alias = name.ident.to_string(); map.entry(alias).or_insert((module, name.ident.to_string())); } UseTree::Path(use_path) => { let new_prefix = if prefix.is_empty() { use_path.ident.to_string() } else { format!("{}::{}", prefix, use_path.ident) }; collect_from_use_tree(&use_path.tree, &new_prefix, map); } UseTree::Rename(rename) => { let module = prefix.to_string(); let alias = rename.ident.to_string(); map.entry(alias) .or_insert((module, rename.ident.to_string())); } UseTree::Glob(_) => {} UseTree::Group(group) => { for item in &group.items { collect_from_use_tree(item, prefix, map); } } } }