How do I define a macro which defines another macro when the inner macro takes arguments? - macros

Minimal code to reproduce:
macro_rules! test {
($name:ident: $count:expr) => {
macro_rules! $name {
($($v:expr),*) => {}
}
}
}
test!(yo: 123);
Got error:
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> src/lib.rs:4:15
|
4 | ($($v:expr),*) => {}
| ^^^^^^^^^
Removing $count:expr or changing $count:expr to another type like $count:block omits the error, but I really need it to be expr. What does the error mean?

This is a known issue (#35853). The current recommended workaround is to pass in the dollar sign $ as a separate token. You can then call yourself, passing in the $:
macro_rules! test {
($name:ident: $count:expr) => { test!($name: $count, $) };
($name:ident: $count:expr, $dol:tt) => {
macro_rules! $name {
($dol($v:expr),*) => {}
}
};
}
fn main() {
test!(yo: 2);
yo!(42);
}

Related

How to capture by reference in rust macro

I have a macro to generate match arms:
macro_rules! sort_by {
( $query:ident, $sort_by:expr, { $( $name:pat => $column:path,)+ } ) => {
match $sort_by.column {
$(
$name => if $sort_by.descending {
$query = $query.order_by($column.desc());
} else {
$query = $query.order_by($column.asc());
},
)+
}
}
}
and I want to call it like this:
sort_by!(query, sort_by.unwrap_or(Sort::desc("id")), {
"id" => table::id,
"customerName" => table::customer_name,
});
But I'm getting an error:
sort_by!(query, &sort_by.unwrap_or(Sort::desc("id")), {
^^^^^^^ value moved here in previous iteration of loop
So I have to call it like this:
let sort = sort_by.unwrap_or(Sort::desc("id"));
sort_by!(query, &sort, {
"id" => table::id,
"customerName" => table::customer_name,
});
What should I change to be able to use the expression directly in the macro invocation?
Using a macro is equivalent to substituting the code it expands to into its call site. This means if the macro expansion contains $sort_by multiple times, the code will evaluate the expression you pass in as $sort_by multiple times. If the expression consumes some variable, this will be invalid.
This is in contrast to how function calls work. If you pass an expression to a function, it will be evaluated before calling the function, and only the result is passed to the function.
If this is the source of your problem, you can fix it by assigning $sort_by to a local variable inside your macro expansion, and only access the local variable subsequently:
macro_rules! sort_by {
($query:ident, $sort_by:expr, { $($name:pat => $column:path,)+ }) => {
let sort_by = $sort_by;
match sort_by.column {
$(
$name => if sort_by.descending {
$query = $query.order_by($column.desc());
} else {
$query = $query.order_by($column.asc());
},
)+
}
}
}
(Note that I could not test this, since your example is incomplete.)

How to implement the Lispian cond macro?

Intended usage:
cond! {
x > 5 => 0,
x < 3 => 1,
true => -1
}
Should expand to:
if x > 5 { 0 } else if x < 3 { 1 } else if true { -1 }
Note that it doesn't produce a catch-all else { ... } suffix.
My attempt:
macro_rules! cond(
($pred:expr => $body:expr) => {{
if $pred {$body}
}};
($pred:expr => $body:expr, $($preds:expr => $bodies:expr),+) => {{
cond! { $pred => $body } else cond! { $($preds => $bodies),+ }
}};
);
However, the compiler complains about the else keyword.
error: expected expression, found keyword `else`
--> src/main.rs:32:34
|
32 | cond! { $pred => $body } else cond! { $($preds => $bodies),+ }
| ^^^^
Macros in Rust don't perform textual substitution like the C preprocessor does. Moreover, the result of a macro is already "parsed", so you can't just append something after the macro invocation that's supposed to be part of what the macro expands to.
In your case, you can't put an else after the first cond! invocation because the compiler has already finished parsing the if expression; you need to put the if and the else together. Likewise, when you invoke cond! again after the else, you need to add braces around the call, because the sequence else if does not begin a nested if expression.
macro_rules! cond {
($pred:expr => $body:expr) => {
if $pred { $body }
};
($pred:expr => $body:expr, $($preds:expr => $bodies:expr),+) => {
if $pred { $body } else { cond! { $($preds => $bodies),+ } }
};
}
Ultimately though, this macro is pretty much useless. An if expression without an else clause always has its type inferred to be (), so unless all the branches evaluate to () (or diverge), the expanded macro will produce type mismatch errors.

How can I use a macro to create an array of function names starting from a collection of function definitions?

I would like to create an array in a macro to transform something like:
let array = create_array!(
fn test() -> i32 { }
fn test1() { }
);
into
let array = [test, test1];
I tried this:
macro_rules! create_array {
() => {
};
(fn $func_name:ident () -> $return_type:ty $block:block $($rest:tt)*) => {
$func_name,
create_array!($($rest)*);
};
(fn $func_name:ident () $block:block $($rest:tt)*) => {
$func_name,
create_array!($($rest)*);
};
}
but it fails with the following error:
error: macro expansion ignores token `,` and any following
--> src/main.rs:11:19
|
11 | $func_name,
| ^
|
note: caused by the macro expansion here; the usage of `create_array!` is likely invalid in expression context
--> src/main.rs:27:18
|
27 | let array = [create_array!(
|
I also tried this:
macro_rules! create_array {
($(fn $func_name:ident () $( -> $return_type:ty )* $block:block)*) => {
[$($func_name),*]
};
}
but it fails with:
error: local ambiguity: multiple parsing options: built-in NTs block ('block') or 1 other option.
--> src/main.rs:22:19
|
22 | fn test() -> i32 { }
|
So how can I create an array in such a case?
The parsing ambiguity of -> vs $:block has been resolved as of Rust 1.20, so the second version you tried will now work as intended.
macro_rules! create_array {
($(fn $func_name:ident () $(-> $return_type:ty)* $block:block)*) => {
[$($func_name),*]
};
}
fn main() {
let test = "TEST";
let test1 = "TEST1";
let array = create_array! {
fn test() -> i32 {}
fn test1() {}
};
println!("{:?}", array);
}

Is it possible to prevent duplicate identical arguments to a macro in Rust?

There are certain rare cases where it may be useful to prevent duplicate arguments to a macro. One example is this elem(value, ...) macro to check if value is either A, B or C:
if (elem(value, A, B, C)) { .... }
Someone could accidentally pass in the same argument multiple times, e.g.:
if (elem(value, A, B, B)) { .... }
While this is valid Rust, it is almost certainly an accident and highly unlikely to be what the developer intended. This is a trivial example, actual error cases would be more complicated.
Is there a way to have the compiler warn/error when passing in duplicate arguments?
Arguments are not necessarily all constants, they could be mixed with variables too.
This is an actual bug I found in some code. While there's a limit macros/compilers can go to prevent mistakes, this could have been detected early if the macro didn't allow it. These kinds of mistakes should be found in code review but mistakes happen.
One way to do this (which isn't fool proof), could be to convert the identifiers to strings, then static-assert if any of the identifiers are exact matches. This has the obvious drawback that different identifiers may represent the same constant value. The same identifier could also be written so as to not compare, e.g.: A[0] vs A[ 0 ].
If the preprocessor/compiler can't do this easily, a fall-back solution may be some basic static checking tool.
I managed to do this with the C preprocessor.
One way to achieve what you want is the following:
macro_rules! unique_args {
($($idents:ident),*) => {
{
#[allow(dead_code, non_camel_case_types)]
enum Idents { $($idents,)* __CountIdentsLast }
}
};
}
macro_rules! _my_elem {
($val:expr, $($var:expr),*) => {{
$($val == $var)||*
}};
}
macro_rules! my_elem {
($($tt:tt)*) => {{
unique_args!($($tt)*);
_my_elem!($($tt)*)
}};
}
The idea is that having the same identifier twice will cause a compiler error because an enum cannot have duplicate variant names.
You can use this as such:
if my_elem!(w, x, y, z) {
println!("{}", w);
}
Here is an example with an error:
// error[E0428]: a value named `y` has already been defined in this enum
if my_elem!(w, x, y, y) {
println!("{}", w);
}
However, this will only work with identifiers.
If you want to use literals as well, you will need a macro with a different syntax to be able to differentiate between a literal and an identifier:
macro_rules! unique_idents {
() => {
};
($tt:tt) => {
};
($ident1:ident, $ident2:ident) => {
{
#[allow(dead_code, non_camel_case_types)]
enum Idents {
$ident1,
$ident2,
}
}
};
($ident:ident, lit $expr:expr) => {
};
($ident1:ident, $ident2:ident, $($tt:tt)*) => {
{
#[allow(dead_code, non_camel_case_types)]
enum Idents {
$ident1,
$ident2,
}
unique_idents!($ident1, $($tt)*);
unique_idents!($ident2, $($tt)*);
}
};
($ident:ident, lit $expr:expr, $($tt:tt)*) => {
unique_idents!($ident, $($tt)*);
};
(lit $expr:expr, $($tt:tt)*) => {
unique_idents!($($tt)*);
};
}
macro_rules! unique_literals {
() => {
};
($tt:tt) => {
};
(lit $lit1:expr, lit $lit2:expr) => {{
type ArrayForStaticAssert_ = [i8; 0 - (($lit1 == $lit2) as usize)];
}};
(lit $lit:expr, $ident:ident) => {
};
(lit $lit1:expr, lit $lit2:ident, $($tt:tt)*) => {{
unique_literals!(lit $lit1, lit $lit2);
unique_literals!(lit $lit1, $($tt)*);
unique_literals!(lit $lit2, $($tt)*);
}};
(lit $lit:expr, $ident:ident, $($tt:tt)*) => {
unique_literals!(lit $lit, $($tt)*);
};
($ident:ident, $($tt:tt)*) => {
unique_literals!($($tt)*);
};
}
macro_rules! unique_args2 {
($($tt:tt)*) => {{
unique_idents!($($tt)*);
unique_literals!($($tt)*);
}};
}
macro_rules! _elem {
() => {
false
};
($val:expr) => {
false
};
($val1:expr, $val2:expr) => {{
$val1 == $val2
}};
($val1:expr, lit $val2:expr) => {{
$val1 == $val2
}};
($val1:expr, $val2:expr, $($tt:tt)*) => {{
$val1 == $val2 || _elem!($val1, $($tt)*)
}};
($val1:expr, lit $val2:expr, $($tt:tt)*) => {{
$val1 == $val2 || _elem!($val1, $($tt)*)
}};
}
macro_rules! elem {
($($tt:tt)*) => {{
unique_args2!($($tt)*);
_elem!($($tt)*)
}};
}
The uniq_idents! macro uses the same trick as above.
The unique_literals! macro will cause a subtract with overflow error that is caught at compile time.
With these macros, you will need to prefix each literal by lit:
if elem!(w, x, lit 1, z) {
println!("{}", w);
}
Here are some examples of errors:
// error[E0428]: a value named `y` has already been defined in this enum
if elem!(w, x, y, y) {
println!("{}", w);
}
// error[E0080]: constant evaluation error
if elem!(w, x, lit 1, z, lit 1) {
println!("{}", w);
}
I think it is the best we can do without using a compiler plugin.
It is possible to improve these macros, but you get the idea.
Even though there is a stringify! macro that can be use to convert any expression to a string, I don't think we currently have a way to compare these strings at compile time (without a compiler plugin), at least until we have const fn.

What does `error: expected open delimiter` when calling rust macro mean?

I have such a macro:
macro_rules! expect_token (
([$($token:matchers, $result:expr)|+] <= $tokens:ident, $parsed_tokens:ident, $error:expr) => (
match $tokens.pop() {
$(
Some($token) => {
$parsed_tokens.push($token);
$result
},
)+
None => {
$parsed_tokens.reverse();
$tokens.extend($parsed_tokens.into_iter());
return NotComplete;
},
_ => return error(expr)
}
);
)
when I call it with expect_token!([Ident(name), name] <= tokens, parsed_tokens, "expected function name in prototype"); I get the error "error: expected open delimiter".
What does this error mean and what I am doing wrong?
P.S. If you are wondering what is the definition of identifiers like NotComplete, you can look at https://github.com/jauhien/iron-kaleidoscope/blob/master/src/parser.rs, but it is not relevant for this question as far as I understand, as the problem is not with the macro body, but with its invocation.
Ok, I have found the response: matchers in macros invocation should be enclosed in parenthesis. The problem was in my misunderstanding of matchers as left hand side of match rules, while they are lhs of the => in macro rules, which is clearly stated in documentation.
P.S. What about the whole macros I gave as example it is wrong anyway. )