I'd like to create a 'def_union' macro that creates a union-like enum with From support.
macro_rules! def_union
{
($name:ident $(< $( $param:tt $( : $bound_lt0:tt $(+ $bound_lt:tt )* )? ),+ >)?
{
$($type_name:ident : $type:ty),*
}) =>
{
enum $name $(< $( $param $( : $bound_lt0 $(+ $bound_lt )* )? ),+ >)?
{
$(
$type_name($type)
),*
}
$(
impl $(< $( $param:tt $( : $bound_lt0:tt $(+ $bound_lt:tt )* )? ),+ >)?
From<$type> for $name $(< $( $param $( : $bound_lt0 $(+ $bound_lt )* )? ),+ >)?
{
fn from(value: $type) -> $name
{
$name::$type_name(value)
}
}
)*
};
}
trait UnionValue {}
def_union!
(
UnionType<Value0: UnionValue, Value1: UnionValue, String>
{
Value0: Value0,
Value1: Value1,
Other: String
}
);
fn main()
{
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2c2f13988dc3b51431db4fe0b40a6706
Unfortunatelly the output is:
Compiling playground v0.0.1 (/playground)
error: meta-variable `param` repeats 1 time, but `r#type` repeats 3 times
--> src/main.rs:15:10
|
15 | $(
| __________^
16 | | impl $(< $( $param:tt $( : $bound_lt0:tt $(+ $bound_lt:tt )* )? ),+ >)?
17 | | From<$type> for $name $(< $( $param $( : $bound_lt0 $(+ $bound_lt )* )? ),+ >)?
18 | | {
... |
23 | | }
24 | | )*
| |_________^
error: could not compile `playground` due to previous error
The problem is with the generic parameters. I don't know a workaround.
EDIT: Added discriminator trait for the generic parameters.
I will assume you want to solve the error and not just implement particular macro.
To solve this error you may choose to use helper macro. The trick is not apparent at first so lets show the code. (with comments)
macro_rules! def_union {
(
$name:ident$(<$($param_name:ident$(($($param_bounds:tt)*))?),+>)? {
$($type_name:ident: $type:ty),*
}
) => {
enum $name $(<$($param_name$(: $($param_bounds)*)?),+>)? {
$(
$type_name($type)
),*
}
def_union!(#$name ($)
($(<$($param_name$(: $($param_bounds)*)?),+>)?)
($name $(<$($param_name),*>)?));
$(
$name!($type_name $type);
)*
};
// here we generate helper macro that will break the repetition paradox.
// all of the outer repeating parts are passed to later outputted macro
// the `$t` is used as escape token that allows us to output new macro
(#$name:ident ($t:tt) ($($generics:tt)*) ($($type:tt)*)) => {
macro_rules! $name {
($t name:ident $t type:ty) => {
impl$($generics)* From<$t type> for $($type)* {
fn from(value: $t type) -> Self {
Self::$t name(value)
}
}
};
}
};
}
trait UnionValue {}
// I removed generic parameters because they were conflicting each other.
// I also altered syntax a bit to simplify the macro, you can choose to
// make it more like rust syntax
def_union!(
UnionType<Value0(UnionValue)> {
Value0: Value0,
Value2: usize
}
);
fn main() {}
Related
I'm just getting going with Rust, and am trying to smooth over a couple inconveniences using macros. The first is that the derive Defaults macros don't support const functions so they don't support static objects. For this, I got help and adapted this macro I was given, it works well:
#[macro_export]
macro_rules! define_struct_with_const_defaults {
($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
#[derive(Clone,Copy)]
$v struct $name {
$(
$f_name: $f_type,
)*
}
impl $name {
pub const fn new() -> $name {
$name {
$(
$f_name: $f_default,
)*
}
}
}
}
}
So the next thing I wanted to do was get painless structure access for unit testing with python going. So I thought I could do something like this:
use pyo3::prelude::*;
#[macro_export]
macro_rules! define_struct_with_const_defaults {
($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
#[derive(Clone,Copy)]
#[pyclass]
$v struct $name {
$(
#[pyo3(get, set)] $f_name: $f_type,
)*
}
impl $name {
pub const fn new() -> $name {
$name {
$(
$f_name: $f_default,
)*
}
}
}
}
}
But that doesn't work, the complaint is "cannot find attribute pyclass in this scope"
On the other hand, if I do this, it works:
use pyo3::prelude::*;
#[pyclass]
struct bar {
baz: i32,
}
This is beyond my level of understanding. It seems to be related to nesting macros, I'm wondering whether it has something to do with order of evaluation.
Eventually, I'd like to conditionally compile the pyo3 stuff with a config macro, depending on whether I'm in a unit test environment, but I can't even get this far ...
Any help would be appreciated in understanding what is going on here.
The problem is probably that the macro will insert #[pyclass] at the callsite of it. So you have to put your use pyo3::prelude::*; there or even better include the full path in the macro to avoid name conflicts:
#[macro_export]
macro_rules! define_struct_with_const_defaults {
($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
#[derive(Clone,Copy)]
#[::pyo3::prelude::pyclass]
$v struct $name {
$(
#[pyo3(get, set)] $f_name: $f_type,
)*
}
impl $name {
pub const fn new() -> $name {
$name {
$(
$f_name: $f_default,
)*
}
}
}
}
}
I try to generate a struct from a macro looking like that:
decl_struct! {
MyStruct {
mut foo,
const bar,
}
}
The thing is that I want to filter out the fields marked as const. I have tried with the following code, but it does not function due to the fact that the outer macro is evaluated first:
macro_rules! decl_struct {
{
$name:ident {
$( $kind:ident $member:ident, )*
}
} => {
decl_struct!(#gen_struct $name { $( decl_struct!(#gen_struct_member $kind $member) )* } );
};
// Filters the `const` members out:
(#gen_struct_member mut $member:ident) => ($member,);
(#gen_struct_member const $member:ident) => ();
// Generate the whole struct:
{
#gen_struct $name:ident {
$( $member:ident, )*
}
} => {
struct $name {
$( $member:ident : u32 /*Dummy type*/, )*
}
};
}
decl_struct! {
MyStruct {
mut foo,
const bar,
}
}
Link to the playground.
What can be an alternative approach?
As documented in the reference:
Macros can expand to expressions, statements, items (including traits, impls, and foreign items), types, or patterns.
Notably, they can't expand to singular field definitions (as attempted by your #gen_struct_member invocations). However, for your particular problem, you can instead simplify to a single macro invocation with a bit of clever repetition:
macro_rules! decl_struct {
{
$name:ident {
$( const $initial:ident, )*
$(
mut $mut:ident,
$( const $const:ident, )*
)*
}
} => {
struct $name {$(
$mut: u32,
)*}
};
}
Playground.
I have an existing macro definition that captures the attributes. Inside that definition, I'm trying to filter and set the attributes selectively.
I have a peculiar problem where I have to set attributes for match arms in one of these cases.
macro_rules! set_mytrait {
(
$target:ident
$(
$( #[$cfgs:meta] )*
$field:ident
),*
$(,)*
) => {
impl $crate::MyTrait for $target
{
fn perform(&self) -> Option<&(dyn $crate::Error + 'static)> {
match self { $(
// Replace the below attributes with a filter function that returns only some of the attributes
$(#[$cfgs])*
$field => {
// doSomething($field);
None
}
)* }
}
}
}
}
I know that we cannot yet use macros in attributes according to this.
Apart from that, I've tried two options and both seem to have hit roadblocks.
Option 1
Approach: Using macro that takes just attributes as arguments and returns filtered attributes.
Implementation:
piped_attribs
#[macro_export]
macro_rules! piped_attribs {
(
$( $attr:tt )*
) => {
// Use filter logic here
$( #[$attr] )*
};
}
called_at
match self { $(
$crate::piped_attribs! { $( $cfgs ),* }
$field => {
// doSomething($field)
None
}
)* }
Problem: Seems like emitting attributes doesn't work in this context and I require to attach it to an item (source)
Option 2
Approach: Using macro that takes in two args. One for attributes and next for the match arm.
Implementation:
piped_attribs
#[macro_export]
macro_rules! piped_attribs {
(
[ $( $attr:tt )* ]
{ $rest: tt }
) => {
// Use filtering logic here
$( #[$attr] )*
$rest,
};
}
called at
match self { $(
$crate::piped_attribs! {
[$( $cfgs ),*]
{$field => {
// doSomething($field);
None
}}
}
)* }
Problem: I discovered that apparently we cannot return match "arms" from an attribute as of now. issue
Finally... the question
Is any of these approaches feasible and if not, is there a better way to look at this issue?
Is it actually not possible to create a macro like this or am I doing it wrong:
sample!("hello", "there") =>
println!("{:?}", "hello");
println!("{:?}", "there");
sample!("hello", "there", a "second type", a "another second type") =>
println!("{:?}", "hello");
println!("{:?}", "there");
println!("second {:?}", "second type");
println!("second {:?}", "another second type");
What I've tried is this (playground link):
macro_rules! sample {
(
$( $first:literal ),*
$( a $second:literal ),*
) => {
$(
println!("{:?}", $first);
)*
$(
println!("second {:?}", $second);
)*
};
}
Which fails with:
error: no rules expected the token `a`
--> main.rs:18:20
|
1 | macro_rules! sample {
| ------------------- when calling this macro
...
18 | sample!("hello", a "testing");
| ^ no rules expected this token in macro call
error: aborting due to previous error
Rust macros are really strict with their separators.
macro_rules! sample {
(
$( $first:literal, )*
$( a $second:literal ),*
) => {
$(println!("{:?}", $first);)*
$(println!("second {:?}", $second);)*
};
}
fn main() {
sample!("hello", a "testing");
}
This sample works, can you spot the change? I moved the comma from outside the first $( ... ) to inside. The difference is:
$( $a:literal ),* accepts only "a", "b", "c" (no trailing comma allowed)
$( $a:literal, )* accepts only "a", "b", "c", (trailing comma required)
In your macro, the in-between comma doesn't match as part of either the first or second repetition. The error is basically saying it expected another $first instead of a $second since that's what the repetition says.
You can potentially fix it by introducing an optional comma:
macro_rules! sample {
(
$( $first:literal ),*
$(,)? // <----------------
$( a $second:literal ),*
) => {
$(println!("{:?}", $first);)*
$(println!("second {:?}", $second);)*
};
}
which is more lenient but would allow weird things like this, which may or may not be okay depending on what you want.
sample!("hello", "there",);
sample!(, a "testing");
sample!("hello" a "testing");
Unfortunately, I don't know a perfect solution without using different arms like so:
macro_rules! sample {
($( $first:literal ),*) => { };
($( $first:literal, )* $( a $second:literal ),+) => { };
($( a $second:literal ),*) => { };
}
fn main() {
sample!("hello", "there");
sample!("hello", "there", a "testing");
sample!(a "second type", a "another second type");
// sample!("hello", "there",);
// sample!(, a "testing");
// sample!("hello" a "testing");
}
See also:
How do I create a Rust macro with optional parameters using repetitions?
How to allow optional trailing commas in macros?
You're doing it almost right- the trailing comma is tripping up the compiler. Removing the comma works just fine. If you are wondering why, it is because macro_rules invocations are quite picky. When it reads the trailing comma, it looks at the next token, which is a, but since that's not a literal, instead of doing the more sensible thing, which is checking the next pattern, it just gives up.
Take a look at this implementation:
impl consensus::Trait for Runtime {
type Log = Log;
type SessionKey = AuthorityId;
// The Aura module handles offline-reports internally
// rather than using an explicit report system.
type InherentOfflineReport = ();
}
How is Log defined? There is no use clause for importing this symbol.
Running
cargo rustc -- -Z unstable-options --pretty=expanded
does not show any Log entry with type clause. It does show other macro declarations after expanding macros at level 0, but I am not sure if this relevant.
I tried using the Atom IDE because it automatically parses the files and lets you find the definition of the symbols, but it did not help.
How can I find how Log is defined?
Log is defined by construct_runtime macro.
Here are some relevant code:
construct_runtime macro:
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L79
macro_rules! construct_runtime {
(
pub enum $runtime:ident with Log ($log_internal:ident: DigestItem<$( $log_genarg:ty ),+>)
where
Block = $block:ident,
NodeBlock = $node_block:ty,
UncheckedExtrinsic = $uncheckedextrinsic:ident
{
$( $rest:tt )*
}
)
calling __decl_outer_log
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L267
$crate::__decl_outer_log!(
$runtime;
$log_internal < $( $log_genarg ),* >;
{};
$(
$name: $module:: $( < $module_instance >:: )? { $( $modules $( ( $( $modules_args )* ) )* )* }
)*
);
__decl_outer_log macro
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L706
macro_rules! __decl_outer_log {
(
$runtime:ident;
$log_internal:ident <$( $log_genarg:ty ),+>;
{ $( $parsed:tt )* };
$name:ident: $module:ident:: $(<$module_instance:ident>::)? {
Log ( $( $args:ident )* ) $( $modules:ident $( ( $( $modules_args:ident )* ) )* )*
}
$( $rest:tt )*
) => {
calling impl_outer_log
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L763
(
$runtime:ident;
$log_internal:ident <$( $log_genarg:ty ),+>;
{ $(
$parsed_modules:ident $(< $parsed_instance:ident >)? ( $( $parsed_args:ident )* )
)* };
) => {
$crate::paste::item! {
$crate::runtime_primitives::impl_outer_log!(
pub enum Log($log_internal: DigestItem<$( $log_genarg ),*>) for $runtime {
$( [< $parsed_modules $(_ $parsed_instance)? >] $(< $parsed_modules::$parsed_instance >)? ( $( $parsed_args ),* ) ),*
}
);
}
};
impl_outer_log macro:
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/core/sr-primitives/src/lib.rs#L630
macro_rules! impl_outer_log {
(
$(#[$attr:meta])*
pub enum $name:ident ($internal:ident: DigestItem<$( $genarg:ty ),*>) for $trait:ident {
$( $module:ident $(<$instance:path>)? ( $( $sitem:ident ),* ) ),*
}
)
which actually declare and implement the Log struct
You should be able to see the result when cargo expand the runtime crate.