about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-04-17 17:56:29 +0000
committerbors <bors@rust-lang.org>2017-04-17 17:56:29 +0000
commit5516bcc4588ea6192298b4e3682eb1d09581912a (patch)
tree9b392d0f56be959b8b36d24e645c6ed5150d867c /src
parent011f240b898899644039bae4933ee8bd03efd609 (diff)
parent07c6295137753cb2a060d53e04b688dac3ced751 (diff)
downloadrust-5516bcc4588ea6192298b4e3682eb1d09581912a.tar.gz
rust-5516bcc4588ea6192298b4e3682eb1d09581912a.zip
Auto merge of #41345 - frewsxcv:rollup, r=frewsxcv
Rollup of 3 pull requests

- Successful merges: #41012, #41280, #41290
- Failed merges:
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/librustdoc/Cargo.toml1
-rw-r--r--src/librustdoc/build.rs30
-rw-r--r--src/librustdoc/clean/mod.rs5
-rw-r--r--src/librustdoc/html/markdown.rs191
-rw-r--r--src/librustdoc/html/render.rs224
-rw-r--r--src/librustdoc/markdown.rs3
-rw-r--r--src/librustdoc/test.rs34
-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
m---------src/rt/hoedown0
-rw-r--r--src/test/compile-fail/feature-gate-macro-vis-matcher.rs19
-rw-r--r--src/test/run-pass/macro-pub-matcher.rs115
-rw-r--r--src/tools/tidy/src/main.rs1
22 files changed, 715 insertions, 43 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/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml
index 52f5d99838d..d1c98a5c6f1 100644
--- a/src/librustdoc/Cargo.toml
+++ b/src/librustdoc/Cargo.toml
@@ -2,6 +2,7 @@
 authors = ["The Rust Project Developers"]
 name = "rustdoc"
 version = "0.0.0"
+build = "build.rs"
 
 [lib]
 name = "rustdoc"
diff --git a/src/librustdoc/build.rs b/src/librustdoc/build.rs
new file mode 100644
index 00000000000..4189e3d2ac7
--- /dev/null
+++ b/src/librustdoc/build.rs
@@ -0,0 +1,30 @@
+// Copyright 2015 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.
+
+extern crate build_helper;
+extern crate gcc;
+
+fn main() {
+    let src_dir = std::path::Path::new("../rt/hoedown/src");
+    build_helper::rerun_if_changed_anything_in_dir(src_dir);
+    let mut cfg = gcc::Config::new();
+    cfg.file("../rt/hoedown/src/autolink.c")
+       .file("../rt/hoedown/src/buffer.c")
+       .file("../rt/hoedown/src/document.c")
+       .file("../rt/hoedown/src/escape.c")
+       .file("../rt/hoedown/src/html.c")
+       .file("../rt/hoedown/src/html_blocks.c")
+       .file("../rt/hoedown/src/html_smartypants.c")
+       .file("../rt/hoedown/src/stack.c")
+       .file("../rt/hoedown/src/version.c")
+       .include(src_dir)
+       .compile("libhoedown.a");
+}
+
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 3d233463bba..99838635356 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -292,7 +292,7 @@ impl Item {
         self.type_() == ItemType::Struct
     }
     pub fn is_enum(&self) -> bool {
-        self.type_() == ItemType::Module
+        self.type_() == ItemType::Enum
     }
     pub fn is_fn(&self) -> bool {
         self.type_() == ItemType::Function
@@ -312,6 +312,9 @@ impl Item {
     pub fn is_primitive(&self) -> bool {
         self.type_() == ItemType::Primitive
     }
+    pub fn is_union(&self) -> bool {
+        self.type_() == ItemType::Union
+    }
     pub fn is_stripped(&self) -> bool {
         match self.inner { StrippedItem(..) => true, _ => false }
     }
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index c59101cc779..b02b60531d1 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -25,6 +25,9 @@
 
 #![allow(non_camel_case_types)]
 
+use libc;
+use std::slice;
+
 use std::ascii::AsciiExt;
 use std::cell::RefCell;
 use std::collections::{HashMap, VecDeque};
@@ -357,6 +360,194 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
     }
 }
 
