aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-28 19:10:17 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-28 19:10:17 +0800
commitbf35af6bbf30492bcd23eb6103fdac3382dd6d33 (patch)
treedb4485c91a78ce1219f9c7774a2988ccce2897be /mingling_macros
parent045c6aab213aadf4dbb66270ec0d203d7ec762a6 (diff)
refactor(pathf): move type resolution from compile-time to build-time
Diffstat (limited to 'mingling_macros')
-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
3 files changed, 36 insertions, 138 deletions
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