aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md8
-rw-r--r--mingling_macros/src/func/gen_program.rs10
-rw-r--r--mingling_macros/src/func/program_final_gen.rs121
-rw-r--r--mingling_macros/src/systems/dispatch_tree_gen.rs43
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs14
-rw-r--r--mingling_pathf/src/type_mapping_builder.rs17
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)?;