aboutsummaryrefslogtreecommitdiff
path: root/mingling_pathf/src/patterns/metadata.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_pathf/src/patterns/metadata.rs')
-rw-r--r--mingling_pathf/src/patterns/metadata.rs165
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);
+ }
+ }
+ }
+}