Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nested macros don't allow repetitions in binding patterns #35853

Closed
colin-kiegel opened this issue Aug 20, 2016 · 19 comments
Closed

nested macros don't allow repetitions in binding patterns #35853

colin-kiegel opened this issue Aug 20, 2016 · 19 comments
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) C-feature-request Category: A feature request, i.e: not implemented / a PR.

Comments

@colin-kiegel
Copy link

Example:

macro_rules! foo {
    () => {
        macro_rules! bar {
            ( $( $any:tt )* ) => { $( $any )* };
        }
    };
}

fn main() { foo!(); }

https://play.rust-lang.org/?gist=f7355a6828cc2af68cc17f280a982ad8&version=beta&backtrace=0

results in:

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth

 --> <anon>:4:15
  |
4 |             ( $( $any:tt )* ) => { $( $any )* };
  |               ^^^^^^^^^^^^

Note

If the repetition is removed, i.e. the offending part ( $( $any:tt )* ) => { $( $any )* }; is changed to ( $any:tt ) => { $any };, the error disappears on rustc 1.12+ due to the fix of #6994: macros should be parsed more lazily. I find it a bit inconsistent, that nested macros now support binding patterns, but no repetitions.

Meta

  • rustc 1.12.0-beta.1 (822166b 2016-08-16)
  • rustc 1.13.0-nightly (499484f 2016-08-18)
@colin-kiegel
Copy link
Author

It appears to be non-trivial to distinguish expansions and binding patterns, because they can both appear in the same position, like this:

macro_rules! foo {
    ( $( $some:tt )* ) => {
        macro_rules! bar {
            ( $( $some )* $( foo )* $( $any:tt bar )* ) => { $( $any )* };
        }
    };
}

Note that $( foo )* is expected to be repeated as often as there are matches for $some:tt. But $( $any:tt bar )* would be expected a matching binding pattern. Wouldn't it?

These distinctions seem to be very delicate and subtle and prone to error. I wonder if it would be better to have explicit escaping (as suggested in #6994 (comment)).

PS: And something like this should be detected as invalid:

macro_rules! foo {
    ( $( $some:tt )* ) => {
        macro_rules! bar {
            ( $( $some foo $any:tt )* ) => { $( $any )* };
        }
    };
}

@colin-kiegel
Copy link
Author

colin-kiegel commented Aug 20, 2016

And another possible problem, this time on the most inner expansion $( $some $any )*

macro_rules! foo {
    ( $( $some:tt )* ) => {
        macro_rules! bar {
            ( $( $any:tt )* ) => { $( $some $any )* };
        }
    };
}

I have the feeling, that it's not possible to address all these questions without new syntax. Other opinions?

@colin-kiegel
Copy link
Author

If matching and expansion syntax were different, this would not be a problem.

@nrc fyi - maybe relevant for macros 2.0?

@durka
Copy link
Contributor

durka commented Aug 21, 2016

cc @jseyfried

@comex
Copy link
Contributor

comex commented Sep 16, 2016

This error also occurs for macro definitions inside macro invocations, not just other definitions:

macro_rules! foo {
    ($($a:tt)*) => ($($a)*)
}
foo! {
    macro_rules! bar { // same as foo
        ($($a:tt)*) => ($($a)*)
    }
}

Definitely no ambiguity in this case.

@tobia
Copy link
Contributor

tobia commented Jan 14, 2017

@colin-kiegel How would that help? Consider this silly example:

macro_rules! make_println {
    ($name:ident, $fmt:expr) => {
        macro_rules! $name {
            ($($args:expr),*) => {           // (1)
                println!($fmt, $($args),*);  // (2)
            }
        }
    };
}

If matching and expansion syntax were different, then (1) wouldn't trigger an error, but the "args" part in (2) would still do, because it's intentionally written to look like expansion syntax, except it's not intended for the macro being parsed.

I can only see two solutions:

  1. Whenever an expansion repeater does not contain a valid variable, it should be left alone, suppressing (or delaying) the error "attempted to repeat an expression containing no syntax variables matched as repeating at this depth," in the same way as the error about unknown variables is delayed until expansion (IIUC) which is what allows macro-generating-macros-with-parameters in the first place.

Or:

  1. Provide some kind of escaping for the $ symbol, such as $$. Then it would just be a matter of duplicating (or quadrupling...) any dollar intended for the child (resp. grandchild...) macro:
macro_rules! make_println {
    ($name:ident, $fmt:expr) => {
        macro_rules! $name {
            ($$($$args:expr),*) => {           // (1)
                println!($fmt, $$($$args),*);  // (2)
            }
        }
    };
}

I would go with 2. because it's good to have explicit error messages about common mistakes and misplacing repeater variables is a common mistake. If someone wants to write a (macro-generating)ⁿ-macro, using 2ⁿ dollars instead of one seems like a small price to pay. Backslashes in string constants already work this way. (I've seen up to 8 consecutive backslashes in real-world code; 4 is not uncommon; 2 are everywhere.)

