diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-28 19:59:21 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-28 19:59:21 +0800 |
| commit | e3c060b3281b6255c2cdebdcf2dda2a22a758662 (patch) | |
| tree | 2e98570320de3a591acfa7af7a6c45e0842b93e5 | |
| parent | a11628efe9288739f5066af20121a6cda114e5f5 (diff) | |
feat(macros): wrap gen_program output in a hidden module
Move pathf type resolution from `program_final_gen` to `gen_program` and
isolate generated code inside `__this_program_impl` to prevent namespace
pollution
| -rw-r--r-- | CHANGELOG.md | 11 | ||||
| -rw-r--r-- | mingling_macros/src/func/gen_program.rs | 105 | ||||
| -rw-r--r-- | mingling_macros/src/func/program_comp_gen.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/func/program_final_gen.rs | 15 |
4 files changed, 96 insertions, 39 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b53d3d6..2d9198b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,13 +60,16 @@ None #### Optimizations: -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. +1. **[`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. - 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`. +2. **[`macros:gen_program`]** Wrapped all code generated by `gen_program!()` inside a `__this_program_impl` module and re-exported it with `pub use __this_program_impl::*;`. This isolates the generated internal items (type aliases, trait implementations, and pathf-generated `use` statements) from the call site's module namespace, preventing name collisions and keeping generated machinery out of the caller's direct scope. - 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. + - The `Next` type alias, `Routable` impl for `ChainProcess<ThisProgram>`, and the `program_fallback_gen!()` / `program_final_gen!()` expansions are now all inside `pub mod __this_program_impl { ... }`, then re-exported publicly. + - Pathf integration: when the `pathf` feature is enabled, the `type_using.rs` file (generated by the build script) is loaded at compile time via `load_pathf_uses()` and emitted as `use ...;` statements **inside** the `__this_program_impl` module. Previously, pathf uses were injected via `include!()` inside the `ProgramCollect` impl block in `program_final_gen`; now they are loaded by `gen_program` itself and placed at the top of the hidden module. A `compile_error!` hint is emitted if the pathf file is missing or empty. + - When `pathf` is **disabled**, `__this_program_impl` emits `use super::*;` to bring the caller's parent scope types into the generated module, preserving existing behavior for projects that don't use pathf. + - Completion generation: removed `crate::` prefix from `CompletionSuggest` references in `program_comp_gen.rs`, since the generated code now lives inside `__this_program_impl` and no longer has a direct `crate` path to the user's crate root. The prefix became unnecessary because `CompletionSuggest` is expected to be in scope (e.g., via pathf glob re-exports or the `use super::*;` fallback). -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. + _No behavioral change for downstream code — all public items are re-exported with the same names. The `__this_program_impl` module is `#[doc(hidden)]` and not part of the public API._ #### Features: diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs index c5358bc..0c35318 100644 --- a/mingling_macros/src/func/gen_program.rs +++ b/mingling_macros/src/func/gen_program.rs @@ -15,33 +15,98 @@ pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream { #[cfg(not(feature = "comp"))] let comp_gen = quote! {}; + // When pathf is enabled, load the type_using.rs generated by the build script + // and emit its use statements so types from submodules are in scope. + #[cfg(feature = "pathf")] + let pathf_uses: Vec<proc_macro2::TokenStream> = { + let uses = load_pathf_uses(); + if uses.is_empty() { + // The file might not exist yet — emit a clear hint + let hint: proc_macro2::TokenStream = syn::parse_quote! { + compile_error!( + "pathf: `{}` not found or empty.\n\ + Make sure `build.rs` calls `mingling::build::analyze_and_build_type_mapping().unwrap();`\n\ + with features [\"build\", \"pathf\"] enabled." + ); + }; + vec![hint] + } else { + uses + } + }; + #[cfg(not(feature = "pathf"))] + let pathf_uses: Vec<proc_macro2::TokenStream> = Vec::new(); + + #[cfg(feature = "pathf")] + let super_use = quote! {}; + + #[cfg(not(feature = "pathf"))] + let super_use = quote! { + use super::*; + }; + TokenStream::from(quote! { - /// Alias for the current program type `crate::ThisProgram` - pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; - - impl ::mingling::Routable<crate::ThisProgram> for ::mingling::ChainProcess<crate::ThisProgram> - { - fn to_chain(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - match self { - ::mingling::ChainProcess::Ok((any, _)) => { - ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain)) + pub use __this_program_impl::*; + + #[doc(hidden)] + pub mod __this_program_impl { + #super_use + #(#pathf_uses)* + + /// Alias for the current program type `ThisProgram` + pub type Next = ::mingling::ChainProcess<ThisProgram>; + + impl ::mingling::Routable<ThisProgram> for ::mingling::ChainProcess<ThisProgram> + { + fn to_chain(self) -> ::mingling::ChainProcess<ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain)) + } + other => other, } - other => other, } - } - fn to_render(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - match self { - ::mingling::ChainProcess::Ok((any, _)) => { - ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer)) + fn to_render(self) -> ::mingling::ChainProcess<ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer)) + } + other => other, } - other => other, } } - } - #comp_gen - ::mingling::macros::program_fallback_gen!(); - ::mingling::macros::program_final_gen!(); + #comp_gen + ::mingling::macros::program_fallback_gen!(); + ::mingling::macros::program_final_gen!(); + } }) } + +/// Loads `type_using.rs` generated by the pathf build script and returns each +/// `use ...;` line as a token stream, ready to be emitted in the generated output. +#[cfg(feature = "pathf")] +fn load_pathf_uses() -> Vec<proc_macro2::TokenStream> { + let out_dir = match std::env::var("OUT_DIR") { + Ok(d) => d, + Err(_) => return Vec::new(), + }; + let crate_name = match std::env::var("CARGO_PKG_NAME") { + Ok(n) => n, + Err(_) => return Vec::new(), + }; + let path = std::path::Path::new(&out_dir) + .join(&crate_name) + .join("type_using.rs"); + let content = match std::fs::read_to_string(&path) { + Ok(c) => c, + Err(_) => return Vec::new(), + }; + content + .lines() + .map(|line| line.trim().to_string()) + .filter(|line| !line.is_empty()) + .filter_map(|line| line.parse::<proc_macro2::TokenStream>().ok()) + .collect() +} diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs index b6b2546..2fbb0e0 100644 --- a/mingling_macros/src/func/program_comp_gen.rs +++ b/mingling_macros/src/func/program_comp_gen.rs @@ -14,7 +14,7 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) + ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest::new((ctx, suggest))) } Err(_) => std::process::exit(1), } @@ -32,7 +32,7 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) + ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest::new((ctx, suggest))) } Err(_) => std::process::exit(1), } diff --git a/mingling_macros/src/func/program_final_gen.rs b/mingling_macros/src/func/program_final_gen.rs index e427f05..0eed1db 100644 --- a/mingling_macros/src/func/program_final_gen.rs +++ b/mingling_macros/src/func/program_final_gen.rs @@ -36,8 +36,8 @@ fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, pr } /// 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. +/// Types are expected to be in scope (e.g. via pathf glob re-exports), 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 } @@ -280,15 +280,6 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { quote! { u128 } }; - // 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! {}; - let expanded = quote! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(#repr_type)] @@ -306,8 +297,6 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { } impl ::mingling::ProgramCollect for #name { - #pathf_include - type Enum = #name; type ErrorDispatcherNotFound = ErrorDispatcherNotFound; type ErrorRendererNotFound = ErrorRendererNotFound; |
