diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-28 19:10:17 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-28 19:10:17 +0800 |
| commit | bf35af6bbf30492bcd23eb6103fdac3382dd6d33 (patch) | |
| tree | db4485c91a78ce1219f9c7774a2988ccce2897be | |
| parent | 045c6aab213aadf4dbb66270ec0d203d7ec762a6 (diff) | |
refactor(pathf): move type resolution from compile-time to build-time
| -rw-r--r-- | CHANGELOG.md | 8 | ||||
| -rw-r--r-- | mingling_macros/src/func/gen_program.rs | 10 | ||||
| -rw-r--r-- | mingling_macros/src/func/program_final_gen.rs | 121 | ||||
| -rw-r--r-- | mingling_macros/src/systems/dispatch_tree_gen.rs | 43 | ||||
| -rw-r--r-- | mingling_pathf/src/pattern_analyzer.rs | 14 | ||||
| -rw-r--r-- | mingling_pathf/src/type_mapping_builder.rs | 17 |
6 files changed, 67 insertions, 146 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 70137fe..15a5f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,13 @@ None #### Optimizations: -None +1. **[`pathf`]** Moved pathf type resolution from `program_final_gen` (compile-time loading and parsing of `type_using.rs`) to `gen_program!()` (build-time `include!()`). The `program_final_gen` macro no longer loads `type_using.rs` at compile time or embeds `use` statements in generated code. Instead, `gen_program!()` now emits `include!(concat!(env!("OUT_DIR"), "/", env!("CARGO_PKG_NAME"), "/type_using.rs"))` (feature-gated behind `pathf`), which brings all discovered types into scope as bare idents. + + Consequently, all type resolution in `program_final_gen` (chain arms, render arms, help arms, completion arms, and dispatch tree generation) now uses simple idents (`ident_tokens(name)`) instead of the former `resolve_type(name, &pathf_map)` call that produced full qualified paths. The `resolve_type` function, `load_pathf_map`, and all associated `HashMap<String, String>` plumbing have been removed from `program_final_gen.rs`. + + In `dispatch_tree_gen.rs`, `gen_get_nodes` and `gen_dispatch_args_trie` no longer accept a `pathf_map` parameter; dispatcher static names and dispatching type names are now emitted as bare idents. + +2. **[`pathf`]** Added `is_module` field to `AnalyzeItem` and a new constructor `AnalyzeItem::local_module(module, item_name)` which sets `is_module: true`. The `type_mapping_builder` now tracks whether an item is a module: when generating `type_using.rs`, module items produce `use path::to::module::*;` (glob import) instead of the standard `use path::to::TypeName;` direct import. Non-module items continue to use direct imports as before. The internal data structure changed from `Vec<(String, String)>` to `Vec<(String, String, bool)>` to carry the `is_module` flag through the pipeline. #### Features: diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs index c5358bc..6b6a643 100644 --- a/mingling_macros/src/func/gen_program.rs +++ b/mingling_macros/src/func/gen_program.rs @@ -15,6 +15,15 @@ pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream { #[cfg(not(feature = "comp"))] let comp_gen = quote! {}; + // When pathf is enabled, include the type_using.rs generated by the build + // script so that types from submodules are in scope for gen_program!(). + #[cfg(feature = "pathf")] + let pathf_include = quote! { + include!(concat!(env!("OUT_DIR"), "/", env!("CARGO_PKG_NAME"), "/type_using.rs")); + }; + #[cfg(not(feature = "pathf"))] + let pathf_include = quote! {}; + TokenStream::from(quote! { /// Alias for the current program type `crate::ThisProgram` pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; @@ -40,6 +49,7 @@ pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream { } } + #pathf_include #comp_gen ::mingling::macros::program_fallback_gen!(); ::mingling::macros::program_final_gen!(); diff --git a/mingling_macros/src/func/program_final_gen.rs b/mingling_macros/src/func/program_final_gen.rs index d3af3b3..41420a4 100644 --- a/mingling_macros/src/func/program_final_gen.rs +++ b/mingling_macros/src/func/program_final_gen.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use proc_macro::TokenStream; use quote::quote; @@ -37,48 +35,12 @@ fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, pr (struct_ident, variant_ident) } -/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`. -fn load_pathf_map() -> Option<std::collections::HashMap<String, String>> { - if !cfg!(feature = "pathf") { - return None; - } - let out_dir = std::env::var("OUT_DIR").ok()?; - let crate_name = std::env::var("CARGO_PKG_NAME").ok()?; - let path = std::path::Path::new(&out_dir) - .join(&crate_name) - .join("type_using.rs"); - let content = std::fs::read_to_string(&path).ok()?; - Some( - content - .lines() - .filter_map(|line| { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("use ") { - let path = rest.strip_suffix(';').unwrap_or(rest); - if let Some((_mod, type_name)) = path.rsplit_once("::") { - return Some((type_name.to_string(), path.to_string())); - } - } - None - }) - .collect(), - ) -} - -/// Resolves a type name to its full path token stream using the pathf mapping. -pub(crate) fn resolve_type( - name: &str, - map: &std::collections::HashMap<String, String>, -) -> proc_macro2::TokenStream { - if let Some(full_path) = map.get(name) { - syn::parse_str::<proc_macro2::TokenStream>(full_path).unwrap_or_else(|_| { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - }) - } else { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - } +/// Helper: convert a string ident into a token stream for the generated code. +/// Types are now brought into scope by `gen_program!()` via `include!()` of the +/// pathf-generated `type_using.rs`, so bare idents suffice. +fn ident_tokens(name: &str) -> proc_macro2::TokenStream { + let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); + quote! { #ident } } #[allow(clippy::too_many_lines)] @@ -126,45 +88,6 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) .collect(); - let pathf_map: Option<HashMap<String, String>> = load_pathf_map(); - - #[cfg(feature = "pathf")] - let pathf_hint: proc_macro2::TokenStream = if pathf_map.is_none() { - quote! { - compile_error!( -"Cannot load type mapping computed by `pathf`. -If not yet configured, execute `mingling::build::analyze_and_build_type_mapping()` in your `build.rs`. - -fn main() { - // Require features: [\"builds\", \"pathf\"] - mingling::build::analyze_and_build_type_mapping().unwrap(); - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\__ Write to `build.rs` - -} - "); - } - } else { - quote! {} - }; - - #[cfg(not(feature = "pathf"))] - let pathf_hint: proc_macro2::TokenStream = quote! {}; - - let pathf_map: HashMap<String, String> = if cfg!(feature = "pathf") { - pathf_map.unwrap_or_default() - } else { - HashMap::new() - }; - - let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") { - pathf_map - .values() - .map(|path| format!("use {};", path).parse().unwrap_or_default()) - .collect() - } else { - Vec::new() - }; - #[cfg(feature = "structural_renderer")] let structural_renderer_tokens: Vec<proc_macro2::TokenStream> = structural_renderers .iter() @@ -177,13 +100,9 @@ fn main() { any: ::mingling::AnyOutput<Self::Enum>, setting: &::mingling::StructuralRendererSetting, ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { - #[allow(unused_imports)] - #(#pathf_uses)* match any.member_id() { #(#structural_renderer_tokens)* _ => { - // Non-structural types: render ResultEmpty (which implements - // StructuralData + Serialize) instead of producing nothing. let mut r = ::mingling::RenderResult::default(); ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?; Ok(r) @@ -222,8 +141,8 @@ fn main() { }) .collect(); - let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries, &pathf_map); - let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries, &pathf_map); + let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries); + let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries); quote! { #get_nodes_fn @@ -243,8 +162,6 @@ fn main() { #[cfg(feature = "comp")] let comp = quote! { fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { - #[allow(unused_imports)] - #(#pathf_uses)* match any.member_id() { #(#completion_tokens)* _ => ::mingling::Suggest::FileCompletion, @@ -266,12 +183,10 @@ fn main() { } else { let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + let downcast_ty = ident_tokens(&variant_ident.to_string()); + let resolved_struct = ident_tokens(&struct_ident.to_string()); quote! { Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; <#resolved_struct as ::mingling::Renderer>::render(value) } @@ -290,12 +205,10 @@ fn main() { // Build do_chain function (async and sync versions) let chain_arms_async: Vec<_> = chain_tokens.iter().map(|entry| { let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + let downcast_ty = ident_tokens(&variant_ident.to_string()); + let resolved_struct = ident_tokens(&struct_ident.to_string()); quote! { Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; let fut = async { <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value).await }; ::std::boxed::Box::pin(fut) @@ -307,12 +220,10 @@ fn main() { .iter() .map(|entry| { let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + let downcast_ty = ident_tokens(&variant_ident.to_string()); + let resolved_struct = ident_tokens(&struct_ident.to_string()); quote! { Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value) } @@ -370,8 +281,6 @@ fn main() { }; let expanded = quote! { - #pathf_hint - #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(#repr_type)] #[allow(nonstandard_style)] @@ -404,8 +313,6 @@ fn main() { #render_fn #do_chain_fn fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - #[allow(unused_imports)] - #(#pathf_uses)* match any.member_id() { #(#help_tokens)* _ => ::mingling::RenderResult::default(), diff --git a/mingling_macros/src/systems/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs index 9eeb637..fe44a49 100644 --- a/mingling_macros/src/systems/dispatch_tree_gen.rs +++ b/mingling_macros/src/systems/dispatch_tree_gen.rs @@ -1,27 +1,22 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use just_fmt::snake_case; use proc_macro2::TokenStream; use quote::quote; -use crate::func::program_final_gen::resolve_type; - /// Generate the `get_nodes()` function body for a ProgramCollect impl. -/// If `pathf_map` is non-empty, resolves internal dispatcher statics using full paths. -pub(crate) fn gen_get_nodes( - entries: &[(String, String, String)], - pathf_map: &HashMap<String, String>, -) -> TokenStream { +pub(crate) fn gen_get_nodes(entries: &[(String, String, String)]) -> TokenStream { let mut node_entries = Vec::new(); for (node_name, _disp_type, _entry_name) in entries { let static_name_str = format!("__internal_dispatcher_{}", snake_case!(node_name)); - let resolved = resolve_type(&static_name_str, pathf_map); + let static_ident = + proc_macro2::Ident::new(&static_name_str, proc_macro2::Span::call_site()); let node_display_name = node_name.replace('.', " "); let node_display_lit = syn::LitStr::new(&node_display_name, proc_macro2::Span::call_site()); node_entries.push(quote! { - (#node_display_lit.to_string(), & #resolved) + (#node_display_lit.to_string(), &#static_ident) }); } @@ -38,20 +33,13 @@ pub(crate) fn gen_get_nodes( /// /// Builds a hardcoded match tree: at each depth, group nodes by character. /// Single-node groups use `starts_with`; multi-node groups recurse with `nth()` match. -/// -/// If `pathf_map` is non-empty, resolves dispatcher types using full paths. -pub(crate) fn gen_dispatch_args_trie( - entries: &[(String, String, String)], - pathf_map: &HashMap<String, String>, -) -> TokenStream { - // Prepare (display_name, disp_type) pairs. - // display_name = node_name.replace('.', " ") +pub(crate) fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> TokenStream { let nodes: Vec<(String, String)> = entries .iter() .map(|(name, disp, _)| (name.replace('.', " "), disp.clone())) .collect(); - let dispatch_body = build_dispatch_body(&nodes, 0, pathf_map); + let dispatch_body = build_dispatch_body(&nodes, 0); quote! { fn dispatch_args_trie( @@ -70,19 +58,13 @@ pub(crate) fn gen_dispatch_args_trie( /// /// `nodes`: slice of (display_name, disp_type) for commands that share the same prefix so far. /// `depth`: The character index currently being matched. -/// `pathf_map`: optional mapping from type name to full path for resolving dispatchers. -fn build_dispatch_body( - nodes: &[(String, String)], - depth: usize, - pathf_map: &HashMap<String, String>, -) -> TokenStream { +fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream { if nodes.is_empty() { return quote! { return Ok(Self::build_dispatcher_not_found(raw.to_vec())); }; } - // Group by character at `depth` let mut groups: BTreeMap<char, Vec<(String, String)>> = BTreeMap::new(); let mut exact_nodes: Vec<(String, String)> = Vec::new(); @@ -97,18 +79,17 @@ fn build_dispatch_body( } } - // Build a dispatch arm for a single node via `starts_with` let make_starts_with_arm = |name: &str, disp_type: &str| -> TokenStream { let name_space = format!("{} ", name); let name_lit = syn::LitStr::new(&name_space, proc_macro2::Span::call_site()); - let disp_resolved = resolve_type(disp_type, pathf_map); + let disp_ident = proc_macro2::Ident::new(disp_type, proc_macro2::Span::call_site()); let prefix_word_count = name.split_whitespace().count(); quote! { if raw_str.starts_with(#name_lit) { let prefix_len = #prefix_word_count; let trimmed_args: Vec<String> = raw.iter().skip(prefix_len).cloned().collect(); - let __cp = <#disp_resolved as ::mingling::Dispatcher<Self::Enum>>::begin( - &#disp_resolved::default(), + let __cp = <#disp_ident as ::mingling::Dispatcher<Self::Enum>>::begin( + &#disp_ident::default(), trimmed_args, ); return match __cp { @@ -136,7 +117,7 @@ fn build_dispatch_body( } }); } else { - let sub_body = build_dispatch_body(sub_nodes, depth + 1, pathf_map); + let sub_body = build_dispatch_body(sub_nodes, depth + 1); arms.push(quote! { Some(#ch_char) => { #sub_body diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs index 5b25c15..e88d9f6 100644 --- a/mingling_pathf/src/pattern_analyzer.rs +++ b/mingling_pathf/src/pattern_analyzer.rs @@ -50,6 +50,8 @@ pub struct AnalyzeItem { pub item_name: String, /// Whether the item is from an external crate (resolved via `use`), bypassing the file's own module path. pub is_foreign: bool, + /// When `true`, this item is a module whose contents should be glob-imported (`::*`). + pub is_module: bool, } impl AnalyzeItem { @@ -59,6 +61,17 @@ impl AnalyzeItem { module, item_name, is_foreign: false, + is_module: false, + } + } + + /// Creates a local module item — generates a `use path::item_name::*;` glob import. + pub fn local_module(module: String, item_name: String) -> Self { + Self { + module, + item_name, + is_foreign: false, + is_module: true, } } @@ -68,6 +81,7 @@ impl AnalyzeItem { module, item_name, is_foreign: true, + is_module: false, } } } diff --git a/mingling_pathf/src/type_mapping_builder.rs b/mingling_pathf/src/type_mapping_builder.rs index f6f57a5..fb59b43 100644 --- a/mingling_pathf/src/type_mapping_builder.rs +++ b/mingling_pathf/src/type_mapping_builder.rs @@ -25,7 +25,7 @@ pub fn analyze_and_build_type_mapping_for( let module_mapping = module_pathf::analyze(crate_dir)?; let analyzer = pattern_analyzer::init_with_config(config.clone()); - let mut type_mappings: Vec<(String, String)> = Vec::new(); + let mut type_mappings: Vec<(String, String, bool)> = Vec::new(); for item in module_mapping { let file_abs = crate_dir.join(item.file_path()); @@ -40,14 +40,13 @@ pub fn analyze_and_build_type_mapping_for( for ai in analyze_items { let full_path = if ai.is_foreign { - // Foreign item — use its own module path as-is format!("{}::{}", ai.module, ai.item_name) } else if ai.module.is_empty() { format!("{}::{}", module_path, ai.item_name) } else { format!("{}::{}::{}", module_path, ai.module, ai.item_name) }; - type_mappings.push((ai.item_name, full_path)); + type_mappings.push((ai.item_name, full_path, ai.is_module)); } } @@ -56,7 +55,7 @@ pub fn analyze_and_build_type_mapping_for( // Deduplicate by type name, keeping the first occurrence let mut seen = HashSet::new(); - type_mappings.retain(|(name, _)| seen.insert(name.clone())); + type_mappings.retain(|(name, _, _)| seen.insert(name.clone())); // Create output directory std::fs::create_dir_all(output_dir)?; @@ -66,14 +65,18 @@ pub fn analyze_and_build_type_mapping_for( let type_using_path = output_dir.join("type_using.rs"); let mut content_mapping = String::new(); - for (name, path) in &type_mappings { + for (name, path, _) in &type_mappings { content_mapping.push_str(&format!("{name} = {path}\n")); } std::fs::write(&output_path, content_mapping)?; let mut content_using = String::new(); - for (_, path) in &type_mappings { - content_using.push_str(&format!("use {path};\n")); + for (_, path, is_module) in &type_mappings { + if *is_module { + content_using.push_str(&format!("use {path}::*;\n")); + } else { + content_using.push_str(&format!("use {path};\n")); + } } std::fs::write(&type_using_path, content_using)?; |
