如何将参数从生成的函数传递给过程宏中的另一个函数?
How do I pass arguments from a generated function to another function in a procedural macro?
我正在尝试创建一个从函数签名生成两个函数的宏。由于函数的签名取决于传递给宏的内容,因此函数参数的数量是变化的。
我想创建的函数是 (i) 一个 内部函数 ,它实际上做了一些事情(我能够根据需要生成)和 (ii) public function 应该环绕内部函数。函数签名在参数方面是相同的。因此,将传递给 public 函数的所有参数传递到内部函数应该很容易。
以下代码片段显示了 public 函数的简化宏生成。我怎样才能正确传递参数? (#params_from_public_function
)
let public_function_ident = ...;
let internal_function_ident = ...;
// ast_function_stub-Type: ForeignItemFn
let params_from_public_function: Punctuated<FnArg, Comma> = ast_function_stub.sig.inputs.clone();
quote! {
pub fn #public_name(#params_from_public_function) {
#internal_function_ident(#params_from_public_function);
}
}
这里有两个示例,结果宏应该如何工作:
generate_both_functions!(fn foo(someParam: &str) -> u8;);
// What the macro should generate:
fn _foo(someParam: &str) -> u8 { // Internal function
// Some internal stuff happens here
}
pub fn foo(someParam: &str) { // Public function
_foo(someParam);
}
// Second example (having two instead of one argument and different types)
generate_both_functions!(fn bar(param1: i32, param2: String) -> String;);
// What the macro should generate
fn _bar(param1: i32, param2: String) -> String {
// Internal stuff again
}
fn bar(param1: i32, param2: String) {
_bar(param1, param2);
}
澄清一下:我能够正确解析宏的输入 (generate_both_functions!
) 并从 AST 中提取函数参数元数据。但是,我无法将实际输入动态传递给内部函数。
这在一般情况下是不可能的。考虑以下函数签名,它在参数位置使用解构来提取其第一个参数的字段:
fn bar(Foo { a, b, .. }: Foo)
由于 Foo
已被解构,宏无法生成将其传递给具有完全相同签名的另一个函数的代码。您可能会尝试重建 Foo
并像 _bar(Foo { a, b })
一样调用 _bar
,但由于 ..
某些字段已永久丢失且无法重建。
这里唯一剩下的选项是从模式中提取绑定集(这不是微不足道的),并生成一个函数,将每个绑定作为单独的参数。但这也是不可能的。我们必须生成像 fn _bar(a: A, b: B)
这样的签名,其中 A
和 B
是 a
和 b
的类型。但是宏看不到类型 Foo
的声明,因此无法确定这些类型是什么。
备选方案
可以支持每个参数都具有 $ident: $type
形式的常见情况。在这种情况下,您需要做的就是检查每个 syn::FnArg
上的 pat
字段是否为 Pat::Ident
并获取参数的名称。然后你可以为 _bar
生成一个匹配的签名并将每个参数传入。
如果没有外部代码需要调用_bar
,可以做成by-move闭包:
fn bar(/* any signature you like */) {
let _bar = move || { /* body goes here, using any arguments from bar */ };
_bar();
}
这应该继承外层函数的所有绑定
无缝,但外部函数将不再能够使用它们
之后,这可能会破坏交易。
首先:谢谢@AlphaModder!
因为我不想支持解构案例,所以我选择了您的第一个替代方案。我想解释一下我是如何最终实现我想要的,以便有类似问题的人能够比我更快地解决它。
首先,我使用 ItemFn
-Struct 而不是使用 parse_quote!
-Macro 构造函数本身:
// The idents from both functions
let internal_function_ident = ...;
let public_function_ident = ...;
// The arguments with the following pattern: $ident: $type, $ident: $type, ...
let params_from_public_function: Punctuated<FnArg, Comma> = ast_function_stub.sig.inputs.clone();
// Transform the arguments to the pattern: $ident, $ident, ...
let transformed_params = transform_params(params_from_public_function.clone());
// Assemble the function
ItemFn {
...
sig: syn::Signature {
...
ident: public_function_ident,
inputs: params_from_public_function
},
block: parse_quote!({
#internal_function_ident#transformed_params;
})
}
transform_params
-函数看起来像这样:
fn transform_params(params: Punctuated<syn::FnArg, syn::token::Comma>) -> Expr {
// 1. Filter the params, so that only typed arguments remain
// 2. Extract the ident (in case the pattern type is ident)
let idents = params.iter().filter_map(|param|{
if let syn::FnArg::Typed(pat_type) = param {
if let syn::Pat::Ident(pat_ident) = *pat_type.pat.clone() {
return Some(pat_ident.ident)
}
}
None
});
// Add all idents to a Punctuated => param1, param2, ...
let mut punctuated: Punctuated<syn::Ident, Comma> = Punctuated::new();
idents.for_each(|ident| punctuated.push(ident));
// Generate expression from Punctuated (and wrap with parentheses)
let transformed_params = parse_quote!((#punctuated));
transformed_params
}
我正在尝试创建一个从函数签名生成两个函数的宏。由于函数的签名取决于传递给宏的内容,因此函数参数的数量是变化的。
我想创建的函数是 (i) 一个 内部函数 ,它实际上做了一些事情(我能够根据需要生成)和 (ii) public function 应该环绕内部函数。函数签名在参数方面是相同的。因此,将传递给 public 函数的所有参数传递到内部函数应该很容易。
以下代码片段显示了 public 函数的简化宏生成。我怎样才能正确传递参数? (#params_from_public_function
)
let public_function_ident = ...;
let internal_function_ident = ...;
// ast_function_stub-Type: ForeignItemFn
let params_from_public_function: Punctuated<FnArg, Comma> = ast_function_stub.sig.inputs.clone();
quote! {
pub fn #public_name(#params_from_public_function) {
#internal_function_ident(#params_from_public_function);
}
}
这里有两个示例,结果宏应该如何工作:
generate_both_functions!(fn foo(someParam: &str) -> u8;);
// What the macro should generate:
fn _foo(someParam: &str) -> u8 { // Internal function
// Some internal stuff happens here
}
pub fn foo(someParam: &str) { // Public function
_foo(someParam);
}
// Second example (having two instead of one argument and different types)
generate_both_functions!(fn bar(param1: i32, param2: String) -> String;);
// What the macro should generate
fn _bar(param1: i32, param2: String) -> String {
// Internal stuff again
}
fn bar(param1: i32, param2: String) {
_bar(param1, param2);
}
澄清一下:我能够正确解析宏的输入 (generate_both_functions!
) 并从 AST 中提取函数参数元数据。但是,我无法将实际输入动态传递给内部函数。
这在一般情况下是不可能的。考虑以下函数签名,它在参数位置使用解构来提取其第一个参数的字段:
fn bar(Foo { a, b, .. }: Foo)
由于 Foo
已被解构,宏无法生成将其传递给具有完全相同签名的另一个函数的代码。您可能会尝试重建 Foo
并像 _bar(Foo { a, b })
一样调用 _bar
,但由于 ..
某些字段已永久丢失且无法重建。
这里唯一剩下的选项是从模式中提取绑定集(这不是微不足道的),并生成一个函数,将每个绑定作为单独的参数。但这也是不可能的。我们必须生成像 fn _bar(a: A, b: B)
这样的签名,其中 A
和 B
是 a
和 b
的类型。但是宏看不到类型 Foo
的声明,因此无法确定这些类型是什么。
备选方案
可以支持每个参数都具有
$ident: $type
形式的常见情况。在这种情况下,您需要做的就是检查每个syn::FnArg
上的pat
字段是否为Pat::Ident
并获取参数的名称。然后你可以为_bar
生成一个匹配的签名并将每个参数传入。如果没有外部代码需要调用
_bar
,可以做成by-move闭包:fn bar(/* any signature you like */) { let _bar = move || { /* body goes here, using any arguments from bar */ }; _bar(); }
这应该继承外层函数的所有绑定 无缝,但外部函数将不再能够使用它们 之后,这可能会破坏交易。
首先:谢谢@AlphaModder!
因为我不想支持解构案例,所以我选择了您的第一个替代方案。我想解释一下我是如何最终实现我想要的,以便有类似问题的人能够比我更快地解决它。
首先,我使用 ItemFn
-Struct 而不是使用 parse_quote!
-Macro 构造函数本身:
// The idents from both functions
let internal_function_ident = ...;
let public_function_ident = ...;
// The arguments with the following pattern: $ident: $type, $ident: $type, ...
let params_from_public_function: Punctuated<FnArg, Comma> = ast_function_stub.sig.inputs.clone();
// Transform the arguments to the pattern: $ident, $ident, ...
let transformed_params = transform_params(params_from_public_function.clone());
// Assemble the function
ItemFn {
...
sig: syn::Signature {
...
ident: public_function_ident,
inputs: params_from_public_function
},
block: parse_quote!({
#internal_function_ident#transformed_params;
})
}
transform_params
-函数看起来像这样:
fn transform_params(params: Punctuated<syn::FnArg, syn::token::Comma>) -> Expr {
// 1. Filter the params, so that only typed arguments remain
// 2. Extract the ident (in case the pattern type is ident)
let idents = params.iter().filter_map(|param|{
if let syn::FnArg::Typed(pat_type) = param {
if let syn::Pat::Ident(pat_ident) = *pat_type.pat.clone() {
return Some(pat_ident.ident)
}
}
None
});
// Add all idents to a Punctuated => param1, param2, ...
let mut punctuated: Punctuated<syn::Ident, Comma> = Punctuated::new();
idents.for_each(|ident| punctuated.push(ident));
// Generate expression from Punctuated (and wrap with parentheses)
let transformed_params = parse_quote!((#punctuated));
transformed_params
}