Tt-muncher into function args?

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?

1 Like

One way around this is to build the function signature in a [$(func_sig:tt)*] list and then just paste in $($func_sig)* into the function signature all at once when you are done, I'm not sure if marcros expand to function arguments, never tried that before!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.