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

Add a vis matcher to macro_rules!. #1575

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions text/0000-vis-matcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
- Feature Name: `vis_matcher`
- Start Date: 2016-04-07
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[summary]: #summary

Add a `vis` matcher to `macro_rules!` that matches valid visibility annotations.

# Motivation
[motivation]: #motivation

Currently, matching visibility in a macro is something of a bug-bear. Depending on the circumstances, there are two available approaches:

1. Match `$(pub)*`. This may or may not be valid, depending on what follows this. It also has the disadvantage of *not* allowing you to capture the visibility for use in substitution. This is *only* useful if the visibility is irrelevant.

2. Write at least two rules, one for `pub` and one for non-`pub`. I say "at least two" because, due to `macro_rules!` limitations, you may *already* be writing multiple rules to distinguish between other, unrelated syntax cases.

An additional frustration is in passing visibility information around within a macro invocation. The simplest approach is use approach #2 above and pass either `()` or `(pub)` to sub-invocations. This can be matched as a single `tt`, and "unpacked" at the other end using `($($vis:tt)*)`. Unfortunately, due to limitations in `macro_rules!`, this requires the use of the "reparse trick" to get the resulting expansion to parse correctly.

Finally, due to this inconsistency, combined with the inability to use a capture *after* a repetition, it is impossible to parse (for example) a sequence of struct fields with visibility annotations.

All of this is to say: handling visibility in macros is, at present, a *discomfort in the posterior*.

