about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorCorey Farwell <coreyf@rwell.org>2017-04-17 13:26:19 -0400
committerGitHub <noreply@github.com>2017-04-17 13:26:19 -0400
commit914b6f1f14e62550c9b7ca7f59490aa0f10ef288 (patch)
tree21874704c9dba7f4aaf4cbff189927c179a5cd58 /src
parent5997806a6a4f1e57491bd9f24c7ac07619bf38d2 (diff)
parentcfa51f226f8190f74bcd3f8275ae05b9d76d59c4 (diff)
downloadrust-914b6f1f14e62550c9b7ca7f59490aa0f10ef288.tar.gz
rust-914b6f1f14e62550c9b7ca7f59490aa0f10ef288.zip
Rollup merge of #41012 - durka:vis-matcher, r=petrochenkov
:vis matcher for macro_rules

Resurrection of @DanielKeep's implementation posted with [RFC 1575](https://github.com/rust-lang/rfcs/pull/1575).

@jseyfried was of the opinion that this doesn't need an RFC.

Needed before merge:

- [x] sign-off from @DanielKeep since I stole his code
- [x] feature gate
- [x] docs
Diffstat (limited to 'src')
-rw-r--r--src/doc/unstable-book/src/SUMMARY.md1
-rw-r--r--src/doc/unstable-book/src/macro-vis-matcher.md14
-rw-r--r--src/librustc_resolve/build_reduced_graph.rs4
-rw-r--r--src/librustc_resolve/macros.rs4
-rw-r--r--src/libsyntax/ext/tt/macro_parser.rs1
-rw-r--r--src/libsyntax/ext/tt/macro_rules.rs79
-rw-r--r--src/libsyntax/feature_gate.rs6
-rw-r--r--src/libsyntax/fold.rs1
-rw-r--r--src/libsyntax/parse/parser.rs4
-rw-r--r--src/libsyntax/parse/token.rs2
-rw-r--r--src/libsyntax/print/pprust.rs19
-rw-r--r--src/test/compile-fail/feature-gate-macro-vis-matcher.rs19
-rw-r--r--src/test/run-pass/macro-pub-matcher.rs115
13 files changed, 237 insertions, 32 deletions
diff --git a/src/doc/unstable-book/src/SUMMARY.md b/src/doc/unstable-book/src/SUMMARY.md
index a9796fdf01e..42af79b8bb0 100644
--- a/src/doc/unstable-book/src/SUMMARY.md
+++ b/src/doc/unstable-book/src/SUMMARY.md
@@ -114,6 +114,7 @@
 - [lookup_host](lookup-host.md)
 - [loop_break_value](loop-break-value.md)
 - [macro_reexport](macro-reexport.md)
+- [macro_vis_matcher](macro-vis-matcher.md)
 - [main](main.md)
 - [manually_drop](manually-drop.md)
 - [map_entry_recover_keys](map-entry-recover-keys.md)
