Skip to content

Instantly share code, notes, and snippets.

@ExpHP
Created September 8, 2019 16:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ExpHP/8ffa060c24c424d8a9a7086bafab201c to your computer and use it in GitHub Desktop.
Save ExpHP/8ffa060c24c424d8a9a7086bafab201c to your computer and use it in GitHub Desktop.
cartesian product macro
/// Higher-order macro that iterates over a cartesian product.
///
/// Useful for generating impls involving opaque traits of the sort
/// sometimes seen used as bounds in public APIs.
///
/// It takes a number of groups of token trees and a suitable definition
/// for a callback macro, and it calls the macro with one token tree from
/// each group in order.
///
/// See the examples module in the source for example usage.
macro_rules! cartesian {
(
$([$($groups:tt)*])*
for_each!($($mac_match:tt)*)
=> {$($mac_body:tt)*}$(;)*
)
=> {
// (this fixed name gets overwritten on each use. Technically, using a fixed name
// makes this macro not "re-entrant", in the sense that you can't call it in a nested
// fashion... but there is no reason to do so, because the macro has a monolithic design)
macro_rules! __cartesian__user_macro {
($($mac_match)*) => {$($mac_body)*};
}
cartesian__!{ @product::next($([$($groups)*])*) -> (__cartesian__user_macro!()) }
};
}
/// implementation detail, go away
macro_rules! cartesian__ {
(@product::next([$($token:tt)+] $($rest:tt)*) -> $cb:tt)
=> { cartesian__!{ @product::unpack([$($token)+] $($rest)*) -> $cb } };
// base case; direct product of no arguments
(@product::next() -> ($mac:ident!($($args:tt)*)))
=> {$mac!{$($args)*}};
// Each direct product in the invocation incurs a fixed number of recursions
// as we replicate the macro. First, we must smash anything we want to replicate
// into a single tt that can be matched without repetitions. Do this to `rest`.
(@product::unpack([$($token:tt)*] $($rest:tt)*) -> $cb:tt)
=> {cartesian__!{ @product::unpack_2([$($token)*] [$($rest)*]) -> $cb }};
// Replicate macro for each token.
(@product::unpack_2([$($token:tt)*] $rest:tt) -> $cb:tt)
=> { $( cartesian__!{ @product::unpack_3($token $rest) -> $cb } )* };
// Expand the unparsed arguments back to normal;
// add the token into the macro call
(@product::unpack_3($token:tt [$($rest:tt)*]) -> ($mac:ident!($($args:tt)*)))
=> {cartesian__!{ @product::next($($rest)*) -> ($mac!($($args)*$token)) }};
}
mod examples {
trait Trait { }
// NOTE: Braces around the alternatives are not strictly necessary
// (cartesian simply iterates over token trees), but they tend
// to help group tokens and resolve any would-be ambiguities in
// the callback's match pattern.
cartesian!{
[{i32} {u32}]
[{0} {1} {2} {3}]
for_each!({$T:ty} {$n:expr})
=> {
impl Trait for [$T; $n] { }
}
}
#[test]
fn example_works() {
fn assert_trait<T:Trait>() {}
assert_trait::<[u32; 0]>();
assert_trait::<[i32; 2]>();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment