声明式宏嵌套调用函数式过程宏报错,如何解决?能否正常运行?
问题:声明式宏间接调用过程宏导致数组长度不匹配错误
尝试通过声明式宏arr!间接调用函数式过程宏arr_proc!,而非直接调用arr_proc!,但编译时出现类型不匹配错误,预期长度为2的数组,实际得到长度为0的数组,疑似编译器未完成所有宏展开。请问该用法是否可行?如何解决?
过程宏代码
#[proc_macro] pub fn arr_proc(input: TokenStream) -> TokenStream { let repeat_expr: ExprRepeat = parse(input) .expect("Like arr!([Test::default(); 16])"); let mut len = 0; // 获取长度 if let Expr::Lit(expr_lit) = repeat_expr.len.deref() { if let Lit::Int(int_lit) = &expr_lit.lit { len = int_lit.base10_parse::<usize>().expect("Failed to parse integer literal"); } } let _expr = repeat_expr.expr; // 生成数组元素 let mut _all = quote!(); for _i in 0..len { _all = quote! { #_all #_expr, }; } // 包裹成数组 let arr = quote! { [ #_all ] }; arr.into() }
声明式宏代码
#[macro_export] macro_rules! arr { ($ele:expr; $repeat:literal) => { custom_proc_macro::arr_proc!([$ele; $repeat]) }; }
测试用例
#[test] fn test_arr_() { let a: [u32; 2] = arr![1_u32;2]; dbg!(a); }
错误信息
error[E0308]: mismatched types --> tests/custom_proc_macro_test.rs:48:23 | 48 | let a: [u32; 2] = arr![1_u32;2]; | -------- ^^^^^^^^^^^^^ 预期固定长度为2的数组,实际得到长度为0的数组 | | | | | 提示:可以考虑指定实际数组长度:`0` | 因此处类型标注导致预期 | = note: 预期数组 `[u32; 2]` 实际数组 `[_; 0]` = note: 该错误源自宏 `custom_proc_macro::arr_proc`,它由宏 `arr` 展开而来(在Nightly版本中,使用-Z macro-backtrace查看更多信息)
解决方案
这种用法完全可行,错误根源是过程宏的解析逻辑存在问题,而非宏展开机制的限制。
核心问题分析
声明式宏传递给arr_proc!的输入是[$ele; $repeat],这是一个数组字面量(ExprArray),但过程宏中错误地将其解析为重复表达式(ExprRepeat),导致解析失败后len保持初始值0,最终生成空数组[]。
修复方案
方案1:修改过程宏以支持数组字面量输入
调整过程宏的解析逻辑,先识别数组字面量,再提取其中的重复表达式部分:
use syn::{parse_macro_input, Expr, ExprArray, ExprRepeat, Lit, ExprLit}; use quote::quote; #[proc_macro] pub fn arr_proc(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // 先解析为通用表达式 let expr = parse_macro_input!(input as Expr); let (elem_expr, len) = match expr { Expr::Array(arr) => { // 检查是否是 [elem; len] 格式的数组 if arr.elems.len() == 1 { match arr.elems[0].as_ref() { Expr::Repeat(repeat) => { // 提取长度字面量 let len = match repeat.len.as_ref() { Expr::Lit(ExprLit { lit: Lit::Int(int_lit), .. }) => { int_lit.base10_parse::<usize>().expect("长度必须是整数字面量") } _ => panic!("长度必须是整数字面量"), }; (repeat.expr.clone(), len) } _ => panic!("数组内必须是 elem; len 格式的重复表达式"), } } else { panic!("仅支持 [elem; len] 格式的数组输入") } } _ => panic!("输入必须是数组字面量,格式为 [elem; len]"), }; // 使用重复展开语法生成数组元素,更简洁高效 let elements = (0..len).map(|_| &elem_expr); let arr = quote! { [ #(#elements),* ] }; arr.into() }
方案2:修改声明式宏的传递内容
直接将$ele; $repeat传递给过程宏,让过程宏直接解析重复表达式:
// 修改声明式宏 #[macro_export] macro_rules! arr { ($ele:expr; $repeat:literal) => { custom_proc_macro::arr_proc!($ele; $repeat) }; } // 对应简化后的过程宏 #[proc_macro] pub fn arr_proc(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let repeat_expr = parse_macro_input!(input as ExprRepeat); let len = match repeat_expr.len.as_ref() { Expr::Lit(ExprLit { lit: Lit::Int(int_lit), .. }) => { int_lit.base10_parse::<usize>().expect("长度必须是整数字面量") } _ => panic!("长度必须是整数字面量"), }; let elements = (0..len).map(|_| &repeat_expr.expr); let arr = quote! { [ #(#elements),* ] }; arr.into() }
验证效果
修复后,arr![1_u32;2]会被正确展开为[1_u32, 1_u32],与类型[u32;2]匹配,编译通过。
内容的提问来源于stack exchange,提问作者Colagy
相关产品推荐
相关产品推荐

