diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-04 13:26:48 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-04 13:26:48 +0800 |
| commit | 4588e17f7ddacd8391a5c881a209735e08d48797 (patch) | |
| tree | 2b513f4d4f8a18ebd3a34cff7c0fb8abf5787321 /mingling_pathf/src/patterns | |
| parent | f7862affd74ac3b129f9e9791a4cd4e1c7e3e6d1 (diff) | |
feat: add entry metadata system with compile-time typed values
Add `Metadata<B>` trait and `#[metadata(Entry)]` attribute macro
to attach arbitrary, compile-time-typed metadata to entries.
Implement `ProgramCollect::get_metadata<T>()` for runtime retrieval,
backed by a global registry populated by `register_metadata!`.
Extend `pathf` with `MetadataPattern` to resolve metadata types
across modules at build time. Add two examples demonstrating
metadata usage with and without pathf integration.
Diffstat (limited to 'mingling_pathf/src/patterns')
| -rw-r--r-- | mingling_pathf/src/patterns/metadata.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/mingling_pathf/src/patterns/metadata.rs b/mingling_pathf/src/patterns/metadata.rs new file mode 100644 index 0000000..24243bf --- /dev/null +++ b/mingling_pathf/src/patterns/metadata.rs @@ -0,0 +1,165 @@ +//! 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<AnalyzeItem> { + 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<String, (String, String)>, + items: &mut Vec<AnalyzeItem>, +) { + 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<String> { + 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<String> { + 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<String, (String, String)> { + 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<String, (String, String)>, +) { + 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); + } + } + } +} |
