I have a big tt-muncher decl-macro:
macro_rules! all_ranges {
(@$p:tt [$($head:tt)*] []) => { $($head)* };
(@process [$($head:tt)*] [@range $($tail:tt)*]) => {
all_ranges!{@{Range<StrIndex>} [$($head)* Range<StrIndex>] [$($tail)*]}
all_ranges!{@{Range<StrIndex>} [$($head)* RangeFrom<StrIndex>] [$($tail)*]}
all_ranges!{@{Range<StrIndex>} [$($head)* RangeInclusive<StrIndex>] [$($tail)*]}
all_ranges!{@{Range<StrIndex>} [$($head)* RangeTo<StrIndex>] [$($tail)*]}
all_ranges!{@{Range<StrIndex>} [$($head)* RangeToInclusive<StrIndex>] [$($tail)*]}
};
(@{$($range:tt)*} [$($head:tt)*] [@range $($tail:tt)*]) => {
all_ranges!{@{$($range)*} [$($head)* $($range)*] [$($tail)*]}
};
(@$p:tt [$($head:tt)*] [{$($next:tt)*} $($tail:tt)*]) => {
all_ranges!{@$p [$($head)* {all_ranges!{@$p [] [$($next)*]}}] [$($tail)*]}
};
(@$p:tt [$($head:tt)*] [() $($tail:tt)*]) => {
all_ranges!{@$p [$($head)* ()] [$($tail)*]}
};
(@$p:tt [$($head:tt)*] [($($next:tt)*) $($tail:tt)*]) => {
all_ranges!{@$p [$($head)* (all_ranges!{@$p [] [$($next)*]})] [$($tail)*]}
};
(@$p:tt [$($head:tt)*] [$next:tt $($tail:tt)*]) => {
all_ranges!{@$p [$($head)* $next] [$($tail)*]}
};
($($tt:tt)*) => {
all_ranges!{@process [] [$($tt)*]}
};
}
(Hint for onlookers: using IntelliJ-Rust's "step macro" feature is invaluable for debugging these tt-munchers.)
The idea is that I can write
all_ranges! {
impl Index<@range> for str {
type Output = str;
fn index(&self, index: @range) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
}
and the impl will be duplicated across all of the Range
types:
impl Index<Range<StrIndex>> for str {
type Output = str;
fn index(&self, index: Range<StrIndex>) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeFrom<StrIndex>> for str {
type Output = str;
fn index(&self, index: RangeFrom<StrIndex>) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeInclusive<StrIndex>> for str {
type Output = str;
fn index(&self, index: RangeInclusive<StrIndex>) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeTo<StrIndex>> for str {
type Output = str;
fn index(&self, index: RangeTo<StrIndex>) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeToInclusive<StrIndex>> for str {
type Output = str;
fn index(&self, Index<RangeToInclusive<StrIndex>) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
I realize now having manually expanded it that the impls are not coherent, so this isn't going to work anyway, but I still want to understand why the macro is failing to produce the expected output.
When running this rustc gives me
error: expected one of `:` or `|`, found `)`
--> src\ops.rs:86:68
|
86 | all_ranges!{@$p [$($head)* (all_ranges!{@$p [] [$($next)*]})] [$($tail)*]}
| ^ expected one of `:` or `|` here
...
110 | / all_ranges! {
111 | | impl Index<@range> for str {
112 | | type Output = str;
113 | |
... |
119 | | }
120 | | }
| |_- in this macro invocation
error: aborting due to previous error
Expanding with IntelliJ-Rust gives me:
impl Index<Range<StrIndex>> for str {
type Output = str;
fn index(&self) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeFrom<StrIndex>> for str {
type Output = str;
fn index(&self) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeInclusive<StrIndex>> for str {
type Output = str;
fn index(&self) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeTo<StrIndex>> for str {
type Output = str;
fn index(&self) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
impl Index<RangeToInclusive<StrIndex>> for str {
type Output = str;
fn index(&self) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
This is not what rustc ends up giving so may be worth a bug report to IntelliJ-Rust.
Manually expanding just the Range
impl with IntelliJ, I get
impl Index<Range<StrIndex>> for str {
type Output = str;
fn index(all_ranges! { @ { Range < StrIndex > } [ ] [ & self , index : @ range ] }) -> &str {
let start = index.start_bound().as_usize_bound();
let end = index.end_bound().as_usize_bound();
&self[(start, end)]
}
}
on which rustc gives
error: expected one of `:` or `|`, found `)`
--> src\ops.rs:124:87
|
124 | fn index(all_ranges! { @ { Range < StrIndex > } [ ] [ & self , index : @ range ] }) -> &str {
| ^ expected one of `:` or `|` here
If I'm reading that right, what's happening is that rustc is pushing the entire macro into the pattern part of the function arguments list, and trying to find the type annotation after it? So macros aren't allowed at function argument list position (even if taking and emitting $($:tt)*
)?
If so, is there any way to get a tt-muncher decl-macro into the function declaration argument list position? Or does a tt-muncher have to recurse on {}
blocks but not ()
blocks to always work?