diff --git a/src/doc/unstable-book/src/macro-vis-matcher.md b/src/doc/unstable-book/src/macro-vis-matcher.md
new file mode 100644
index 00000000000..7918a356843
--- /dev/null
+++ b/src/doc/unstable-book/src/macro-vis-matcher.md
@@ -0,0 +1,14 @@
+# `macro_vis_matcher`
+
+The tracking issue for this feature is: [#41022]
+
+With this feature gate enabled, the [list of fragment specifiers][frags] gains one more entry:
+
+* `vis`: a visibility qualifier. Examples: nothing (default visibility); `pub`; `pub(crate)`.
+
+A `vis` variable may be followed by a comma, ident, type, or path.
+
+[#41022]: https://github.com/rust-lang/rust/issues/41022
+[frags]: ../book/first-edition/macros.html#syntactic-requirements
+
+------------------------
diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs
index 80f853778c7..c797c151de6 100644
--- a/src/librustc_resolve/build_reduced_graph.rs
+++ b/src/librustc_resolve/build_reduced_graph.rs
@@ -521,7 +521,9 @@ impl<'a> Resolver<'a> {
             LoadedMacro::ProcMacro(ext) => return ext,
         };
 
-        let ext = Rc::new(macro_rules::compile(&self.session.parse_sess, &macro_def));
+        let ext = Rc::new(macro_rules::compile(&self.session.parse_sess,
+                                               &self.session.features,
+                                               &macro_def));
         self.macro_map.insert(def_id, ext.clone());
         ext
     }
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index 966cb7ee8d8..030e3936de9 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -671,7 +671,9 @@ impl<'a> Resolver<'a> {
         }
 
         let def_id = self.definitions.local_def_id(item.id);
-        let ext = Rc::new(macro_rules::compile(&self.session.parse_sess, item));
+        let ext = Rc::new(macro_rules::compile(&self.session.parse_sess,
+                                               &self.session.features,
+                                               item));
         self.macro_map.insert(def_id, ext);
         *legacy_scope = LegacyScope::Binding(self.arenas.alloc_legacy_binding(LegacyBinding {
             parent: Cell::new(*legacy_scope), name: ident.name, def_id: def_id, span: item.span,
diff --git a/src/libsyntax/ext/tt/macro_parser.rs b/src/libsyntax/ext/tt/macro_parser.rs
index 6cd1fea2e75..eb0b7c29f8d 100644
--- a/src/libsyntax/ext/tt/macro_parser.rs
+++ b/src/libsyntax/ext/tt/macro_parser.rs
@@ -529,6 +529,7 @@ fn parse_nt<'a>(p: &mut Parser<'a>, sp: Span, name: &str) -> Nonterminal {
             token::NtPath(panictry!(p.parse_path(PathStyle::Type)))
         },
         "meta" => token::NtMeta(panictry!(p.parse_meta_item())),
+        "vis" => token::NtVis(panictry!(p.parse_visibility(true))),
         // this is not supposed to happen, since it has been checked
         // when compiling the macro.
         _ => p.span_bug(sp, "invalid fragment specifier")
diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs
index 93348c8f083..be979960725 100644
--- a/src/libsyntax/ext/tt/macro_rules.rs
+++ b/src/libsyntax/ext/tt/macro_rules.rs
@@ -18,6 +18,7 @@ use ext::tt::macro_parser::{MatchedSeq, MatchedNonterminal};
 use ext::tt::macro_parser::{parse, parse_failure_msg};
 use ext::tt::quoted;
 use ext::tt::transcribe::transcribe;
+use feature_gate::{self, emit_feature_err, Features, GateIssue};
 use parse::{Directory, ParseSess};
 use parse::parser::Parser;
 use parse::token::{self, NtTT};
@@ -25,6 +26,7 @@ use parse::token::Token::*;
 use symbol::Symbol;
 use tokenstream::{TokenStream, TokenTree};
 
+use std::cell::RefCell;
 use std::collections::{HashMap};
 use std::collections::hash_map::{Entry};
 use std::rc::Rc;
@@ -154,7 +156,7 @@ fn generic_extension<'cx>(cx: &'cx ExtCtxt,
 // Holy self-referential!
 
 /// Converts a `macro_rules!` invocation into a syntax extension.
-pub fn compile(sess: &ParseSess, def: &ast::Item) -> SyntaxExtension {
+pub fn compile(sess: &ParseSess, features: &RefCell<Features>, def: &ast::Item) -> SyntaxExtension {
     let lhs_nm = ast::Ident::with_empty_ctxt(Symbol::gensym("lhs"));
     let rhs_nm = ast::Ident::with_empty_ctxt(Symbol::gensym("rhs"));
 
@@ -208,7 +210,7 @@ pub fn compile(sess: &ParseSess, def: &ast::Item) -> SyntaxExtension {
                 if let MatchedNonterminal(ref nt) = **m {
                     if let NtTT(ref tt) = **nt {
                         let tt = quoted::parse(tt.clone().into(), true, sess).pop().unwrap();
-                        valid &= check_lhs_nt_follows(sess, &tt);
+                        valid &= check_lhs_nt_follows(sess, features, &tt);
                         return tt;
                     }
                 }
@@ -251,11 +253,13 @@ pub fn compile(sess: &ParseSess, def: &ast::Item) -> SyntaxExtension {
     NormalTT(exp, Some(def.span), attr::contains_name(&def.attrs, "allow_internal_unstable"))
 }
 
-fn check_lhs_nt_follows(sess: &ParseSess, lhs: &quoted::TokenTree) -> bool {
+fn check_lhs_nt_follows(sess: &ParseSess,
+                        features: &RefCell<Features>,
+                        lhs: &quoted::TokenTree) -> bool {
     // lhs is going to be like TokenTree::Delimited(...), where the
     // entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
     match lhs {
-        &quoted::TokenTree::Delimited(_, ref tts) => check_matcher(sess, &tts.tts),
+        &quoted::TokenTree::Delimited(_, ref tts) => check_matcher(sess, features, &tts.tts),
         _ => {
             let msg = "invalid macro matcher; matchers must be contained in balanced delimiters";
             sess.span_diagnostic.span_err(lhs.span(), msg);
@@ -307,11 +311,13 @@ fn check_rhs(sess: &ParseSess, rhs: &quoted::TokenTree) -> bool {
     false
 }
 
-fn check_matcher(sess: &ParseSess, matcher: &[quoted::TokenTree]) -> bool {
+fn check_matcher(sess: &ParseSess,
+                 features: &RefCell<Features>,
+                 matcher: &[quoted::TokenTree]) -> bool {
     let first_sets = FirstSets::new(matcher);
     let empty_suffix = TokenSet::empty();
     let err = sess.span_diagnostic.err_count();
-    check_matcher_core(sess, &first_sets, matcher, &empty_suffix);
+    check_matcher_core(sess, features, &first_sets, matcher, &empty_suffix);
     err == sess.span_diagnostic.err_count()
 }
 
@@ -553,6 +559,7 @@ impl TokenSet {
 // Requires that `first_sets` is pre-computed for `matcher`;
 // see `FirstSets::new`.
 fn check_matcher_core(sess: &ParseSess,
+                      features: &RefCell<Features>,
                       first_sets: &FirstSets,
                       matcher: &[quoted::TokenTree],
                       follow: &TokenSet) -> TokenSet {
@@ -583,12 +590,11 @@ fn check_matcher_core(sess: &ParseSess,
         match *token {
             TokenTree::Token(..) | TokenTree::MetaVarDecl(..) => {
                 let can_be_followed_by_any;
-                if let Err(bad_frag) = has_legal_fragment_specifier(token) {
+                if let Err(bad_frag) = has_legal_fragment_specifier(sess, features, token) {
                     let msg = format!("invalid fragment specifier `{}`", bad_frag);
                     sess.span_diagnostic.struct_span_err(token.span(), &msg)
-                        .help("valid fragment specifiers are `ident`, `block`, \
-                               `stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
-                               and `item`")
+                        .help("valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, \
+                              `pat`, `ty`, `path`, `meta`, `tt`, `item` and `vis`")
                         .emit();
                     // (This eliminates false positives and duplicates
                     // from error messages.)
@@ -610,7 +616,7 @@ fn check_matcher_core(sess: &ParseSess,
             }
             TokenTree::Delimited(span, ref d) => {
                 let my_suffix = TokenSet::singleton(d.close_tt(span));
-                check_matcher_core(sess, first_sets, &d.tts, &my_suffix);
+                check_matcher_core(sess, features, first_sets, &d.tts, &my_suffix);
                 // don't track non NT tokens
                 last.replace_with_irrelevant();
 
@@ -642,7 +648,7 @@ fn check_matcher_core(sess: &ParseSess,
                 // At this point, `suffix_first` is built, and
                 // `my_suffix` is some TokenSet that we can use
                 // for checking the interior of `seq_rep`.
-                let next = check_matcher_core(sess, first_sets, &seq_rep.tts, my_suffix);
+                let next = check_matcher_core(sess, features, first_sets, &seq_rep.tts, my_suffix);
                 if next.maybe_empty {
                     last.add_all(&next);
                 } else {
@@ -790,30 +796,61 @@ fn is_in_follow(tok: &quoted::TokenTree, frag: &str) -> Result<bool, (String, &'
                 // harmless
                 Ok(true)
             },
+            "vis" => {
+                // Explicitly disallow `priv`, on the off chance it comes back.
+                match *tok {
+                    TokenTree::Token(_, ref tok) => match *tok {
+                        Comma => Ok(true),
+                        Ident(i) if i.name != "priv" => Ok(true),
+                        ref tok => Ok(tok.can_begin_type())
+                    },
+                    TokenTree::MetaVarDecl(_, _, frag) if frag.name == "ident"
+                                                       || frag.name == "ty"
+                                                       || frag.name == "path" => Ok(true),
+                    _ => Ok(false)
+                }
+            },
             "" => Ok(true), // keywords::Invalid
             _ => Err((format!("invalid fragment specifier `{}`", frag),
                      "valid fragment specifiers are `ident`, `block`, \
-                      `stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
-                      and `item`"))
+                      `stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt`, \
+                      `item` and `vis`"))
         }
     }
 }
 
-fn has_legal_fragment_specifier(tok: &quoted::TokenTree) -> Result<(), String> {
+fn has_legal_fragment_specifier(sess: &ParseSess,
+                                features: &RefCell<Features>,
+                                tok: &quoted::TokenTree) -> Result<(), String> {
     debug!("has_legal_fragment_specifier({:?})", tok);
-    if let quoted::TokenTree::MetaVarDecl(_, _, frag_spec) = *tok {
-        let s = &frag_spec.name.as_str();
-        if !is_legal_fragment_specifier(s) {
-            return Err(s.to_string());
+    if let quoted::TokenTree::MetaVarDecl(_, _, ref frag_spec) = *tok {
+        let frag_name = frag_spec.name.as_str();
+        let frag_span = tok.span();
+        if !is_legal_fragment_specifier(sess, features, &frag_name, frag_span) {
+            return Err(frag_name.to_string());
         }
     }
     Ok(())
 }
 
-fn is_legal_fragment_specifier(frag: &str) -> bool {
-    match frag {
+fn is_legal_fragment_specifier(sess: &ParseSess,
+                               features: &RefCell<Features>,
+                               frag_name: &str,
+                               frag_span: Span) -> bool {
+    match frag_name {
         "item" | "block" | "stmt" | "expr" | "pat" |
         "path" | "ty" | "ident" | "meta" | "tt" | "" => true,
+        "vis" => {
+            if !features.borrow().macro_vis_matcher {
+                let explain = feature_gate::EXPLAIN_VIS_MATCHER;
+                emit_feature_err(sess,
+                                 "macro_vis_matcher",
+                                 frag_span,
+                                 GateIssue::Language,
+                                 explain);
+            }
+            true
+        },
         _ => false,
     }
 }
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index 6e455234196..129674b7476 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -352,6 +352,9 @@ declare_features! (
 
     // Allows overlapping impls of marker traits
     (active, overlapping_marker_traits, "1.18.0", Some(29864)),
+
+    // Allows use of the :vis macro fragment specifier
+    (active, macro_vis_matcher, "1.18.0", Some(41022)),
 );
 
 declare_features! (
@@ -1012,6 +1015,9 @@ pub const EXPLAIN_DEPR_CUSTOM_DERIVE: &'static str =
 pub const EXPLAIN_DERIVE_UNDERSCORE: &'static str =
     "attributes of the form `#[derive_*]` are reserved for the compiler";
 
+pub const EXPLAIN_VIS_MATCHER: &'static str =
+    ":vis fragment specifier is experimental and subject to change";
+
 pub const EXPLAIN_PLACEMENT_IN: &'static str =
     "placement-in expression syntax is experimental and subject to change.";
 
diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs
index a6ab8e10d9f..f39399a62e8 100644
--- a/src/libsyntax/fold.rs
+++ b/src/libsyntax/fold.rs
@@ -636,6 +636,7 @@ pub fn noop_fold_interpolated<T: Folder>(nt: token::Nonterminal, fld: &mut T)
         token::NtWhereClause(where_clause) =>
             token::NtWhereClause(fld.fold_where_clause(where_clause)),
         token::NtArg(arg) => token::NtArg(fld.fold_arg(arg)),
+        token::NtVis(vis) => token::NtVis(fld.fold_vis(vis)),
     }
 }
 
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index 3b928ea93c7..31669e1bbe3 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -5056,7 +5056,9 @@ impl<'a> Parser<'a> {
     /// and `pub(super)` for `pub(in super)`.  If the following element can't be a tuple (i.e. it's
     /// a function definition, it's not a tuple struct field) and the contents within the parens
     /// isn't valid, emit a proper diagnostic.
-    fn parse_visibility(&mut self, can_take_tuple: bool) -> PResult<'a, Visibility> {
+    pub fn parse_visibility(&mut self, can_take_tuple: bool) -> PResult<'a, Visibility> {
+        maybe_whole!(self, NtVis, |x| x);
+
         if !self.eat_keyword(keywords::Pub) {
             return Ok(Visibility::Inherited)
         }
diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs
index 74aa3984a9a..25cabef70c1 100644
--- a/src/libsyntax/parse/token.rs
+++ b/src/libsyntax/parse/token.rs
@@ -363,6 +363,7 @@ pub enum Nonterminal {
     /// Stuff inside brackets for attributes
     NtMeta(ast::MetaItem),
     NtPath(ast::Path),
+    NtVis(ast::Visibility),
     NtTT(TokenTree),
     // These are not exposed to macros, but are used by quasiquote.
     NtArm(ast::Arm),
@@ -392,6 +393,7 @@ impl fmt::Debug for Nonterminal {
             NtGenerics(..) => f.pad("NtGenerics(..)"),
             NtWhereClause(..) => f.pad("NtWhereClause(..)"),
             NtArg(..) => f.pad("NtArg(..)"),
+            NtVis(..) => f.pad("NtVis(..)"),
         }
     }
 }
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index 433ba3d3693..be1d26f8fe4 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -293,6 +293,7 @@ pub fn token_to_string(tok: &Token) -> String {
             token::NtGenerics(ref e)    => generics_to_string(&e),
             token::NtWhereClause(ref e) => where_clause_to_string(&e),
             token::NtArg(ref e)         => arg_to_string(&e),
+            token::NtVis(ref e)         => vis_to_string(&e),
         }
     }
 }
@@ -373,6 +374,10 @@ pub fn ident_to_string(id: ast::Ident) -> String {
     to_string(|s| s.print_ident(id))
 }
 
+pub fn vis_to_string(v: &ast::Visibility) -> String {
+    to_string(|s| s.print_visibility(v))
+}
+
 pub fn fun_to_string(decl: &ast::FnDecl,
                      unsafety: ast::Unsafety,
                      constness: ast::Constness,
@@ -427,13 +432,7 @@ pub fn mac_to_string(arg: &ast::Mac) -> String {
 }
 
 pub fn visibility_qualified(vis: &ast::Visibility, s: &str) -> String {
-    match *vis {
-        ast::Visibility::Public => format!("pub {}", s),
-        ast::Visibility::Crate(_) => format!("pub(crate) {}", s),
-        ast::Visibility::Restricted { ref path, .. } =>
-            format!("pub({}) {}", to_string(|s| s.print_path(path, false, 0, true)), s),
-        ast::Visibility::Inherited => s.to_string()
-    }
+    format!("{}{}", to_string(|s| s.print_visibility(vis)), s)
 }
 
 fn needs_parentheses(expr: &ast::Expr) -> bool {
@@ -1468,7 +1467,11 @@ impl<'a> State<'a> {
             ast::Visibility::Crate(_) => self.word_nbsp("pub(crate)"),
             ast::Visibility::Restricted { ref path, .. } => {
                 let path = to_string(|s| s.print_path(path, false, 0, true));
-                self.word_nbsp(&format!("pub({})", path))
+                if path == "self" || path == "super" {
+                    self.word_nbsp(&format!("pub({})", path))
+                } else {
+                    self.word_nbsp(&format!("pub(in {})", path))
+                }
             }
             ast::Visibility::Inherited => Ok(())
         }
diff --git a/src/test/compile-fail/feature-gate-macro-vis-matcher.rs b/src/test/compile-fail/feature-gate-macro-vis-matcher.rs
new file mode 100644
index 00000000000..5d6f2acea83
--- /dev/null
+++ b/src/test/compile-fail/feature-gate-macro-vis-matcher.rs
@@ -0,0 +1,19 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that the MSP430 interrupt ABI cannot be used when msp430_interrupt
+// feature gate is not used.
+
+macro_rules! m { ($v:vis) => {} }
+//~^ ERROR :vis fragment specifier is experimental and subject to change
+
+fn main() {
+    m!(pub);
+}
diff --git a/src/test/run-pass/macro-pub-matcher.rs b/src/test/run-pass/macro-pub-matcher.rs
new file mode 100644
index 00000000000..d79f4b65b69
--- /dev/null
+++ b/src/test/run-pass/macro-pub-matcher.rs
@@ -0,0 +1,115 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(dead_code, unused_imports)]
+#![feature(macro_vis_matcher)]
+
+/**
+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(in garden) const A: i32 = 0; }
+        vis_passthru! { pub(in garden) enum B {} }
+        vis_passthru! { pub(in garden) extern "C" fn c() {} }
+        vis_passthru! { pub(in garden) mod d {} }
+        vis_passthru! { pub(in garden) static E: i32 = 0; }
+        vis_passthru! { pub(in garden) struct F; }
+        vis_passthru! { pub(in garden) trait G {} }
+        vis_passthru! { pub(in garden) type H = i32; }
+        vis_passthru! { pub(in 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 {
+    ($(#[$($attrs:tt)*])* $vis:vis struct $name:ident {$($body:tt)*}) => {
+        vis_parse_struct! { @parse_fields $(#[$($attrs)*])*, $vis, $name, $($body)* }
+    };
+
+    ($(#[$($attrs:tt)*])* $vis:vis struct $name:ident ($($body:tt)*);) => {
+        vis_parse_struct! { @parse_tuple $(#[$($attrs)*])*, $vis, $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 D (pub i32, i32, pub(crate) i32); }
+    vis_parse_struct! { pub struct E (i32, pub(crate) i32, pub i32); }
+    vis_parse_struct! { struct F (pub(crate) i32, pub i32, i32); }
+}
+
+fn main() {}