The recently accepted [RFC #1422](https://github.com/rust-lang/rfcs/blob/master/text/1422-pub-restricted.md) has made the situation *worse*.

Now, there are three syntactically distinct variants of visibility: nothing, `pub`, and `pub(...)`. As such, the approaches to handling this become:

1. Match `$(pub$(($($vis:tt)*))*)*`. Hopefully, we can all agree that this is just *silly*.

2. Write at least *three* rules, one for `pub`, one for non-`pub`, and one for `pub($($vis:tt)*)`.

As a result of all of the above, I believe it is high time Rust gained a matcher for visibility annotations, in which case everything I just wrote goes away and instead becomes:

1. Match `$vis:vis`.

The exception is parsing a sequence of struct field which, due to attributes, remains impossible to do in general. However, in cases where the existence of attributes can be ignored, it does *become* possible.

# Detailed design
[design]: #detailed-design

Introduce a new `macro_rules!` matcher kind called `vis`. It should call `Parser::parse_visibility` and wrap the result in a `Nonterminal::NtVis` (also to be added). The parser should be modified such that `Parser::parse_visibility` detects and unpacks such nonterminals, allowing them to be substituted without the need for reparse tricks.

The `vis` matcher's follow set should consist of *at least* the following tokens:

<Ident> <Comma> <ModSep> <NtIdent> <NtTy>
const enum extern fn mod
static struct trait type use

The follow set is chosen to match all currently extant tokens that can follow a visibility: item keywords, identifiers (in a record struct), and types (in a tuple struct).

The `priv` reserved keyword should be excluded from the follow set, on the basis that it *might* be re-introduced as a visibility qualifier in the future.

## Test Case (Normative)

*Ideally*, the following source file should compile and run with this change. It is possible the tests may need to be adjusted for practical considerations during implementation.

```rust
#![allow(dead_code, unused_imports)]

/**
Ensure that `:vis` matches can be captured in existing positions, and passed
through without the need for reparse tricks.
*/
macro_rules! vis_passthru {
($vis:vis const $name:ident: $ty:ty = $e:expr;) => { $vis const $name: $ty = $e; };
($vis:vis enum $name:ident {}) => { $vis struct $name {} };
($vis:vis extern "C" fn $name:ident() {}) => { $vis extern "C" fn $name() {} };
($vis:vis fn $name:ident() {}) => { $vis fn $name() {} };
($vis:vis mod $name:ident {}) => { $vis mod $name {} };
($vis:vis static $name:ident: $ty:ty = $e:expr;) => { $vis static $name: $ty = $e; };
($vis:vis struct $name:ident;) => { $vis struct $name; };
($vis:vis trait $name:ident {}) => { $vis trait $name {} };
($vis:vis type $name:ident = $ty:ty;) => { $vis type $name = $ty; };
($vis:vis use $path:ident as $name:ident;) => { $vis use self::$path as $name; };
}

mod with_pub {
vis_passthru! { pub const A: i32 = 0; }
vis_passthru! { pub enum B {} }
vis_passthru! { pub extern "C" fn c() {} }
vis_passthru! { pub mod d {} }
vis_passthru! { pub static E: i32 = 0; }
vis_passthru! { pub struct F; }
vis_passthru! { pub trait G {} }
vis_passthru! { pub type H = i32; }
vis_passthru! { pub use A as I; }
}

mod without_pub {
vis_passthru! { const A: i32 = 0; }
vis_passthru! { enum B {} }
vis_passthru! { extern "C" fn c() {} }
vis_passthru! { mod d {} }
vis_passthru! { static E: i32 = 0; }
vis_passthru! { struct F; }
vis_passthru! { trait G {} }
vis_passthru! { type H = i32; }
vis_passthru! { use A as I; }
}

mod with_pub_restricted {
vis_passthru! { pub(crate) const A: i32 = 0; }
vis_passthru! { pub(crate) enum B {} }
vis_passthru! { pub(crate) extern "C" fn c() {} }
vis_passthru! { pub(crate) mod d {} }
vis_passthru! { pub(crate) static E: i32 = 0; }
vis_passthru! { pub(crate) struct F; }
vis_passthru! { pub(crate) trait G {} }
vis_passthru! { pub(crate) type H = i32; }
vis_passthru! { pub(crate) use A as I; }
}

mod garden {
mod with_pub_restricted_path {
vis_passthru! { pub(::garden) const A: i32 = 0; }
vis_passthru! { pub(::garden) enum B {} }
vis_passthru! { pub(::garden) extern "C" fn c() {} }
vis_passthru! { pub(::garden) mod d {} }
vis_passthru! { pub(::garden) static E: i32 = 0; }
vis_passthru! { pub(::garden) struct F; }
vis_passthru! { pub(::garden) trait G {} }
vis_passthru! { pub(::garden) type H = i32; }
vis_passthru! { pub(::garden) use A as I; }
}
}

/*
Ensure that the `:vis` matcher works in a more complex situation: parsing a
struct definition.
*/
macro_rules! vis_parse_struct {
/*
The rule duplication is currently unavoidable due to the leading attribute
matching.
*/
($(#[$($attrs:tt)*])* pub($($vis:tt)*) struct $name:ident {$($body:tt)*}) => {
vis_parse_struct! { @parse_fields $(#[$($attrs)*])*, pub($($vis)*), $name, $($body)* }
};
($(#[$($attrs:tt)*])* pub struct $name:ident {$($body:tt)*}) => {
vis_parse_struct! { @parse_fields $(#[$($attrs)*])*, pub, $name, $($body)* }
};
($(#[$($attrs:tt)*])* struct $name:ident {$($body:tt)*}) => {
vis_parse_struct! { @parse_fields $(#[$($attrs)*])*, , $name, $($body)* }
};

($(#[$($attrs:tt)*])* pub($($vis:tt)*) struct $name:ident ($($body:tt)*);) => {
vis_parse_struct! { @parse_tuple $(#[$($attrs)*])*, pub($($vis)*), $name, $($body)* }
};
($(#[$($attrs:tt)*])* pub struct $name:ident ($($body:tt)*);) => {
vis_parse_struct! { @parse_tuple $(#[$($attrs)*])*, pub, $name, $($body)* }
};
($(#[$($attrs:tt)*])* struct $name:ident ($($body:tt)*);) => {
vis_parse_struct! { @parse_tuple $(#[$($attrs)*])*, , $name, $($body)* }
};

(@parse_fields $(#[$attrs:meta])*, $vis:vis, $name:ident, $($fvis:vis $fname:ident: $fty:ty),* $(,)*) => {
$(#[$attrs])* $vis struct $name { $($fvis $fname: $fty,)* }
};

(@parse_tuple $(#[$attrs:meta])*, $vis:vis, $name:ident, $($fvis:vis $fty:ty),* $(,)*) => {
$(#[$attrs])* $vis struct $name ( $($fvis $fty,)* );
};
}

mod test_struct {
vis_parse_struct! { pub(crate) struct A { pub a: i32, b: i32, pub(crate) c: i32 } }
vis_parse_struct! { pub struct B { a: i32, pub(crate) b: i32, pub c: i32 } }
vis_parse_struct! { struct C { pub(crate) a: i32, pub b: i32, c: i32 } }

vis_parse_struct! { pub(crate) struct A (pub i32, i32, pub(crate) i32); }
vis_parse_struct! { pub struct B (i32, pub(crate) i32, pub i32); }
vis_parse_struct! { struct C (pub(crate) i32, pub i32, i32); }
}

fn main() {}
```

# Drawbacks
[drawbacks]: #drawbacks

It's more code to maintain, and an extra bit of complication for the already fairly complicated macro system. The compensation for this is that it makes the macros *themselves* less complicated.

# Alternatives
[alternatives]: #alternatives

- Do nothing and drive the people writing macros ever closer to complete mental breakdown.

- Dramatically expand `macro_rules!` such that it is expressive enough to represent something akin to `$vis:( $| pub $($($:tt)*)? )` (bind submatch with alternation and a zero-or-one group).

# Unresolved questions
[unresolved]: #unresolved-questions

- Should the matcher be called `vis` or something else?