@jjpe
Copy link

jjpe commented Aug 25, 2018

I have found myself today wishing this was an implemented feature in Rust.
What would be required to get this issue moving forward?
Specifically I'm interested in pursuing option 2, as it seems the more pragmatic choice i.e. it is the easiest to use and remember rules for.

@durka
Copy link
Contributor

durka commented Aug 25, 2018

Small note: the problem from @comex's comment has been fixed already (not sure when).

@durka
Copy link
Contributor

durka commented Aug 25, 2018

For the second problem (@tobia), there exists a workaround, though it kinda sucks: pass in the dollar sign as a token, so you get kind of a "private escape sequence".

macro_rules! make_println {
    ($d:tt $name:ident, $fmt:expr) => {
        macro_rules! $name {
            ($d($d args:expr),*) => {           // (1)
                println!($fmt, $d($d args),*);  // (2)
            }
        }
    };
}

make_println!($ dbg, "{:?}");

The dollar sign has to be passed in from the top level, because that's the only time you can write it without being interpreted as a macro variable. So there's no way (that I can see) to wrap this up in a meta-macro like with_dollar_sign or something. (edit: I was wrong, see below)

You can wrap this to avoid making the ugliness visible at the top level:

macro_rules! with_dollar_sign {
    ($($body:tt)*) => {
        macro_rules! __with_dollar_sign { $($body)* }
        __with_dollar_sign!($);
    }
}

macro_rules! make_println {
    ($name:ident, $fmt:expr) => {
        with_dollar_sign! {
            ($d:tt) => {
                macro_rules! $name {
                    ($d($d args:expr),*) => {           // (1)
                        println!($fmt, $d($d args),*);  // (2)
                    }
                }
            }
        }
    };
}

make_println!(my_dbg, "{:?}");

fn main() {
    my_dbg!(42);
}

@bluss
Copy link
Member

bluss commented Nov 30, 2018

It can be passed from inside the macro with ($)

See https://github.com/bluss/defmac/blob/6886f04d412e1ee2b6d4d240feb51e51a9caf808/src/lib.rs

@Avi-D-coder
Copy link
Contributor

Avi-D-coder commented Dec 10, 2018

Does @tobia's option 2 need an RFC or just a pull request?

@durka
Copy link
Contributor

durka commented Dec 11, 2018

I think that would need an RFC. I edited my comment above to include @bluss' workaround.

@bluss
Copy link
Member

bluss commented Dec 11, 2018

@durka didn't you teach me the workaround? :)

@PatchMixolydic
Copy link
Contributor

Due to the acceptance of RFC 3086 (tracking issue #83527), a resolution to this issue seems to be forthcoming in the form of $$ (with this issue in particular being a motivating example for that feature).

@c410-f3r
Copy link
Contributor

This issue can probably be closed now

@ehuss
Copy link
Contributor

ehuss commented Jun 12, 2022

Yea, with $$ from RFC 3086 now stabilized in 1.63 via #95860, I'm going to go ahead and close this.

@ehuss ehuss closed this as completed Jun 12, 2022
@nirvana-msu
Copy link

Shouldn't this be re-opened given stabilization got reverted? This thread makes it look like $$ is available on stable, but it isn't.

@ehuss
Copy link
Contributor

ehuss commented Oct 14, 2022

Yea, unfortunately it got reverted. You can follow issues #83527 and #99035 for updates to $$ expansion. We usually don't keep other issues open when there is a tracking issue for the RFC that specifically intends to resolve something (to prefer to have just one place for tracking).

@fahimMuhamma
Copy link

Example:

macro_rules! foo {
    () => {
        macro_rules! bar {
            ( $( $any:tt )* ) => { $( $any )* };
        }
    };
}

fn main() { foo!(); }

https://play.rust-lang.org/?gist=f7355a6828cc2af68cc17f280a982ad8&version=beta&backtrace=0

results in:

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth

 --> <anon>:4:15
  |
4 |             ( $( $any:tt )* ) => { $( $any )* };
  |               ^^^^^^^^^^^^

Note

If the repetition is removed, i.e. the offending part ( $( $any:tt )* ) => { $( $any )* }; is changed to ( $any:tt ) => { $any };, the error disappears on rustc 1.12+ due to the fix of #6994: macros should be parsed more lazily. I find it a bit inconsistent, that nested macros now support binding patterns, but no repetitions.

Meta

  • rustc 1.12.0-beta.1 (822166b 2016-08-16)
  • rustc 1.13.0-nightly (499484f 2016-08-18)

vberlier added a commit to vberlier/rukt that referenced this issue Nov 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) C-feature-request Category: A feature request, i.e: not implemented / a PR.
Projects
None yet
Development

No branches or pull requests