+const DEF_OUNIT: libc::size_t = 64;
+const HOEDOWN_EXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 11;
+const HOEDOWN_EXT_TABLES: libc::c_uint = 1 << 0;
+const HOEDOWN_EXT_FENCED_CODE: libc::c_uint = 1 << 1;
+const HOEDOWN_EXT_AUTOLINK: libc::c_uint = 1 << 3;
+const HOEDOWN_EXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
+const HOEDOWN_EXT_SUPERSCRIPT: libc::c_uint = 1 << 8;
+const HOEDOWN_EXT_FOOTNOTES: libc::c_uint = 1 << 2;
+
+const HOEDOWN_EXTENSIONS: libc::c_uint =
+    HOEDOWN_EXT_NO_INTRA_EMPHASIS | HOEDOWN_EXT_TABLES |
+    HOEDOWN_EXT_FENCED_CODE | HOEDOWN_EXT_AUTOLINK |
+    HOEDOWN_EXT_STRIKETHROUGH | HOEDOWN_EXT_SUPERSCRIPT |
+    HOEDOWN_EXT_FOOTNOTES;
+
+enum hoedown_document {}
+
+type blockcodefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
+                                 *const hoedown_buffer, *const hoedown_renderer_data,
+                                 libc::size_t);
+
+type blockquotefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
+                                  *const hoedown_renderer_data, libc::size_t);
+
+type headerfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
+                              libc::c_int, *const hoedown_renderer_data,
+                              libc::size_t);
+
+type blockhtmlfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
+                                 *const hoedown_renderer_data, libc::size_t);
+
+type codespanfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
+                                *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
+
+type linkfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
+                             *const hoedown_buffer, *const hoedown_buffer,
+                             *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
+
+type entityfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
+                               *const hoedown_renderer_data, libc::size_t);
+
+type normaltextfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
+                                  *const hoedown_renderer_data, libc::size_t);
+
+#[repr(C)]
+struct hoedown_renderer_data {
+    opaque: *mut libc::c_void,
+}
+
+#[repr(C)]
+struct hoedown_renderer {
+    opaque: *mut libc::c_void,
+
+    blockcode: Option<blockcodefn>,
+    blockquote: Option<blockquotefn>,
+    header: Option<headerfn>,
+
+    other_block_level_callbacks: [libc::size_t; 11],
+
+    blockhtml: Option<blockhtmlfn>,
+
+    /* span level callbacks - NULL or return 0 prints the span verbatim */
+    autolink: libc::size_t, // unused
+    codespan: Option<codespanfn>,
+    other_span_level_callbacks_1: [libc::size_t; 7],
+    link: Option<linkfn>,
+    other_span_level_callbacks_2: [libc::size_t; 6],
+
+    /* low level callbacks - NULL copies input directly into the output */
+    entity: Option<entityfn>,
+    normal_text: Option<normaltextfn>,
+
+    /* header and footer */
+    other_callbacks: [libc::size_t; 2],
+}
+
+#[repr(C)]
+struct hoedown_html_renderer_state {
+    opaque: *mut libc::c_void,
+    toc_data: html_toc_data,
+    flags: libc::c_uint,
+    link_attributes: Option<extern "C" fn(*mut hoedown_buffer,
+                                          *const hoedown_buffer,
+                                          *const hoedown_renderer_data)>,
+}
+
+#[repr(C)]
+struct html_toc_data {
+    header_count: libc::c_int,
+    current_level: libc::c_int,
+    level_offset: libc::c_int,
+    nesting_level: libc::c_int,
+}
+
+#[repr(C)]
+struct hoedown_buffer {
+    data: *const u8,
+    size: libc::size_t,
+    asize: libc::size_t,
+    unit: libc::size_t,
+}
+
+extern {
+    fn hoedown_html_renderer_new(render_flags: libc::c_uint,
+                                 nesting_level: libc::c_int)
+        -> *mut hoedown_renderer;
+    fn hoedown_html_renderer_free(renderer: *mut hoedown_renderer);
+
+    fn hoedown_document_new(rndr: *const hoedown_renderer,
+                            extensions: libc::c_uint,
+                            max_nesting: libc::size_t) -> *mut hoedown_document;
+    fn hoedown_document_render(doc: *mut hoedown_document,
+                               ob: *mut hoedown_buffer,
+                               document: *const u8,
+                               doc_size: libc::size_t);
+    fn hoedown_document_free(md: *mut hoedown_document);
+
+    fn hoedown_buffer_new(unit: libc::size_t) -> *mut hoedown_buffer;
+    fn hoedown_buffer_free(b: *mut hoedown_buffer);
+}
+
+impl hoedown_buffer {
+    fn as_bytes(&self) -> &[u8] {
+        unsafe { slice::from_raw_parts(self.data, self.size as usize) }
+    }
+}
+
+pub fn old_find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
+    extern fn block(_ob: *mut hoedown_buffer,
+                    text: *const hoedown_buffer,
+                    lang: *const hoedown_buffer,
+                    data: *const hoedown_renderer_data,
+                    line: libc::size_t) {
+        unsafe {
+            if text.is_null() { return }
+            let block_info = if lang.is_null() {
+                LangString::all_false()
+            } else {
+                let lang = (*lang).as_bytes();
+                let s = str::from_utf8(lang).unwrap();
+                LangString::parse(s)
+            };
+            if !block_info.rust { return }
+            let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
+            let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
+            let line = tests.get_line() + line;
+            let filename = tests.get_filename();
+            tests.add_old_test(line, filename);
+        }
+    }
+
+    extern fn header(_ob: *mut hoedown_buffer,
+                     text: *const hoedown_buffer,
+                     level: libc::c_int, data: *const hoedown_renderer_data,
+                     _: libc::size_t) {
+        unsafe {
+            let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
+            let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
+            if text.is_null() {
+                tests.register_header("", level as u32);
+            } else {
+                let text = (*text).as_bytes();
+                let text = str::from_utf8(text).unwrap();
+                tests.register_header(text, level as u32);
+            }
+        }
+    }
+
+    tests.set_position(position);
+
+    unsafe {
+        let ob = hoedown_buffer_new(DEF_OUNIT);
+        let renderer = hoedown_html_renderer_new(0, 0);
+        (*renderer).blockcode = Some(block);
+        (*renderer).header = Some(header);
+        (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
+                = tests as *mut _ as *mut libc::c_void;
+
+        let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
+        hoedown_document_render(document, ob, doc.as_ptr(),
+                                doc.len() as libc::size_t);
+        hoedown_document_free(document);
+
+        hoedown_html_renderer_free(renderer);
+        hoedown_buffer_free(ob);
+    }
+}
+
 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
     tests.set_position(position);
 
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 42a18345681..d55a0640562 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -2430,7 +2430,7 @@ fn item_struct(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
     }).peekable();
     if let doctree::Plain = s.struct_type {
         if fields.peek().is_some() {
-            write!(w, "<h2 class='fields'>Fields</h2>")?;
+            write!(w, "<h2 id='fields' class='fields'>Fields</h2>")?;
             for (field, ty) in fields {
                 let id = derive_id(format!("{}.{}",
                                            ItemType::StructField,
@@ -2478,7 +2478,7 @@ fn item_union(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
         }
     }).peekable();
     if fields.peek().is_some() {
-        write!(w, "<h2 class='fields'>Fields</h2>")?;
+        write!(w, "<h2 id='fields' class='fields'>Fields</h2>")?;
         for (field, ty) in fields {
             write!(w, "<span id='{shortty}.{name}' class=\"{shortty}\"><code>{name}: {ty}</code>
                        </span>",
@@ -2550,7 +2550,7 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
 
     document(w, cx, it)?;
     if !e.variants.is_empty() {
-        write!(w, "<h2 class='variants'>Variants</h2>\n")?;
+        write!(w, "<h2 id='variants' class='variants'>Variants</h2>\n")?;
         for variant in &e.variants {
             let id = derive_id(format!("{}.{}",
                                        ItemType::Variant,
@@ -3074,6 +3074,37 @@ impl<'a> fmt::Display for Sidebar<'a> {
         let it = self.item;
         let parentlen = cx.current.len() - if it.is_mod() {1} else {0};
 
+        if it.is_struct() || it.is_trait() || it.is_primitive() || it.is_union()
+            || it.is_enum() || it.is_mod()
+        {
+            write!(fmt, "<p class='location'>")?;
+            match it.inner {
+                clean::StructItem(..) => write!(fmt, "Struct ")?,
+                clean::TraitItem(..) => write!(fmt, "Trait ")?,
+                clean::PrimitiveItem(..) => write!(fmt, "Primitive Type ")?,
+                clean::UnionItem(..) => write!(fmt, "Union ")?,
+                clean::EnumItem(..) => write!(fmt, "Enum ")?,
+                clean::ModuleItem(..) => if it.is_crate() {
+                    write!(fmt, "Crate ")?;
+                } else {
+                    write!(fmt, "Module ")?;
+                },
+                _ => (),
+            }
+            write!(fmt, "{}", it.name.as_ref().unwrap())?;
+            write!(fmt, "</p>")?;
+
+            match it.inner {
+                clean::StructItem(ref s) => sidebar_struct(fmt, it, s)?,
+                clean::TraitItem(ref t) => sidebar_trait(fmt, it, t)?,
+                clean::PrimitiveItem(ref p) => sidebar_primitive(fmt, it, p)?,
+                clean::UnionItem(ref u) => sidebar_union(fmt, it, u)?,
+                clean::EnumItem(ref e) => sidebar_enum(fmt, it, e)?,
+                clean::ModuleItem(ref m) => sidebar_module(fmt, it, &m.items)?,
+                _ => (),
+            }
+        }
+
         // The sidebar is designed to display sibling functions, modules and
         // other miscellaneous information. since there are lots of sibling
         // items (and that causes quadratic growth in large modules),
@@ -3116,6 +3147,193 @@ impl<'a> fmt::Display for Sidebar<'a> {
     }
 }
 
+fn sidebar_assoc_items(it: &clean::Item) -> String {
+    let mut out = String::new();
+    let c = cache();
+    if let Some(v) = c.impls.get(&it.def_id) {
+        if v.iter().any(|i| i.inner_impl().trait_.is_none()) {
+            out.push_str("<li><a href=\"#methods\">Methods</a></li>");
+        }
+
+        if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
+            if let Some(impl_) = v.iter()
+                                  .filter(|i| i.inner_impl().trait_.is_some())
+                                  .find(|i| i.inner_impl().trait_.def_id() == c.deref_trait_did) {
+                if let Some(target) = impl_.inner_impl().items.iter().filter_map(|item| {
+                    match item.inner {
+                        clean::TypedefItem(ref t, true) => Some(&t.type_),
+                        _ => None,
+                    }
+                }).next() {
+                    let inner_impl = target.def_id().or(target.primitive_type().and_then(|prim| {
+                        c.primitive_locations.get(&prim).cloned()
+                    })).and_then(|did| c.impls.get(&did));
+                    if inner_impl.is_some() {
+                        out.push_str("<li><a href=\"#deref-methods\">");
+                        out.push_str(&format!("Methods from {:#}&lt;Target={:#}&gt;",
+                                                  impl_.inner_impl().trait_.as_ref().unwrap(),
+                                                  target));
+                        out.push_str("</a></li>");
+                    }
+                }
+            }
+            out.push_str("<li><a href=\"#implementations\">Trait Implementations</a></li>");
+        }
+    }
+
+    out
+}
+
+fn sidebar_struct(fmt: &mut fmt::Formatter, it: &clean::Item,
+                  s: &clean::Struct) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    if s.fields.iter()
+               .any(|f| if let clean::StructFieldItem(..) = f.inner { true } else { false }) {
+        if let doctree::Plain = s.struct_type {
+            sidebar.push_str("<li><a href=\"#fields\">Fields</a></li>");
+        }
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
+fn sidebar_trait(fmt: &mut fmt::Formatter, it: &clean::Item,
+                 t: &clean::Trait) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    let has_types = t.items.iter().any(|m| m.is_associated_type());
+    let has_consts = t.items.iter().any(|m| m.is_associated_const());
+    let has_required = t.items.iter().any(|m| m.is_ty_method());
+    let has_provided = t.items.iter().any(|m| m.is_method());
+
+    if has_types {
+        sidebar.push_str("<li><a href=\"#associated-types\">Associated Types</a></li>");
+    }
+    if has_consts {
+        sidebar.push_str("<li><a href=\"#associated-const\">Associated Constants</a></li>");
+    }
+    if has_required {
+        sidebar.push_str("<li><a href=\"#required-methods\">Required Methods</a></li>");
+    }
+    if has_provided {
+        sidebar.push_str("<li><a href=\"#provided-methods\">Provided Methods</a></li>");
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    sidebar.push_str("<li><a href=\"#implementors\">Implementors</a></li>");
+
+    write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)
+}
+
+fn sidebar_primitive(fmt: &mut fmt::Formatter, it: &clean::Item,
+                     _p: &clean::PrimitiveType) -> fmt::Result {
+    let sidebar = sidebar_assoc_items(it);
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
+fn sidebar_union(fmt: &mut fmt::Formatter, it: &clean::Item,
+                 u: &clean::Union) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    if u.fields.iter()
+               .any(|f| if let clean::StructFieldItem(..) = f.inner { true } else { false }) {
+        sidebar.push_str("<li><a href=\"#fields\">Fields</a></li>");
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
+fn sidebar_enum(fmt: &mut fmt::Formatter, it: &clean::Item,
+                e: &clean::Enum) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    if !e.variants.is_empty() {
+        sidebar.push_str("<li><a href=\"#variants\">Variants</a></li>");
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
+fn sidebar_module(fmt: &mut fmt::Formatter, _it: &clean::Item,
+                  items: &[clean::Item]) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    if items.iter().any(|it| it.type_() == ItemType::ExternCrate ||
+                             it.type_() == ItemType::Import) {
+        sidebar.push_str(&format!("<li><a href=\"#{id}\">{name}</a></li>",
+                                  id = "reexports",
+                                  name = "Reexports"));
+    }
+
+    // ordering taken from item_module, reorder, where it prioritized elements in a certain order
+    // to print its headings
+    for &myty in &[ItemType::Primitive, ItemType::Module, ItemType::Macro, ItemType::Struct,
+                   ItemType::Enum, ItemType::Constant, ItemType::Static, ItemType::Trait,
+                   ItemType::Function, ItemType::Typedef, ItemType::Union, ItemType::Impl,
+                   ItemType::TyMethod, ItemType::Method, ItemType::StructField, ItemType::Variant,
+                   ItemType::AssociatedType, ItemType::AssociatedConst] {
+        if items.iter().any(|it| {
+            if let clean::DefaultImplItem(..) = it.inner {
+                false
+            } else {
+                !maybe_ignore_item(it) && !it.is_stripped() && it.type_() == myty
+            }
+        }) {
+            let (short, name) = match myty {
+                ItemType::ExternCrate |
+                ItemType::Import          => ("reexports", "Reexports"),
+                ItemType::Module          => ("modules", "Modules"),
+                ItemType::Struct          => ("structs", "Structs"),
+                ItemType::Union           => ("unions", "Unions"),
+                ItemType::Enum            => ("enums", "Enums"),
+                ItemType::Function        => ("functions", "Functions"),
+                ItemType::Typedef         => ("types", "Type Definitions"),
+                ItemType::Static          => ("statics", "Statics"),
+                ItemType::Constant        => ("constants", "Constants"),
+                ItemType::Trait           => ("traits", "Traits"),
+                ItemType::Impl            => ("impls", "Implementations"),
+                ItemType::TyMethod        => ("tymethods", "Type Methods"),
+                ItemType::Method          => ("methods", "Methods"),
+                ItemType::StructField     => ("fields", "Struct Fields"),
+                ItemType::Variant         => ("variants", "Variants"),
+                ItemType::Macro           => ("macros", "Macros"),
+                ItemType::Primitive       => ("primitives", "Primitive Types"),
+                ItemType::AssociatedType  => ("associated-types", "Associated Types"),
+                ItemType::AssociatedConst => ("associated-consts", "Associated Constants"),
+            };
+            sidebar.push_str(&format!("<li><a href=\"#{id}\">{name}</a></li>",
+                                      id = short,
+                                      name = name));
+        }
+    }
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
 impl<'a> fmt::Display for Source<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
         let Source(s) = *self;
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index 5fadda030a4..f75144c23ac 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -25,7 +25,7 @@ use externalfiles::{ExternalHtml, LoadStringError, load_string};
 use html::render::reset_ids;
 use html::escape::Escape;
 use html::markdown;
-use html::markdown::{Markdown, MarkdownWithToc, find_testable_code};
+use html::markdown::{Markdown, MarkdownWithToc, find_testable_code, old_find_testable_code};
 use test::{TestOptions, Collector};
 
 /// Separate any lines at the start of the file that begin with `# ` or `%`.
@@ -159,6 +159,7 @@ pub fn test(input: &str, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
     let mut collector = Collector::new(input.to_string(), cfgs, libs, externs,
                                        true, opts, maybe_sysroot, None,
                                        Some(input.to_owned()));
+    old_find_testable_code(&input_str, &mut collector, DUMMY_SP);
     find_testable_code(&input_str, &mut collector, DUMMY_SP);
     test_args.insert(0, "rustdoctest".to_string());
     testing::test_main(&test_args, collector.tests);
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index f6b7a07bdae..fb681b20065 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -380,6 +380,8 @@ fn partition_source(s: &str) -> (String, String) {
 
 pub struct Collector {
     pub tests: Vec<testing::TestDescAndFn>,
+    // to be removed when hoedown will be definitely gone
+    pub old_tests: Vec<String>,
     names: Vec<String>,
     cfgs: Vec<String>,
     libs: SearchPaths,
@@ -401,6 +403,7 @@ impl Collector {
                codemap: Option<Rc<CodeMap>>, filename: Option<String>) -> Collector {
         Collector {
             tests: Vec::new(),
+            old_tests: Vec::new(),
             names: Vec::new(),
             cfgs: cfgs,
             libs: libs,
@@ -417,11 +420,8 @@ impl Collector {
         }
     }
 
-    pub fn add_test(&mut self, test: String,
-                    should_panic: bool, no_run: bool, should_ignore: bool,
-                    as_test_harness: bool, compile_fail: bool, error_codes: Vec<String>,
-                    line: usize, filename: String) {
-        let name = if self.use_headers {
+    fn generate_name(&self, line: usize, filename: &str) -> String {
+        if self.use_headers {
             if let Some(ref header) = self.current_header {
                 format!("{} - {} (line {})", filename, header, line)
             } else {
@@ -429,7 +429,27 @@ impl Collector {
             }
         } else {
             format!("{} - {} (line {})", filename, self.names.join("::"), line)
-        };
+        }
+    }
+
+    pub fn add_old_test(&mut self, line: usize, filename: String) {
+        let name = self.generate_name(line, &filename);
+        self.old_tests.push(name);
+    }
+
+    pub fn add_test(&mut self, test: String,
+                    should_panic: bool, no_run: bool, should_ignore: bool,
+                    as_test_harness: bool, compile_fail: bool, error_codes: Vec<String>,
+                    line: usize, filename: String) {
+        let name = self.generate_name(line, &filename);
+        if self.old_tests.iter().find(|&x| x == &name).is_none() {
+            let _ = writeln!(&mut io::stderr(),
+                             "WARNING: {} Code block is not currently run as a test, but will in \
+                              future versions of rustdoc. Please ensure this code block is a \
+                              runnable test, or use the `ignore` directive.",
+                             name);
+            return
+        }
         let cfgs = self.cfgs.clone();
         let libs = self.libs.clone();
         let externs = self.externs.clone();
@@ -544,6 +564,8 @@ impl<'a, 'hir> HirCollector<'a, 'hir> {
         attrs.unindent_doc_comments();
         if let Some(doc) = attrs.doc_value() {
             self.collector.cnt = 0;
+            markdown::old_find_testable_code(doc, self.collector,
+                                             attrs.span.unwrap_or(DUMMY_SP));
             markdown::find_testable_code(doc, self.collector,
                                          attrs.span.unwrap_or(DUMMY_SP));
         }
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 ab4ac6f1b91..3e71c0f0f68 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/rt/hoedown b/src/rt/hoedown
new file mode 160000
+Subproject da282f1bb7277b4d30fa1599ee29ad8eb4dd2a9
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() {}
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index 17f8b62117a..44063e627a3 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -86,6 +86,7 @@ fn filter_dirs(path: &Path) -> bool {
         "src/rust-installer",
         "src/liblibc",
         "src/vendor",
+        "src/rt/hoedown",
     ];
     skip.iter().any(|p| path.ends_with(p))
 }