From e6c73a9cbe3bf9d19a6c66cd399c51f151447d75 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Mon, 29 Jun 2026 14:22:35 +0800 Subject: feat(macros): add pathf type resolution for downcasts --- mingling_macros/src/lib.rs | 110 +++++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 29 deletions(-) (limited to 'mingling_macros/src/lib.rs') diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 43747aa..579a686 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -1801,6 +1801,54 @@ 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`. +/// Always compiled; returns empty map when pathf feature is not enabled. +fn load_pathf_map() -> std::collections::HashMap { + if !cfg!(feature = "pathf") { + return std::collections::HashMap::new(); + } + let out_dir = std::env::var("OUT_DIR").ok(); + let crate_name = std::env::var("CARGO_PKG_NAME").ok(); + match (out_dir, crate_name) { + (Some(dir), Some(name)) => { + let path = std::path::Path::new(&dir).join(&name).join("type_using.rs"); + match std::fs::read_to_string(&path) { + Ok(content) => 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(), + Err(_) => std::collections::HashMap::new(), + } + } + _ => std::collections::HashMap::new(), + } +} + +/// Resolves a type name to its full path token stream using the pathf mapping. +fn resolve_type( + name: &str, + map: &std::collections::HashMap, +) -> proc_macro2::TokenStream { + if let Some(full_path) = map.get(name) { + syn::parse_str::(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 } + } +} + /// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.) /// are poisoned. #[proc_macro] @@ -1849,6 +1897,21 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { .map(|s| syn::parse_str::(s).unwrap()) .collect(); + let pathf_map: std::collections::HashMap = if cfg!(feature = "pathf") { + load_pathf_map() + } else { + std::collections::HashMap::new() + }; + + let pathf_uses: Vec = 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 = structural_renderers .iter() @@ -1861,6 +1924,8 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { any: ::mingling::AnyOutput, setting: &::mingling::StructuralRendererSetting, ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { + #[allow(unused_imports)] + #(#pathf_uses)* match any.member_id { #(#structural_renderer_tokens)* _ => { @@ -1925,6 +1990,8 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { #[cfg(feature = "comp")] let comp = quote! { fn do_comp(any: &::mingling::AnyOutput, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { + #[allow(unused_imports)] + #(#pathf_uses)* match any.member_id { #(#completion_tokens)* _ => ::mingling::Suggest::FileCompletion, @@ -1943,12 +2010,14 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { } 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); 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::<#variant_ident>().unwrap_unchecked() }; - <#struct_ident as ::mingling::Renderer>::render(value, __renderer_inner_result); + let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; + <#resolved_struct as ::mingling::Renderer>::render(value, __renderer_inner_result); } } }).collect(); @@ -1965,12 +2034,14 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { // 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); 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::<#variant_ident>().unwrap_unchecked() }; - let fut = async { <#struct_ident as ::mingling::Chain>::proc(value).await }; + let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; + let fut = async { <#resolved_struct as ::mingling::Chain>::proc(value).await }; ::std::boxed::Box::pin(fut) } } @@ -1980,12 +2051,14 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { .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); 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::<#variant_ident>().unwrap_unchecked() }; - <#struct_ident as ::mingling::Chain>::proc(value) + let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; + <#resolved_struct as ::mingling::Chain>::proc(value) } } }) @@ -2040,30 +2113,7 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { quote! { u128 } }; - let pathf_include = if cfg!(feature = "pathf") { - let out_dir = std::env::var("OUT_DIR").ok(); - let crate_name = std::env::var("CARGO_PKG_NAME").ok(); - - match (out_dir, crate_name) { - (Some(dir), Some(name)) => { - let path = std::path::Path::new(&dir).join(&name).join("type_using.rs"); - match std::fs::read_to_string(&path) { - Ok(content) => { - let tokens: proc_macro2::TokenStream = content.parse().unwrap_or_default(); - tokens - } - Err(_) => quote! {}, - } - } - _ => quote! {}, - } - } else { - quote! {} - }; - let expanded = quote! { - #pathf_include - #[derive(Debug, PartialEq, Eq, Clone)] #[repr(#repr_type)] #[allow(nonstandard_style)] @@ -2096,6 +2146,8 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { #render_fn #do_chain_fn fn render_help(any: ::mingling::AnyOutput, __renderer_inner_result: &mut ::mingling::RenderResult) { + #[allow(unused_imports)] + #(#pathf_uses)* match any.member_id { #(#help_tokens)* _ => (), -- cgit