about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2018-01-23 07:38:53 +0000
committerbors <bors@rust-lang.org>2018-01-23 07:38:53 +0000
commit48a7ea9c4095e3218df39832dfc51a456534ecc9 (patch)
treef9b180716e6551ea716040850288b68bea67294f
parent47a8eb7c4e24b61a8a9ab4eaff60ef65291aaa56 (diff)
parent63811b66f676c1971b15452946bad35223b8c403 (diff)
downloadrust-48a7ea9c4095e3218df39832dfc51a456534ecc9.tar.gz
rust-48a7ea9c4095e3218df39832dfc51a456534ecc9.zip
Auto merge of #47046 - Manishearth:intra-doc-links, r=eddyb,GuillaumeGomez,QuietMisdreavus,Manishearth
Implement RFC 1946 - intra-rustdoc links

https://github.com/rust-lang/rfcs/pull/1946 https://github.com/rust-lang/rust/issues/43466

Note for reviewers: The plain line counts are a little inflated because of how the markdown link parsing was done. [Read the file diff with "whitespace only" changes removed](https://github.com/rust-lang/rust/pull/47046/files?w=1) to get a better view of what actually changed there.

This pulls the name/path resolution mechanisms out of the compiler and runs it on the markdown in a crate's docs, so that links can be made to `SomeStruct` directly rather than finding the folder path to `struct.SomeStruct.html`. Check the `src/test/rustdoc/intra-paths.rs` test in this PR for a demo. The change was... a little invasive, but unlocks a really powerful mechanism for writing documentation that doesn't care about where an item was written to on the hard disk.

Items included:

 - [x] Make work with the hoedown renderer
 - [x] Handle relative paths
 - [x] Parse out the "path ambiguities" qualifiers (`[crate foo]`, `[struct Foo]`, `[foo()]`, `[static FOO]`, `[foo!]`, etc)
 - [x] Resolve foreign macros
 - [x] Resolve local macros
 - [x] Handle the use of inner/outer attributes giving different resolution scopes (handling for non-modules pushed to different PR)

Items not included:

 - [ ] Make sure cross-crate inlining works (blocked on refactor described in https://github.com/rust-lang/rust/pull/47046#issuecomment-354824520)
 - [ ] Implied Shortcut Reference Links (where just doing `[::std::iter::Iterator][]` without a reference anchor will resolve using the reference name rather than the link target) (requires modifying the markdown parser - blocked on Hoedown/Pulldown switch and https://github.com/google/pulldown-cmark/issues/121)
 - [ ] Handle enum variants and UFCS methods (Enum variants link to the enum page, associated methods don't link at all)
 - [ ] Emit more warnings/errors when things fail to resolve (linking to a value-namespaced item without a qualifier will emit an error, otherwise the link is just treated as a url, not a rust path)
 - [ ] Give better spans for resolution errors (currently the span for the first doc comment is used)
 - [ ] Check for inner doc comments on things that aren't modules

I'm making the PR, but it should be noted that most of the work was done by Misdreavus :smile:

(Editor's note: This has become a lie, check that commit log, Manish did a ton of work after this PR was opened `>_>`)
-rw-r--r--src/librustc/hir/lowering.rs16
-rw-r--r--src/librustc_driver/driver.rs83
-rw-r--r--src/librustc_resolve/lib.rs89
-rw-r--r--src/librustc_resolve/macros.rs5
-rw-r--r--src/librustdoc/clean/inline.rs6
-rw-r--r--src/librustdoc/clean/mod.rs287
-rw-r--r--src/librustdoc/core.rs64
-rw-r--r--src/librustdoc/externalfiles.rs4
-rw-r--r--src/librustdoc/html/item_type.rs1
-rw-r--r--src/librustdoc/html/markdown.rs582
-rw-r--r--src/librustdoc/html/render.rs31
-rw-r--r--src/librustdoc/lib.rs7
-rw-r--r--src/librustdoc/markdown.rs2
-rw-r--r--src/librustdoc/visit_ast.rs12
-rw-r--r--src/librustdoc/visit_lib.rs8
-rw-r--r--src/test/rustdoc/intra-links.rs60
-rw-r--r--src/tools/error_index_generator/main.rs2
17 files changed, 995 insertions, 264 deletions
diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index 32b55a05124..a87f2747a57 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -151,6 +151,11 @@ pub trait Resolver {
     /// We must keep the set of definitions up to date as we add nodes that weren't in the AST.
     /// This should only return `None` during testing.
     fn definitions(&mut self) -> &mut Definitions;
+
+    /// Given suffix ["b","c","d"], creates a HIR path for `[::crate_root]::b::c::d` and resolves
+    /// it based on `is_value`.
+    fn resolve_str_path(&mut self, span: Span, crate_root: Option<&str>,
+                components: &[&str], is_value: bool) -> hir::Path;
 }
 
 #[derive(Clone, Copy, Debug)]
@@ -3625,16 +3630,7 @@ impl<'a> LoweringContext<'a> {
     /// `fld.cx.use_std`, and `::core::b::c::d` otherwise.
     /// The path is also resolved according to `is_value`.
     fn std_path(&mut self, span: Span, components: &[&str], is_value: bool) -> hir::Path {
-        let mut path = hir::Path {
-            span,
-            def: Def::Err,
-            segments: iter::once(keywords::CrateRoot.name()).chain({
-                self.crate_root.into_iter().chain(components.iter().cloned()).map(Symbol::intern)
-            }).map(hir::PathSegment::from_name).collect(),
-        };
-
-        self.resolver.resolve_hir_path(&mut path, is_value);
-        path
+        self.resolver.resolve_str_path(span, self.crate_root, components, is_value)
     }
 
     fn signal_block_expr(&mut self,
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index 468a08b1fd9..b2897bd4548 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -28,7 +28,7 @@ use rustc::util::common::{ErrorReported, time};
 use rustc_allocator as allocator;
 use rustc_borrowck as borrowck;
 use rustc_incremental;
-use rustc_resolve::{MakeGlobMap, Resolver};
+use rustc_resolve::{MakeGlobMap, Resolver, ResolverArenas};
 use rustc_metadata::creader::CrateLoader;
 use rustc_metadata::cstore::{self, CStore};
 use rustc_trans_utils::trans_crate::TransCrate;
@@ -139,6 +139,7 @@ pub fn compile_input(trans: Box<TransCrate>,
 
         let crate_name =
             ::rustc_trans_utils::link::find_crate_name(Some(sess), &krate.attrs, input);
+
         let ExpansionResult { expanded_crate, defs, analysis, resolutions, mut hir_forest } = {
             phase_2_configure_and_expand(
                 sess,
@@ -562,6 +563,12 @@ pub struct ExpansionResult {
     pub hir_forest: hir_map::Forest,
 }
 
+pub struct InnerExpansionResult<'a> {
+    pub expanded_crate: ast::Crate,
+    pub resolver: Resolver<'a>,
+    pub hir_forest: hir_map::Forest,
+}
+
 /// Run the "early phases" of the compiler: initial `cfg` processing,
 /// loading compiler plugins (including those from `addl_plugins`),
 /// syntax expansion, secondary `cfg` expansion, synthesis of a test
@@ -578,6 +585,55 @@ pub fn phase_2_configure_and_expand<F>(sess: &Session,
                                        make_glob_map: MakeGlobMap,
                                        after_expand: F)
                                        -> Result<ExpansionResult, CompileIncomplete>
+    where F: FnOnce(&ast::Crate) -> CompileResult {
+    // Currently, we ignore the name resolution data structures for the purposes of dependency
+    // tracking. Instead we will run name resolution and include its output in the hash of each
+    // item, much like we do for macro expansion. In other words, the hash reflects not just
+    // its contents but the results of name resolution on those contents. Hopefully we'll push
+    // this back at some point.
+    let mut crate_loader = CrateLoader::new(sess, &cstore, &crate_name);
+    let resolver_arenas = Resolver::arenas();
+    let result = phase_2_configure_and_expand_inner(sess, cstore, krate, registry, crate_name,
+                                                    addl_plugins, make_glob_map, &resolver_arenas,
+                                                    &mut crate_loader, after_expand);
+    match result {
+        Ok(InnerExpansionResult {expanded_crate, resolver, hir_forest}) => {
+            Ok(ExpansionResult {
+                expanded_crate,
+                defs: resolver.definitions,
+                hir_forest,
+                resolutions: Resolutions {
+                    freevars: resolver.freevars,
+                    export_map: resolver.export_map,
+                    trait_map: resolver.trait_map,
+                    maybe_unused_trait_imports: resolver.maybe_unused_trait_imports,
+                    maybe_unused_extern_crates: resolver.maybe_unused_extern_crates,
+                },
+
+                analysis: ty::CrateAnalysis {
+                    access_levels: Rc::new(AccessLevels::default()),
+                    name: crate_name.to_string(),
+                    glob_map: if resolver.make_glob_map { Some(resolver.glob_map) } else { None },
+                },
+            })
+        }
+        Err(x) => Err(x)
+    }
+}
+
+/// Same as phase_2_configure_and_expand, but doesn't let you keep the resolver
+/// around
+pub fn phase_2_configure_and_expand_inner<'a, F>(sess: &'a Session,
+                                       cstore: &'a CStore,
+                                       krate: ast::Crate,
+                                       registry: Option<Registry>,
+                                       crate_name: &str,
+                                       addl_plugins: Option<Vec<String>>,
+                                       make_glob_map: MakeGlobMap,
+                                       resolver_arenas: &'a ResolverArenas<'a>,
+                                       crate_loader: &'a mut CrateLoader,
+                                       after_expand: F)
+                                       -> Result<InnerExpansionResult<'a>, CompileIncomplete>
     where F: FnOnce(&ast::Crate) -> CompileResult,
 {
     let time_passes = sess.time_passes();
@@ -666,19 +722,12 @@ pub fn phase_2_configure_and_expand<F>(sess: &Session,
         return Err(CompileIncomplete::Stopped);
     }
 
-    // Currently, we ignore the name resolution data structures for the purposes of dependency
-    // tracking. Instead we will run name resolution and include its output in the hash of each
-    // item, much like we do for macro expansion. In other words, the hash reflects not just
-    // its contents but the results of name resolution on those contents. Hopefully we'll push
-    // this back at some point.
-    let mut crate_loader = CrateLoader::new(sess, &cstore, crate_name);
-    let resolver_arenas = Resolver::arenas();
     let mut resolver = Resolver::new(sess,
                                      cstore,
                                      &krate,
                                      crate_name,
                                      make_glob_map,
-                                     &mut crate_loader,
+                                     crate_loader,
                                      &resolver_arenas);
     resolver.whitelisted_legacy_custom_derives = whitelisted_legacy_custom_derives;
     syntax_ext::register_builtins(&mut resolver, syntax_exts, sess.features.borrow().quote);
@@ -855,21 +904,9 @@ pub fn phase_2_configure_and_expand<F>(sess: &Session,
         syntax::ext::hygiene::clear_markings();
     }
 
-    Ok(ExpansionResult {
+    Ok(InnerExpansionResult {
         expanded_crate: krate,
-        defs: resolver.definitions,
-        analysis: ty::CrateAnalysis {
-            access_levels: Rc::new(AccessLevels::default()),
-            name: crate_name.to_string(),
-            glob_map: if resolver.make_glob_map { Some(resolver.glob_map) } else { None },
-        },
-        resolutions: Resolutions {
-            freevars: resolver.freevars,
-            export_map: resolver.export_map,
-            trait_map: resolver.trait_map,
-            maybe_unused_trait_imports: resolver.maybe_unused_trait_imports,
-            maybe_unused_extern_crates: resolver.maybe_unused_extern_crates,
-        },
+        resolver,
         hir_forest,
     })
 }
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index d9ae776a4d7..55c7e5f3924 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -67,6 +67,7 @@ use std::cell::{Cell, RefCell};
 use std::cmp;
 use std::collections::BTreeSet;
 use std::fmt;
+use std::iter;
 use std::mem::replace;
 use std::rc::Rc;
 
@@ -1320,6 +1321,7 @@ pub struct Resolver<'a> {
     crate_loader: &'a mut CrateLoader,
     macro_names: FxHashSet<Ident>,
     global_macros: FxHashMap<Name, &'a NameBinding<'a>>,
+    pub all_macros: FxHashMap<Name, Def>,
     lexical_macro_resolutions: Vec<(Ident, &'a Cell<LegacyScope<'a>>)>,
     macro_map: FxHashMap<DefId, Rc<SyntaxExtension>>,
     macro_defs: FxHashMap<Mark, DefId>,
@@ -1407,6 +1409,71 @@ impl<'a, 'b: 'a> ty::DefIdTree for &'a Resolver<'b> {
 
 impl<'a> hir::lowering::Resolver for Resolver<'a> {
     fn resolve_hir_path(&mut self, path: &mut hir::Path, is_value: bool) {
+        self.resolve_hir_path_cb(path, is_value,
+                                 |resolver, span, error| resolve_error(resolver, span, error))
+    }
+
+    fn resolve_str_path(&mut self, span: Span, crate_root: Option<&str>,
+                        components: &[&str], is_value: bool) -> hir::Path {
+        let mut path = hir::Path {
+            span,
+            def: Def::Err,
+            segments: iter::once(keywords::CrateRoot.name()).chain({
+                crate_root.into_iter().chain(components.iter().cloned()).map(Symbol::intern)
+            }).map(hir::PathSegment::from_name).collect(),
+        };
+
+        self.resolve_hir_path(&mut path, is_value);
+        path
+    }
+
+    fn get_resolution(&mut self, id: NodeId) -> Option<PathResolution> {
+        self.def_map.get(&id).cloned()
+    }
+
+    fn definitions(&mut self) -> &mut Definitions {
+        &mut self.definitions
+    }
+}
+
+impl<'a> Resolver<'a> {
+    /// Rustdoc uses this to resolve things in a recoverable way. ResolutionError<'a>
+    /// isn't something that can be returned because it can't be made to live that long,
+    /// and also it's a private type. Fortunately rustdoc doesn't need to know the error,
+    /// just that an error occured.
+    pub fn resolve_str_path_error(&mut self, span: Span, path_str: &str, is_value: bool)
+        -> Result<hir::Path, ()> {
+        use std::iter;
+        let mut errored = false;
+
+        let mut path = if path_str.starts_with("::") {
+            hir::Path {
+                span,
+                def: Def::Err,
+                segments: iter::once(keywords::CrateRoot.name()).chain({
+                    path_str.split("::").skip(1).map(Symbol::intern)
+                }).map(hir::PathSegment::from_name).collect(),
+            }
+        } else {
+            hir::Path {
+                span,
+                def: Def::Err,
+                segments: path_str.split("::").map(Symbol::intern)
+                                  .map(hir::PathSegment::from_name).collect(),
+            }
+        };
+        self.resolve_hir_path_cb(&mut path, is_value, |_, _, _| errored = true);
+        if errored || path.def == Def::Err {
+            Err(())
+        } else {
+            Ok(path)
+        }
+    }
+
+    /// resolve_hir_path, but takes a callback in case there was an error
+    fn resolve_hir_path_cb<F>(&mut self, path: &mut hir::Path, is_value: bool, error_callback: F)
+            where F: for<'c, 'b> FnOnce(&'c mut Resolver, Span, ResolutionError<'b>)
+        {
         let namespace = if is_value { ValueNS } else { TypeNS };
         let hir::Path { ref segments, span, ref mut def } = *path;
         let path: Vec<SpannedIdent> = segments.iter()
@@ -1418,24 +1485,16 @@ impl<'a> hir::lowering::Resolver for Resolver<'a> {
                 *def = path_res.base_def(),
             PathResult::NonModule(..) => match self.resolve_path(&path, None, true, span) {
                 PathResult::Failed(span, msg, _) => {
-                    resolve_error(self, span, ResolutionError::FailedToResolve(&msg));
+                    error_callback(self, span, ResolutionError::FailedToResolve(&msg));
                 }
                 _ => {}
             },
             PathResult::Indeterminate => unreachable!(),
             PathResult::Failed(span, msg, _) => {
-                resolve_error(self, span, ResolutionError::FailedToResolve(&msg));
+                error_callback(self, span, ResolutionError::FailedToResolve(&msg));
             }
         }
     }
-
-    fn get_resolution(&mut self, id: NodeId) -> Option<PathResolution> {
-        self.def_map.get(&id).cloned()
-    }
-
-    fn definitions(&mut self) -> &mut Definitions {
-        &mut self.definitions
-    }
 }
 
 impl<'a> Resolver<'a> {
@@ -1538,6 +1597,7 @@ impl<'a> Resolver<'a> {
             crate_loader,
             macro_names: FxHashSet(),
             global_macros: FxHashMap(),
+            all_macros: FxHashMap(),
             lexical_macro_resolutions: Vec::new(),
             macro_map: FxHashMap(),
             macro_exports: Vec::new(),
@@ -1833,8 +1893,8 @@ impl<'a> Resolver<'a> {
     // generate a fake "implementation scope" containing all the
     // implementations thus found, for compatibility with old resolve pass.
 
-    fn with_scope<F>(&mut self, id: NodeId, f: F)
-        where F: FnOnce(&mut Resolver)
+    pub fn with_scope<F, T>(&mut self, id: NodeId, f: F) -> T
+        where F: FnOnce(&mut Resolver) -> T
     {
         let id = self.definitions.local_def_id(id);
         let module = self.module_map.get(&id).cloned(); // clones a reference
@@ -1845,13 +1905,14 @@ impl<'a> Resolver<'a> {
             self.ribs[TypeNS].push(Rib::new(ModuleRibKind(module)));
 
             self.finalize_current_module_macro_resolutions();
-            f(self);
+            let ret = f(self);
 
             self.current_module = orig_module;
             self.ribs[ValueNS].pop();
             self.ribs[TypeNS].pop();
+            ret
         } else {
-            f(self);
+            f(self)
         }
     }
 
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index ceb39aea108..080ef3252a6 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -409,7 +409,7 @@ impl<'a> Resolver<'a> {
         def
     }
 
-    fn resolve_macro_to_def_inner(&mut self, scope: Mark, path: &ast::Path,
+    pub fn resolve_macro_to_def_inner(&mut self, scope: Mark, path: &ast::Path,
                                   kind: MacroKind, force: bool)
                                   -> Result<Def, Determinacy> {
         let ast::Path { ref segments, span } = *path;
@@ -755,8 +755,9 @@ impl<'a> Resolver<'a> {
             *legacy_scope = LegacyScope::Binding(self.arenas.alloc_legacy_binding(LegacyBinding {
                 parent: Cell::new(*legacy_scope), ident: ident, def_id: def_id, span: item.span,
             }));
+            let def = Def::Macro(def_id, MacroKind::Bang);
+            self.all_macros.insert(ident.name, def);
             if attr::contains_name(&item.attrs, "macro_export") {
-                let def = Def::Macro(def_id, MacroKind::Bang);
                 self.macro_exports.push(Export {
                     ident: ident.modern(),
                     def: def,
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index b8c34d78d30..e4e3cc2acd5 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -135,7 +135,11 @@ pub fn record_extern_fqn(cx: &DocContext, did: DefId, kind: clean::TypeKind) {
             None
         }
     });
-    let fqn = once(crate_name).chain(relative).collect();
+    let fqn = if let clean::TypeKind::Macro = kind {
+        vec![crate_name, relative.last().unwrap()]
+    } else {
+        once(crate_name).chain(relative).collect()
+    };
     cx.renderinfo.borrow_mut().external_paths.insert(did, (fqn, kind));
 }
 
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index cc75664cacb..fb1d21d6527 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -20,9 +20,10 @@ pub use self::FunctionRetTy::*;
 pub use self::Visibility::*;
 
 use syntax::abi::Abi;
-use syntax::ast;
+use syntax::ast::{self, AttrStyle};
 use syntax::attr;
 use syntax::codemap::Spanned;
+use syntax::feature_gate::UnstableFeatures;
 use syntax::ptr::P;
 use syntax::symbol::keywords;
 use syntax_pos::{self, DUMMY_SP, Pos, FileName};
@@ -53,6 +54,7 @@ use core::DocContext;
 use doctree;
 use visit_ast;
 use html::item_type::ItemType;
+use html::markdown::markdown_links;
 
 pub mod inline;
 pub mod cfg;
@@ -124,7 +126,7 @@ pub struct Crate {
     pub masked_crates: FxHashSet<CrateNum>,
 }
 
-impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
+impl<'a, 'tcx, 'rcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx, 'rcx> {
     fn clean(&self, cx: &DocContext) -> Crate {
         use ::visit_lib::LibEmbargoVisitor;
 
@@ -305,6 +307,11 @@ impl Item {
     pub fn collapsed_doc_value(&self) -> Option<String> {
         self.attrs.collapsed_doc_value()
     }
+
+    pub fn links(&self) -> Vec<(String, String)> {
+        self.attrs.links()
+    }
+
     pub fn is_crate(&self) -> bool {
         match self.inner {
             StrippedItem(box ModuleItem(Module { is_crate: true, ..})) |
@@ -465,6 +472,23 @@ impl Clean<Item> for doctree::Module {
             "".to_string()
         };
 
+        // maintain a stack of mod ids, for doc comment path resolution
+        // but we also need to resolve the module's own docs based on whether its docs were written
+        // inside or outside the module, so check for that
+        let attrs = if self.attrs.iter()
+                                 .filter(|a| a.check_name("doc"))
+                                 .next()
+                                 .map_or(true, |a| a.style == AttrStyle::Inner) {
+            // inner doc comment, use the module's own scope for resolution
+            cx.mod_ids.borrow_mut().push(self.id);
+            self.attrs.clean(cx)
+        } else {
+            // outer doc comment, use its parent's scope
+            let attrs = self.attrs.clean(cx);
+            cx.mod_ids.borrow_mut().push(self.id);
+            attrs
+        };
+
         let mut items: Vec<Item> = vec![];
         items.extend(self.extern_crates.iter().map(|x| x.clean(cx)));
         items.extend(self.imports.iter().flat_map(|x| x.clean(cx)));
@@ -481,6 +505,8 @@ impl Clean<Item> for doctree::Module {
         items.extend(self.impls.iter().flat_map(|x| x.clean(cx)));
         items.extend(self.macros.iter().map(|x| x.clean(cx)));
 
+        cx.mod_ids.borrow_mut().pop();
+
         // determine if we should display the inner contents or
         // the outer `mod` item for the source code.
         let whence = {
@@ -498,7 +524,7 @@ impl Clean<Item> for doctree::Module {
 
         Item {
             name: Some(name),
-            attrs: self.attrs.clean(cx),
+            attrs,
             source: whence.clean(cx),
             visibility: self.vis.clean(cx),
             stability: self.stab.clean(cx),
@@ -633,6 +659,7 @@ pub struct Attributes {
     pub other_attrs: Vec<ast::Attribute>,
     pub cfg: Option<Rc<Cfg>>,
     pub span: Option<syntax_pos::Span>,
+    pub links: Vec<(String, DefId)>,
 }
 
 impl Attributes {
@@ -762,11 +789,13 @@ impl Attributes {
                 Some(attr.clone())
             })
         }).collect();
+
         Attributes {
             doc_strings,
             other_attrs,
             cfg: if cfg == Cfg::True { None } else { Some(Rc::new(cfg)) },
             span: sp,
+            links: vec![],
         }
     }
 
@@ -785,6 +814,20 @@ impl Attributes {
             None
         }
     }
+
+    /// Get links as a vector
+    ///
+    /// Cache must be populated before call
+    pub fn links(&self) -> Vec<(String, String)> {
+        use html::format::href;
+        self.links.iter().filter_map(|&(ref s, did)| {
+            if let Some((href, ..)) = href(did) {
+                Some((s.clone(), href))
+            } else {
+                None
+            }
+        }).collect()
+    }
 }
 
 impl AttributesExt for Attributes {
@@ -793,9 +836,243 @@ impl AttributesExt for Attributes {
     }
 }
 
+/// Given a def, returns its name and disambiguator
+/// for a value namespace
+///
+/// Returns None for things which cannot be ambiguous since
+/// they exist in both namespaces (structs and modules)
+fn value_ns_kind(def: Def, path_str: &str) -> Option<(&'static str, String)> {
+    match def {
+        // structs and mods exist in both namespaces. skip them
+        Def::StructCtor(..) | Def::Mod(..) => None,
+        Def::Variant(..) | Def::VariantCtor(..)
+            => Some(("variant", format!("{}()", path_str))),
+        Def::Fn(..)
+            => Some(("function", format!("{}()", path_str))),
+        Def::Method(..)
+            => Some(("method", format!("{}()", path_str))),
+        Def::Const(..)
+            => Some(("const", format!("const@{}", path_str))),
+        Def::Static(..)
+            => Some(("static", format!("static@{}", path_str))),
+        _ => Some(("value", format!("value@{}", path_str))),
+    }
+}
+
+/// Given a def, returns its name, the article to be used, and a disambiguator
+/// for the type namespace
+fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) {
+    let (kind, article) = match def {
+        // we can still have non-tuple structs
+        Def::Struct(..) => ("struct", "a"),
+        Def::Enum(..) => ("enum", "an"),
+        Def::Trait(..) => ("trait", "a"),
+        Def::Union(..) => ("union", "a"),
+        _ => ("type", "a"),
+    };
+    (kind, article, format!("{}@{}", kind, path_str))
+}
+
+fn ambiguity_error(cx: &DocContext, attrs: &Attributes,
+                   path_str: &str,
+                   article1: &str, kind1: &str, disambig1: &str,
+                   article2: &str, kind2: &str, disambig2: &str) {
+    let sp = attrs.doc_strings.first()
+                  .map_or(DUMMY_SP, |a| a.span());
+    cx.sess()
+      .struct_span_err(sp,
+                       &format!("`{}` is both {} {} and {} {}",
+                                path_str, article1, kind1,
+                                article2, kind2))
+      .help(&format!("try `{}` if you want to select the {}, \
+                      or `{}` if you want to \
+                      select the {}",
+                      disambig1, kind1, disambig2,
+                      kind2))
+             .emit();
+}
+
+/// Resolve a given string as a path, along with whether or not it is
+/// in the value namespace
+fn resolve(cx: &DocContext, path_str: &str, is_val: bool) -> Result<hir::Path, ()> {
+    // In case we're in a module, try to resolve the relative
+    // path
+    if let Some(id) = cx.mod_ids.borrow().last() {
+        cx.resolver.borrow_mut()
+                   .with_scope(*id, |resolver| {
+                        resolver.resolve_str_path_error(DUMMY_SP,
+                                                        &path_str, is_val)
+                    })
+    } else {
+        // FIXME(Manishearth) this branch doesn't seem to ever be hit, really
+        cx.resolver.borrow_mut()
+                   .resolve_str_path_error(DUMMY_SP, &path_str, is_val)
+    }
+}
+
+/// Resolve a string as a macro
+fn macro_resolve(cx: &DocContext, path_str: &str) -> Option<Def> {
+    use syntax::ext::base::MacroKind;
+    use syntax::ext::hygiene::Mark;
+    let segment = ast::PathSegment {
+        identifier: ast::Ident::from_str(path_str),
+        span: DUMMY_SP,
+        parameters: None,
+    };
+    let path = ast::Path {
+        span: DUMMY_SP,
+        segments: vec![segment],
+    };
+
+    let mut resolver = cx.resolver.borrow_mut();
+    let mark = Mark::root();
+    let res = resolver
+        .resolve_macro_to_def_inner(mark, &path, MacroKind::Bang, false);
+    if let Ok(def) = res {
+        Some(def)
+    } else if let Some(def) = resolver.all_macros.get(&path_str.into()) {
+        Some(*def)
+    } else {
+        None
+    }
+}
+
+enum PathKind {
+    /// can be either value or type, not a macro
+    Unknown,
+    /// macro
+    Macro,
+    /// values, functions, consts, statics, everything in the value namespace
+    Value,
+    /// types, traits, everything in the type namespace
+    Type
+}
+
 impl Clean<Attributes> for [ast::Attribute] {
     fn clean(&self, cx: &DocContext) -> Attributes {
-        Attributes::from_ast(cx.sess().diagnostic(), self)
+        let mut attrs = Attributes::from_ast(cx.sess().diagnostic(), self);
+
+        if UnstableFeatures::from_environment().is_nightly_build() {
+            let dox = attrs.collapsed_doc_value().unwrap_or_else(String::new);
+            for link in markdown_links(&dox, cx.render_type) {
+                let def = {
+                    let mut kind = PathKind::Unknown;
+                    let path_str = if let Some(prefix) =
+                        ["struct@", "enum@", "type@",
+                         "trait@", "union@"].iter()
+                                          .find(|p| link.starts_with(**p)) {
+                        kind = PathKind::Type;
+                        link.trim_left_matches(prefix)
+                    } else if let Some(prefix) =
+                        ["const@", "static@",
+                         "value@", "function@", "mod@", "fn@", "module@"]
+                            .iter().find(|p| link.starts_with(**p)) {
+                        kind = PathKind::Value;
+                        link.trim_left_matches(prefix)
+                    } else if link.ends_with("()") {
+                        kind = PathKind::Value;
+                        link.trim_right_matches("()")
+                    } else if link.starts_with("macro@") {
+                        kind = PathKind::Macro;
+                        link.trim_left_matches("macro@")
+                    } else if link.ends_with('!') {
+                        kind = PathKind::Macro;
+                        link.trim_right_matches('!')
+                    } else {
+                        &link[..]
+                    }.trim();
+
+                    // avoid resolving things (i.e. regular links) which aren't like paths
+                    // FIXME(Manishearth) given that most links have slashes in them might be worth
+                    // doing a check for slashes first
+                    if path_str.contains(|ch: char| !(ch.is_alphanumeric() ||
+                                                      ch == ':' || ch == '_')) {
+                        continue;
+                    }
+
+
+                    match kind {
+                        PathKind::Value => {
+                            if let Ok(path) = resolve(cx, path_str, true) {
+                                path.def
+                            } else {
+                                // this could just be a normal link or a broken link
+                                // we could potentially check if something is
+                                // "intra-doc-link-like" and warn in that case
+                                continue;
+                            }
+                        }
+                        PathKind::Type => {
+                            if let Ok(path) = resolve(cx, path_str, false) {
+                                path.def
+                            } else {
+                                // this could just be a normal link
+                                continue;
+                            }
+                        }
+                        PathKind::Unknown => {
+                            // try everything!
+                            if let Some(macro_def) = macro_resolve(cx, path_str) {
+                                if let Ok(type_path) = resolve(cx, path_str, false) {
+                                    let (type_kind, article, type_disambig)
+                                        = type_ns_kind(type_path.def, path_str);
+                                    ambiguity_error(cx, &attrs, path_str,
+                                                    article, type_kind, &type_disambig,
+                                                    "a", "macro", &format!("macro@{}", path_str));
+                                    continue;
+                                } else if let Ok(value_path) = resolve(cx, path_str, true) {
+                                    let (value_kind, value_disambig)
+                                        = value_ns_kind(value_path.def, path_str)
+                                            .expect("struct and mod cases should have been \
+                                                     caught in previous branch");
+                                    ambiguity_error(cx, &attrs, path_str,
+                                                    "a", value_kind, &value_disambig,
+                                                    "a", "macro", &format!("macro@{}", path_str));
+                                }
+                                macro_def
+                            } else if let Ok(type_path) = resolve(cx, path_str, false) {
+                                // It is imperative we search for not-a-value first
+                                // Otherwise we will find struct ctors for when we are looking
+                                // for structs, and the link won't work.
+                                // if there is something in both namespaces
+                                if let Ok(value_path) = resolve(cx, path_str, true) {
+                                    let kind = value_ns_kind(value_path.def, path_str);
+                                    if let Some((value_kind, value_disambig)) = kind {
+                                        let (type_kind, article, type_disambig)
+                                            = type_ns_kind(type_path.def, path_str);
+                                        ambiguity_error(cx, &attrs, path_str,
+                                                        article, type_kind, &type_disambig,
+                                                        "a", value_kind, &value_disambig);
+                                        continue;
+                                    }
+                                }
+                                type_path.def
+                            } else if let Ok(value_path) = resolve(cx, path_str, true) {
+                                value_path.def
+                            } else {
+                                // this could just be a normal link
+                                continue;
+                            }
+                        }
+                        PathKind::Macro => {
+                            if let Some(def) = macro_resolve(cx, path_str) {
+                                def
+                            } else {
+                                continue
+                            }
+                        }
+                    }
+                };
+
+
+                let id = register_def(cx, def);
+                attrs.links.push((link, id));
+            }
+
+            cx.sess().abort_if_errors();
+        }
+
+        attrs
     }
 }
 
@@ -1853,6 +2130,7 @@ pub enum TypeKind {
     Variant,
     Typedef,
     Foreign,
+    Macro,
 }
 
 pub trait GetDefId {
@@ -3154,6 +3432,7 @@ fn register_def(cx: &DocContext, def: Def) -> DefId {
         Def::TyForeign(i) => (i, TypeKind::Foreign),
         Def::Static(i, _) => (i, TypeKind::Static),
         Def::Variant(i) => (cx.tcx.parent_def_id(i).unwrap(), TypeKind::Enum),
+        Def::Macro(i, _) => (i, TypeKind::Macro),
         Def::SelfTy(Some(def_id), _) => (def_id, TypeKind::Trait),
         Def::SelfTy(_, Some(impl_def_id)) => {
             return impl_def_id
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index ef7d5b5ff84..5fe4794389f 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -20,8 +20,10 @@ use rustc::lint;
 use rustc::util::nodemap::FxHashMap;
 use rustc_trans;
 use rustc_resolve as resolve;
+use rustc_metadata::creader::CrateLoader;
 use rustc_metadata::cstore::CStore;
 
+use syntax::ast::NodeId;
 use syntax::codemap;
 use syntax::feature_gate::UnstableFeatures;
 use errors;
@@ -35,6 +37,7 @@ use std::path::PathBuf;
 use visit_ast::RustdocVisitor;
 use clean;
 use clean::Clean;
+use html::markdown::RenderType;
 use html::render::RenderInfo;
 
 pub use rustc::session::config::Input;
@@ -42,8 +45,11 @@ pub use rustc::session::search_paths::SearchPaths;
 
 pub type ExternalPaths = FxHashMap<DefId, (Vec<String>, clean::TypeKind)>;
 
-pub struct DocContext<'a, 'tcx: 'a> {
+pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a> {
     pub tcx: TyCtxt<'a, 'tcx, 'tcx>,
+    pub resolver: &'a RefCell<resolve::Resolver<'rcx>>,
+    /// The stack of module NodeIds up till this point
+    pub mod_ids: RefCell<Vec<NodeId>>,
     pub populated_all_crate_impls: Cell<bool>,
     // Note that external items for which `doc(hidden)` applies to are shown as
     // non-reachable while local items aren't. This is because we're reusing
@@ -54,6 +60,8 @@ pub struct DocContext<'a, 'tcx: 'a> {
     pub renderinfo: RefCell<RenderInfo>,
     /// Later on moved through `clean::Crate` into `html::render::CACHE_KEY`
     pub external_traits: RefCell<FxHashMap<DefId, clean::Trait>>,
+    /// Which markdown renderer to use when extracting links.
+    pub render_type: RenderType,
 
     // The current set of type and lifetime substitutions,
     // for expanding type aliases at the HIR level:
@@ -64,7 +72,7 @@ pub struct DocContext<'a, 'tcx: 'a> {
     pub lt_substs: RefCell<FxHashMap<DefId, clean::Lifetime>>,
 }
 
-impl<'a, 'tcx> DocContext<'a, 'tcx> {
+impl<'a, 'tcx, 'rcx> DocContext<'a, 'tcx, 'rcx> {
     pub fn sess(&self) -> &session::Session {
         &self.tcx.sess
     }
@@ -104,7 +112,8 @@ pub fn run_core(search_paths: SearchPaths,
                 triple: Option<String>,
                 maybe_sysroot: Option<PathBuf>,
                 allow_warnings: bool,
-                force_unstable_if_unmarked: bool) -> (clean::Crate, RenderInfo)
+                force_unstable_if_unmarked: bool,
+                render_type: RenderType) -> (clean::Crate, RenderInfo)
 {
     // Parse, resolve, and typecheck the given crate.
 
@@ -156,16 +165,40 @@ pub fn run_core(search_paths: SearchPaths,
 
     let name = ::rustc_trans_utils::link::find_crate_name(Some(&sess), &krate.attrs, &input);
 
-    let driver::ExpansionResult { defs, analysis, resolutions, mut hir_forest, .. } = {
-        let result = driver::phase_2_configure_and_expand(&sess,
-                                                          &cstore,
-                                                          krate,
-                                                          None,
-                                                          &name,
-                                                          None,
-                                                          resolve::MakeGlobMap::No,
-                                                          |_| Ok(()));
-        abort_on_err(result, &sess)
+    let mut crate_loader = CrateLoader::new(&sess, &cstore, &name);
+
+    let resolver_arenas = resolve::Resolver::arenas();
+    let result = driver::phase_2_configure_and_expand_inner(&sess,
+                                                      &cstore,
+                                                      krate,
+                                                      None,
+                                                      &name,
+                                                      None,
+                                                      resolve::MakeGlobMap::No,
+                                                      &resolver_arenas,
+                                                      &mut crate_loader,
+                                                      |_| Ok(()));
+    let driver::InnerExpansionResult {
+        mut hir_forest,
+        resolver,
+        ..
+    } = abort_on_err(result, &sess);
+
+    // We need to hold on to the complete resolver, so we clone everything
+    // for the analysis passes to use. Suboptimal, but necessary in the
+    // current architecture.
+    let defs = resolver.definitions.clone();
+    let resolutions = ty::Resolutions {
+        freevars: resolver.freevars.clone(),
+        export_map: resolver.export_map.clone(),
+        trait_map: resolver.trait_map.clone(),
+        maybe_unused_trait_imports: resolver.maybe_unused_trait_imports.clone(),
+        maybe_unused_extern_crates: resolver.maybe_unused_extern_crates.clone(),
+    };
+    let analysis = ty::CrateAnalysis {
+        access_levels: Rc::new(AccessLevels::default()),
+        name: name.to_string(),
+        glob_map: if resolver.make_glob_map { Some(resolver.glob_map.clone()) } else { None },
     };
 
     let arenas = AllArenas::new();
@@ -176,6 +209,8 @@ pub fn run_core(search_paths: SearchPaths,
                                                           &[],
                                                           &sess);
 
+    let resolver = RefCell::new(resolver);
+
     abort_on_err(driver::phase_3_run_analysis_passes(&*trans,
                                                      control,
                                                      &sess,
@@ -203,12 +238,15 @@ pub fn run_core(search_paths: SearchPaths,
 
         let ctxt = DocContext {
             tcx,
+            resolver: &resolver,
             populated_all_crate_impls: Cell::new(false),
             access_levels: RefCell::new(access_levels),
             external_traits: Default::default(),
             renderinfo: Default::default(),
+            render_type,
             ty_substs: Default::default(),
             lt_substs: Default::default(),
+            mod_ids: Default::default(),
         };
         debug!("crate: {:?}", tcx.hir.krate());
 
diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs
index f8320330ad2..f7d07af04ea 100644
--- a/src/librustdoc/externalfiles.rs
+++ b/src/librustdoc/externalfiles.rs
@@ -37,7 +37,7 @@ impl ExternalHtml {
             )
             .and_then(|(ih, bc)|
                 load_external_files(md_before_content)
-                    .map(|m_bc| (ih, format!("{}{}", bc, Markdown(&m_bc, render))))
+                    .map(|m_bc| (ih, format!("{}{}", bc, Markdown(&m_bc, &[], render))))
             )
             .and_then(|(ih, bc)|
                 load_external_files(after_content)
@@ -45,7 +45,7 @@ impl ExternalHtml {
             )
             .and_then(|(ih, bc, ac)|
                 load_external_files(md_after_content)
-                    .map(|m_ac| (ih, bc, format!("{}{}", ac, Markdown(&m_ac, render))))
+                    .map(|m_ac| (ih, bc, format!("{}{}", ac, Markdown(&m_ac, &[], render))))
             )
             .map(|(ih, bc, ac)|
                 ExternalHtml {
diff --git a/src/librustdoc/html/item_type.rs b/src/librustdoc/html/item_type.rs
index 81087cd412e..e9c6488c49c 100644
--- a/src/librustdoc/html/item_type.rs
+++ b/src/librustdoc/html/item_type.rs
@@ -102,6 +102,7 @@ impl From<clean::TypeKind> for ItemType {
             clean::TypeKind::Variant  => ItemType::Variant,
             clean::TypeKind::Typedef  => ItemType::Typedef,
             clean::TypeKind::Foreign  => ItemType::ForeignType,
+            clean::TypeKind::Macro  => ItemType::Macro,
         }
     }
 }
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index e66add20376..dce0c4b001a 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -56,15 +56,16 @@ pub enum RenderType {
 /// A unit struct which has the `fmt::Display` trait implemented. When
 /// formatted, this struct will emit the HTML corresponding to the rendered
 /// version of the contained markdown string.
-// The second parameter is whether we need a shorter version or not.
-pub struct Markdown<'a>(pub &'a str, pub RenderType);
+/// The second parameter is a list of link replacements
+// The third parameter is whether we need a shorter version or not.
+pub struct Markdown<'a>(pub &'a str, pub &'a [(String, String)], pub RenderType);
 /// A unit struct like `Markdown`, that renders the markdown with a
 /// table of contents.
 pub struct MarkdownWithToc<'a>(pub &'a str, pub RenderType);
 /// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
 pub struct MarkdownHtml<'a>(pub &'a str, pub RenderType);
 /// A unit struct like `Markdown`, that renders only the first paragraph.
-pub struct MarkdownSummaryLine<'a>(pub &'a str);
+pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [(String, String)]);
 
 /// Controls whether a line will be hidden or shown in HTML output.
 ///
@@ -248,6 +249,39 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
 }
 
 /// Make headings links with anchor ids and build up TOC.
+struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
+    inner: I,
+    links: &'b [(String, String)]
+}
+
+impl<'a, 'b, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, 'b, I> {
+    fn new(iter: I, links: &'b [(String, String)]) -> Self {
+        LinkReplacer {
+            inner: iter,
+            links
+        }
+    }
+}
+
+impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
+    type Item = Event<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let event = self.inner.next();
+        if let Some(Event::Start(Tag::Link(dest, text))) = event {
+            if let Some(&(_, ref replace)) = self.links.into_iter().find(|link| &*link.0 == &*dest)
+            {
+                Some(Event::Start(Tag::Link(replace.to_owned().into(), text)))
+            } else {
+                Some(Event::Start(Tag::Link(dest, text)))
+            }
+        } else {
+            event
+        }
+    }
+}
+
+/// Make headings links with anchor ids and build up TOC.
 struct HeadingLinks<'a, 'b, I: Iterator<Item = Event<'a>>> {
     inner: I,
     toc: Option<&'b mut TocBuilder>,
@@ -527,6 +561,8 @@ struct MyOpaque {
                            *const hoedown_buffer, *const hoedown_renderer_data,
                            libc::size_t),
     toc_builder: Option<TocBuilder>,
+    links_out: Option<Vec<String>>,
+    links_replace: Vec<(String, String)>,
 }
 
 extern {
@@ -555,186 +591,293 @@ impl hoedown_buffer {
     }
 }
 
-pub fn render(w: &mut fmt::Formatter,
-              s: &str,
-              print_toc: bool,
-              html_flags: libc::c_uint) -> fmt::Result {
-    extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
-                    lang: *const hoedown_buffer, data: *const hoedown_renderer_data,
-                    line: libc::size_t) {
-        unsafe {
-            if orig_text.is_null() { return }
-
-            let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
-            let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque);
-            let text = (*orig_text).as_bytes();
-            let origtext = str::from_utf8(text).unwrap();
-            let origtext = origtext.trim_left();
-            debug!("docblock: ==============\n{:?}\n=======", text);
-            let mut compile_fail = false;
-            let mut ignore = false;
-
-            let rendered = if lang.is_null() || origtext.is_empty() {
-                false
+extern fn hoedown_block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
+                        lang: *const hoedown_buffer, data: *const hoedown_renderer_data,
+                        line: libc::size_t) {
+    unsafe {
+        if orig_text.is_null() { return }
+
+        let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
+        let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque);
+        let text = (*orig_text).as_bytes();
+        let origtext = str::from_utf8(text).unwrap();
+        let origtext = origtext.trim_left();
+        debug!("docblock: ==============\n{:?}\n=======", text);
+        let mut compile_fail = false;
+        let mut ignore = false;
+
+        let rendered = if lang.is_null() || origtext.is_empty() {
+            false
+        } else {
+            let rlang = (*lang).as_bytes();
+            let rlang = str::from_utf8(rlang).unwrap();
+            let parse_result = LangString::parse(rlang);
+            compile_fail = parse_result.compile_fail;
+            ignore = parse_result.ignore;
+            if !parse_result.rust {
+                (my_opaque.dfltblk)(ob, orig_text, lang,
+                                    opaque as *const hoedown_renderer_data,
+                                    line);
+                true
             } else {
-                let rlang = (*lang).as_bytes();
-                let rlang = str::from_utf8(rlang).unwrap();
-                let parse_result = LangString::parse(rlang);
-                compile_fail = parse_result.compile_fail;
-                ignore = parse_result.ignore;
-                if !parse_result.rust {
-                    (my_opaque.dfltblk)(ob, orig_text, lang,
-                                        opaque as *const hoedown_renderer_data,
-                                        line);
-                    true
+                false
+            }
+        };
+
+        let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
+        let text = lines.collect::<Vec<&str>>().join("\n");
+        if rendered { return }
+        PLAYGROUND.with(|play| {
+            // insert newline to clearly separate it from the
+            // previous block so we can shorten the html output
+            let mut s = String::from("\n");
+            let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
+                if url.is_empty() {
+                    return None;
+                }
+                let test = origtext.lines()
+                    .map(|l| map_line(l).for_code())
+                    .collect::<Vec<&str>>().join("\n");
+                let krate = krate.as_ref().map(|s| &**s);
+                let (test, _) = test::make_test(&test, krate, false,
+                                                &Default::default());
+                let channel = if test.contains("#![feature(") {
+                    "&amp;version=nightly"
                 } else {
-                    false
+                    ""
+                };
+                // These characters don't need to be escaped in a URI.
+                // FIXME: use a library function for percent encoding.
+                fn dont_escape(c: u8) -> bool {
+                    (b'a' <= c && c <= b'z') ||
+                    (b'A' <= c && c <= b'Z') ||
+                    (b'0' <= c && c <= b'9') ||
+                    c == b'-' || c == b'_' || c == b'.' ||
+                    c == b'~' || c == b'!' || c == b'\'' ||
+                    c == b'(' || c == b')' || c == b'*'
                 }
-            };
-
-            let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
-            let text = lines.collect::<Vec<&str>>().join("\n");
-            if rendered { return }
-            PLAYGROUND.with(|play| {
-                // insert newline to clearly separate it from the
-                // previous block so we can shorten the html output
-                let mut s = String::from("\n");
-                let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
-                    if url.is_empty() {
-                        return None;
-                    }
-                    let test = origtext.lines()
-                        .map(|l| map_line(l).for_code())
-                        .collect::<Vec<&str>>().join("\n");
-                    let krate = krate.as_ref().map(|s| &**s);
-                    let (test, _) = test::make_test(&test, krate, false,
-                                               &Default::default());
-                    let channel = if test.contains("#![feature(") {
-                        "&amp;version=nightly"
+                let mut test_escaped = String::new();
+                for b in test.bytes() {
+                    if dont_escape(b) {
+                        test_escaped.push(char::from(b));
                     } else {
-                        ""
-                    };
-                    // These characters don't need to be escaped in a URI.
-                    // FIXME: use a library function for percent encoding.
-                    fn dont_escape(c: u8) -> bool {
-                        (b'a' <= c && c <= b'z') ||
-                        (b'A' <= c && c <= b'Z') ||
-                        (b'0' <= c && c <= b'9') ||
-                        c == b'-' || c == b'_' || c == b'.' ||
-                        c == b'~' || c == b'!' || c == b'\'' ||
-                        c == b'(' || c == b')' || c == b'*'
-                    }
-                    let mut test_escaped = String::new();
-                    for b in test.bytes() {
-                        if dont_escape(b) {
-                            test_escaped.push(char::from(b));
-                        } else {
-                            write!(test_escaped, "%{:02X}", b).unwrap();
-                        }
+                        write!(test_escaped, "%{:02X}", b).unwrap();
                     }
-                    Some(format!(
-                        r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
-                        url, test_escaped, channel
-                    ))
-                });
-                let tooltip = if ignore {
-                    Some(("This example is not tested", "ignore"))
-                } else if compile_fail {
-                    Some(("This example deliberately fails to compile", "compile_fail"))
-                } else {
-                    None
-                };
-                s.push_str(&highlight::render_with_highlighting(
-                               &text,
-                               Some(&format!("rust-example-rendered{}",
-                                             if ignore { " ignore" }
-                                             else if compile_fail { " compile_fail" }
-                                             else { "" })),
-                               None,
-                               playground_button.as_ref().map(String::as_str),
-                               tooltip));
-                hoedown_buffer_put(ob, s.as_ptr(), s.len());
-            })
-        }
+                }
+                Some(format!(
+                    r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
+                    url, test_escaped, channel
+                ))
+            });
+            let tooltip = if ignore {
+                Some(("This example is not tested", "ignore"))
+            } else if compile_fail {
+                Some(("This example deliberately fails to compile", "compile_fail"))
+            } else {
+                None
+            };
+            s.push_str(&highlight::render_with_highlighting(
+                           &text,
+                           Some(&format!("rust-example-rendered{}",
+                                         if ignore { " ignore" }
+                                         else if compile_fail { " compile_fail" }
+                                         else { "" })),
+                           None,
+                           playground_button.as_ref().map(String::as_str),
+                           tooltip));
+            hoedown_buffer_put(ob, s.as_ptr(), s.len());
+        })
     }
+}
 
-    extern fn header(ob: *mut hoedown_buffer, text: *const hoedown_buffer,
-                     level: libc::c_int, data: *const hoedown_renderer_data,
-                     _: libc::size_t) {
-        // hoedown does this, we may as well too
-        unsafe { hoedown_buffer_put(ob, "\n".as_ptr(), 1); }
+extern fn hoedown_header(ob: *mut hoedown_buffer, text: *const hoedown_buffer,
+                         level: libc::c_int, data: *const hoedown_renderer_data,
+                         _: libc::size_t) {
+    // hoedown does this, we may as well too
+    unsafe { hoedown_buffer_put(ob, "\n".as_ptr(), 1); }
 
-        // Extract the text provided
-        let s = if text.is_null() {
-            "".to_owned()
-        } else {
-            let s = unsafe { (*text).as_bytes() };
-            str::from_utf8(&s).unwrap().to_owned()
-        };
+    // Extract the text provided
+    let s = if text.is_null() {
+        "".to_owned()
+    } else {
+        let s = unsafe { (*text).as_bytes() };
+        str::from_utf8(&s).unwrap().to_owned()
+    };
 
-        // Discard '<em>', '<code>' tags and some escaped characters,
-        // transform the contents of the header into a hyphenated string
-        // without non-alphanumeric characters other than '-' and '_'.
-        //
-        // This is a terrible hack working around how hoedown gives us rendered
-        // html for text rather than the raw text.
-        let mut id = s.clone();
-        let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
-                            "<strong>", "</strong>",
-                            "&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
-        for sub in repl_sub {
-            id = id.replace(sub, "");
-        }
-        let id = id.chars().filter_map(|c| {
-            if c.is_alphanumeric() || c == '-' || c == '_' {
-                if c.is_ascii() {
-                    Some(c.to_ascii_lowercase())
-                } else {
-                    Some(c)
-                }
-            } else if c.is_whitespace() && c.is_ascii() {
-                Some('-')
+    // Discard '<em>', '<code>' tags and some escaped characters,
+    // transform the contents of the header into a hyphenated string
+    // without non-alphanumeric characters other than '-' and '_'.
+    //
+    // This is a terrible hack working around how hoedown gives us rendered
+    // html for text rather than the raw text.
+    let mut id = s.clone();
+    let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
+                        "<strong>", "</strong>",
+                        "&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
+    for sub in repl_sub {
+        id = id.replace(sub, "");
+    }
+    let id = id.chars().filter_map(|c| {
+        if c.is_alphanumeric() || c == '-' || c == '_' {
+            if c.is_ascii() {
+                Some(c.to_ascii_lowercase())
             } else {
-                None
+                Some(c)
             }
-        }).collect::<String>();
+        } else if c.is_whitespace() && c.is_ascii() {
+            Some('-')
+        } else {
+            None
+        }
+    }).collect::<String>();
 
-        let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
-        let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
+    let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
+    let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
 
-        let id = derive_id(id);
+    let id = derive_id(id);
 
-        let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
-            format!("{} ", builder.push(level as u32, s.clone(), id.clone()))
-        });
+    let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
+        format!("{} ", builder.push(level as u32, s.clone(), id.clone()))
+    });
+
+    // Render the HTML
+    let text = format!("<h{lvl} id='{id}' class='section-header'>\
+                       <a href='#{id}'>{sec}{}</a></h{lvl}>",
+                       s, lvl = level, id = id, sec = sec);
 
-        // Render the HTML
-        let text = format!("<h{lvl} id='{id}' class='section-header'>\
-                           <a href='#{id}'>{sec}{}</a></h{lvl}>",
-                           s, lvl = level, id = id, sec = sec);
+    unsafe { hoedown_buffer_put(ob, text.as_ptr(), text.len()); }
+}
+
+extern fn hoedown_codespan(
+    ob: *mut hoedown_buffer,
+    text: *const hoedown_buffer,
+    _: *const hoedown_renderer_data,
+    _: libc::size_t
+) -> libc::c_int {
+    let content = if text.is_null() {
+        "".to_owned()
+    } else {
+        let bytes = unsafe { (*text).as_bytes() };
+        let s = str::from_utf8(bytes).unwrap();
+        collapse_whitespace(s)
+    };
 
-        unsafe { hoedown_buffer_put(ob, text.as_ptr(), text.len()); }
+    let content = format!("<code>{}</code>", Escape(&content));
+    unsafe {
+        hoedown_buffer_put(ob, content.as_ptr(), content.len());
     }
+    // Return anything except 0, which would mean "also print the code span verbatim".
+    1
+}
 
-    extern fn codespan(
+pub fn render(w: &mut fmt::Formatter,
+              s: &str,
+              links: &[(String, String)],
+              print_toc: bool,
+              html_flags: libc::c_uint) -> fmt::Result {
+    // copied from pulldown-cmark (MIT license, Google)
+    // https://github.com/google/pulldown-cmark
+    // this is temporary till we remove the hoedown renderer
+    static HREF_SAFE: [u8; 128] = [
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+            0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+        ];
+
+    static HEX_CHARS: &'static [u8] = b"0123456789ABCDEF";
+
+    fn escape_href(ob: &mut String, s: &str) {
+        let mut mark = 0;
+        for i in 0..s.len() {
+            let c = s.as_bytes()[i];
+            if c >= 0x80 || HREF_SAFE[c as usize] == 0 {
+                // character needing escape
+
+                // write partial substring up to mark
+                if mark < i {
+                    ob.push_str(&s[mark..i]);
+                }
+                match c {
+                    b'&' => {
+                        ob.push_str("&amp;");
+                    },
+                    b'\'' => {
+                        ob.push_str("&#x27;");
+                    },
+                    _ => {
+                        let mut buf = [0u8; 3];
+                        buf[0] = b'%';
+                        buf[1] = HEX_CHARS[((c as usize) >> 4) & 0xF];
+                        buf[2] = HEX_CHARS[(c as usize) & 0xF];
+                        ob.push_str(str::from_utf8(&buf).unwrap());
+                    }
+                }
+                mark = i + 1;  // all escaped characters are ASCII
+            }
+        }
+        ob.push_str(&s[mark..]);
+    }
+    // end code copied from pulldown-cmark
+
+    extern fn hoedown_link(
         ob: *mut hoedown_buffer,
-        text: *const hoedown_buffer,
-        _: *const hoedown_renderer_data,
-        _: libc::size_t
+        content: *const hoedown_buffer,
+        link: *const hoedown_buffer,
+        title: *const hoedown_buffer,
+        data: *const hoedown_renderer_data,
+        _line: libc::size_t
     ) -> libc::c_int {
-        let content = if text.is_null() {
-            "".to_owned()
+        if link.is_null() {
+            return 0;
+        }
+
+        let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
+        let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
+
+        let link = {
+            let s = unsafe { (*link).as_bytes() };
+            str::from_utf8(s).unwrap().to_owned()
+        };
+
+        let link = if let Some(&(_, ref new_target)) = opaque.links_replace
+                                                             .iter()
+                                                             .find(|t| &*t.0 == &*link) {
+            new_target.to_owned()
         } else {
-            let bytes = unsafe { (*text).as_bytes() };
-            let s = str::from_utf8(bytes).unwrap();
-            collapse_whitespace(s)
+            link
         };
 
-        let content = format!("<code>{}</code>", Escape(&content));
-        unsafe {
-            hoedown_buffer_put(ob, content.as_ptr(), content.len());
-        }
-        // Return anything except 0, which would mean "also print the code span verbatim".
+        let content = unsafe {
+            content.as_ref().map(|c| {
+                let s = c.as_bytes();
+                str::from_utf8(s).unwrap().to_owned()
+            })
+        };
+
+        let mut link_buf = String::new();
+        escape_href(&mut link_buf, &link);
+
+        let title = unsafe {
+            title.as_ref().map(|t| {
+                let s = t.as_bytes();
+                str::from_utf8(s).unwrap().to_owned()
+            })
+        };
+
+        let link_out = format!("<a href=\"{link}\"{title}>{content}</a>",
+                               link = link_buf,
+                               title = title.map_or(String::new(),
+                                                    |t| format!(" title=\"{}\"", t)),
+                               content = content.unwrap_or(String::new()));
+
+        unsafe { hoedown_buffer_put(ob, link_out.as_ptr(), link_out.len()); }
+
+        //return "anything but 0" to show we've written the link in
         1
     }
 
@@ -743,13 +886,16 @@ pub fn render(w: &mut fmt::Formatter,
         let renderer = hoedown_html_renderer_new(html_flags, 0);
         let mut opaque = MyOpaque {
             dfltblk: (*renderer).blockcode.unwrap(),
-            toc_builder: if print_toc {Some(TocBuilder::new())} else {None}
+            toc_builder: if print_toc {Some(TocBuilder::new())} else {None},
+            links_out: None,
+            links_replace: links.to_vec(),
         };
         (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
                 = &mut opaque as *mut _ as *mut libc::c_void;
-        (*renderer).blockcode = Some(block);
-        (*renderer).header = Some(header);
-        (*renderer).codespan = Some(codespan);
+        (*renderer).blockcode = Some(hoedown_block);
+        (*renderer).header = Some(hoedown_header);
+        (*renderer).codespan = Some(hoedown_codespan);
+        (*renderer).link = Some(hoedown_link);
 
         let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
         hoedown_document_render(document, ob, s.as_ptr(),
@@ -993,12 +1139,12 @@ impl LangString {
 
 impl<'a> fmt::Display for Markdown<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        let Markdown(md, render_type) = *self;
+        let Markdown(md, links, render_type) = *self;
 
         // This is actually common enough to special-case
         if md.is_empty() { return Ok(()) }
         if render_type == RenderType::Hoedown {
-            render(fmt, md, false, 0)
+            render(fmt, md, links, false, 0)
         } else {
             let mut opts = Options::empty();
             opts.insert(OPTION_ENABLE_TABLES);
@@ -1009,7 +1155,11 @@ impl<'a> fmt::Display for Markdown<'a> {
             let mut s = String::with_capacity(md.len() * 3 / 2);
 
             html::push_html(&mut s,
-                            Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
+                            Footnotes::new(
+                                CodeBlocks::new(
+                                    LinkReplacer::new(
+                                        HeadingLinks::new(p, None),
+                                        links))));
 
             fmt.write_str(&s)
         }
@@ -1021,7 +1171,7 @@ impl<'a> fmt::Display for MarkdownWithToc<'a> {
         let MarkdownWithToc(md, render_type) = *self;
 
         if render_type == RenderType::Hoedown {
-            render(fmt, md, true, 0)
+            render(fmt, md, &[], true, 0)
         } else {
             let mut opts = Options::empty();
             opts.insert(OPTION_ENABLE_TABLES);
@@ -1050,7 +1200,7 @@ impl<'a> fmt::Display for MarkdownHtml<'a> {
         // This is actually common enough to special-case
         if md.is_empty() { return Ok(()) }
         if render_type == RenderType::Hoedown {
-            render(fmt, md, false, HOEDOWN_HTML_ESCAPE)
+            render(fmt, md, &[], false, HOEDOWN_HTML_ESCAPE)
         } else {
             let mut opts = Options::empty();
             opts.insert(OPTION_ENABLE_TABLES);
@@ -1076,7 +1226,7 @@ impl<'a> fmt::Display for MarkdownHtml<'a> {
 
 impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        let MarkdownSummaryLine(md) = *self;
+        let MarkdownSummaryLine(md, links) = *self;
         // This is actually common enough to special-case
         if md.is_empty() { return Ok(()) }
 
@@ -1084,7 +1234,7 @@ impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
 
         let mut s = String::new();
 
-        html::push_html(&mut s, SummaryLine::new(p));
+        html::push_html(&mut s, LinkReplacer::new(SummaryLine::new(p), links));
 
         fmt.write_str(&s)
     }
@@ -1140,6 +1290,90 @@ pub fn plain_summary_line(md: &str) -> String {
     s
 }
 
+pub fn markdown_links(md: &str, render_type: RenderType) -> Vec<String> {
+    if md.is_empty() {
+        return vec![];
+    }
+
+    match render_type {
+        RenderType::Hoedown => {
+            extern fn hoedown_link(
+                _ob: *mut hoedown_buffer,
+                _content: *const hoedown_buffer,
+                link: *const hoedown_buffer,
+                _title: *const hoedown_buffer,
+                data: *const hoedown_renderer_data,
+                _line: libc::size_t
+            ) -> libc::c_int {
+                if link.is_null() {
+                    return 0;
+                }
+
+                let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
+                let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
+
+                if let Some(ref mut links) = opaque.links_out {
+                    let s = unsafe { (*link).as_bytes() };
+                    let s = str::from_utf8(&s).unwrap().to_owned();
+
+                    debug!("found link: {}", s);
+
+                    links.push(s);
+                }
+
+                //returning 0 here means "emit the span verbatim", but we're not using the output
+                //anyway so we don't really care
+                0
+            }
+
+            unsafe {
+                let ob = hoedown_buffer_new(DEF_OUNIT);
+                let renderer = hoedown_html_renderer_new(0, 0);
+                let mut opaque = MyOpaque {
+                    dfltblk: (*renderer).blockcode.unwrap(),
+                    toc_builder: None,
+                    links_out: Some(vec![]),
+                    links_replace: vec![],
+                };
+                (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
+                        = &mut opaque as *mut _ as *mut libc::c_void;
+                (*renderer).header = Some(hoedown_header);
+                (*renderer).codespan = Some(hoedown_codespan);
+                (*renderer).link = Some(hoedown_link);
+
+                let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
+                hoedown_document_render(document, ob, md.as_ptr(),
+                                        md.len() as libc::size_t);
+                hoedown_document_free(document);
+
+                hoedown_html_renderer_free(renderer);
+                hoedown_buffer_free(ob);
+
+                opaque.links_out.unwrap()
+            }
+        }
+        RenderType::Pulldown => {
+            let mut opts = Options::empty();
+            opts.insert(OPTION_ENABLE_TABLES);
+            opts.insert(OPTION_ENABLE_FOOTNOTES);
+
+            let p = Parser::new_ext(md, opts);
+
+            let iter = Footnotes::new(HeadingLinks::new(p, None));
+            let mut links = vec![];
+
+            for ev in iter {
+                if let Event::Start(Tag::Link(dest, _)) = ev {
+                    debug!("found link: {}", dest);
+                    links.push(dest.into_owned());
+                }
+            }
+
+            links
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::{LangString, Markdown, MarkdownHtml};
@@ -1191,14 +1425,14 @@ mod tests {
     #[test]
     fn issue_17736() {
         let markdown = "# title";
-        format!("{}", Markdown(markdown, RenderType::Pulldown));
+        format!("{}", Markdown(markdown, &[], RenderType::Pulldown));
         reset_ids(true);
     }
 
     #[test]
     fn test_header() {
         fn t(input: &str, expect: &str) {
-            let output = format!("{}", Markdown(input, RenderType::Pulldown));
+            let output = format!("{}", Markdown(input, &[], RenderType::Pulldown));
             assert_eq!(output, expect, "original: {}", input);
             reset_ids(true);
         }
@@ -1220,7 +1454,7 @@ mod tests {
     #[test]
     fn test_header_ids_multiple_blocks() {
         fn t(input: &str, expect: &str) {
-            let output = format!("{}", Markdown(input, RenderType::Pulldown));
+            let output = format!("{}", Markdown(input, &[], RenderType::Pulldown));
             assert_eq!(output, expect, "original: {}", input);
         }
 
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index cfa09ea30a8..b58a59f1217 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -421,7 +421,7 @@ thread_local!(pub static CURRENT_LOCATION_KEY: RefCell<Vec<String>> =
 thread_local!(pub static USED_ID_MAP: RefCell<FxHashMap<String, usize>> =
                     RefCell::new(init_ids()));
 
-pub fn render_text<F: FnMut(RenderType) -> String>(mut render: F) -> (String, String) {
+pub fn render_text<T, F: FnMut(RenderType) -> T>(mut render: F) -> (T, T) {
     // Save the state of USED_ID_MAP so it only gets updated once even
     // though we're rendering twice.
     let orig_used_id_map = USED_ID_MAP.with(|map| map.borrow().clone());
@@ -1284,7 +1284,7 @@ impl DocFolder for Cache {
             clean::FunctionItem(..) | clean::ModuleItem(..) |
             clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) |
             clean::ConstantItem(..) | clean::StaticItem(..) |
-            clean::UnionItem(..) | clean::ForeignTypeItem
+            clean::UnionItem(..) | clean::ForeignTypeItem | clean::MacroItem(..)
             if !self.stripped_mod => {
                 // Re-exported items mean that the same id can show up twice
                 // in the rustdoc ast that we're looking at. We know,
@@ -1861,12 +1861,14 @@ fn document(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Re
 /// rendering between Pulldown and Hoedown.
 fn render_markdown(w: &mut fmt::Formatter,
                    md_text: &str,
+                   links: Vec<(String, String)>,
                    span: Span,
                    render_type: RenderType,
                    prefix: &str,
                    scx: &SharedContext)
                    -> fmt::Result {
-    let (hoedown_output, pulldown_output) = render_text(|ty| format!("{}", Markdown(md_text, ty)));
+    let (hoedown_output, pulldown_output) =
+        render_text(|ty| format!("{}", Markdown(md_text, &links, ty)));
     let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output);
     differences.retain(|s| {
         match *s {
@@ -1898,7 +1900,13 @@ fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLin
         } else {
             format!("{}", &plain_summary_line(Some(s)))
         };
-        render_markdown(w, &markdown, item.source.clone(), cx.render_type, prefix, &cx.shared)?;
+        render_markdown(w,
+                        &markdown,
+                        item.links(),
+                        item.source.clone(),
+                        cx.render_type,
+                        prefix,
+                        &cx.shared)?;
     } else if !prefix.is_empty() {
         write!(w, "<div class='docblock'>{}</div>", prefix)?;
     }
@@ -1924,7 +1932,13 @@ fn document_full(w: &mut fmt::Formatter, item: &clean::Item,
                  cx: &Context, prefix: &str) -> fmt::Result {
     if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) {
         debug!("Doc block: =====\n{}\n=====", s);
-        render_markdown(w, &*s, item.source.clone(), cx.render_type, prefix, &cx.shared)?;
+        render_markdown(w,
+                        &*s,
+                        item.links(),
+                        item.source.clone(),
+                        cx.render_type,
+                        prefix,
+                        &cx.shared)?;
     } else if !prefix.is_empty() {
         write!(w, "<div class='docblock'>{}</div>", prefix)?;
     }
@@ -2146,10 +2160,10 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
                        stab_docs = stab_docs,
                        docs = if cx.render_type == RenderType::Hoedown {
                            format!("{}",
-                                   shorter(Some(&Markdown(doc_value,
+                                   shorter(Some(&Markdown(doc_value, &myitem.links(),
                                                           RenderType::Hoedown).to_string())))
                        } else {
-                           format!("{}", MarkdownSummaryLine(doc_value))
+                           format!("{}", MarkdownSummaryLine(doc_value, &myitem.links()))
                        },
                        class = myitem.type_(),
                        stab = myitem.stability_class().unwrap_or("".to_string()),
@@ -3338,7 +3352,8 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
         write!(w, "</span>")?;
         write!(w, "</h3>\n")?;
         if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) {
-            write!(w, "<div class='docblock'>{}</div>", Markdown(&*dox, cx.render_type))?;
+            write!(w, "<div class='docblock'>{}</div>",
+                   Markdown(&*dox, &i.impl_item.links(), cx.render_type))?;
         }
     }
 
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 2e2dba7681c..6347c4a58dd 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -503,6 +503,11 @@ where R: 'static + Send, F: 'static + Send + FnOnce(Output) -> R {
     let crate_name = matches.opt_str("crate-name");
     let crate_version = matches.opt_str("crate-version");
     let plugin_path = matches.opt_str("plugin-path");
+    let render_type = if matches.opt_present("disable-commonmark") {
+        RenderType::Hoedown
+    } else {
+        RenderType::Pulldown
+    };
 
     info!("starting to run rustc");
     let display_warnings = matches.opt_present("display-warnings");
@@ -517,7 +522,7 @@ where R: 'static + Send, F: 'static + Send + FnOnce(Output) -> R {
 
         let (mut krate, renderinfo) =
             core::run_core(paths, cfgs, externs, Input::File(cratefile), triple, maybe_sysroot,
-                           display_warnings, force_unstable_if_unmarked);
+                           display_warnings, force_unstable_if_unmarked, render_type);
 
         info!("finished with rustc");
 
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index af93505293c..9af2ebf0661 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -104,7 +104,7 @@ pub fn render(input: &Path, mut output: PathBuf, matches: &getopts::Matches,
     } else {
         // Save the state of USED_ID_MAP so it only gets updated once even
         // though we're rendering twice.
-        render_text(|ty| format!("{}", Markdown(text, ty)))
+        render_text(|ty| format!("{}", Markdown(text, &[], ty)))
     };
 
     let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output);
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 1cb52d735bb..7b208465369 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -40,11 +40,11 @@ use doctree::*;
 // also, is there some reason that this doesn't use the 'visit'
 // framework from syntax?
 
-pub struct RustdocVisitor<'a, 'tcx: 'a> {
-    cstore: &'tcx CrateStore,
+pub struct RustdocVisitor<'a, 'tcx: 'a, 'rcx: 'a> {
+    cstore: &'a CrateStore,
     pub module: Module,
     pub attrs: hir::HirVec<ast::Attribute>,
-    pub cx: &'a core::DocContext<'a, 'tcx>,
+    pub cx: &'a core::DocContext<'a, 'tcx, 'rcx>,
     view_item_stack: FxHashSet<ast::NodeId>,
     inlining: bool,
     /// Is the current module and all of its parents public?
@@ -52,9 +52,9 @@ pub struct RustdocVisitor<'a, 'tcx: 'a> {
     reexported_macros: FxHashSet<DefId>,
 }
 
-impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
-    pub fn new(cstore: &'tcx CrateStore,
-               cx: &'a core::DocContext<'a, 'tcx>) -> RustdocVisitor<'a, 'tcx> {
+impl<'a, 'tcx, 'rcx> RustdocVisitor<'a, 'tcx, 'rcx> {
+    pub fn new(cstore: &'a CrateStore,
+               cx: &'a core::DocContext<'a, 'tcx, 'rcx>) -> RustdocVisitor<'a, 'tcx, 'rcx> {
         // If the root is re-exported, terminate all recursion.
         let mut stack = FxHashSet();
         stack.insert(ast::CRATE_NODE_ID);
diff --git a/src/librustdoc/visit_lib.rs b/src/librustdoc/visit_lib.rs
index 2fd47fa0a6d..15a8b58d0f6 100644
--- a/src/librustdoc/visit_lib.rs
+++ b/src/librustdoc/visit_lib.rs
@@ -22,8 +22,8 @@ use clean::{AttributesExt, NestedAttributesExt};
 
 /// Similar to `librustc_privacy::EmbargoVisitor`, but also takes
 /// specific rustdoc annotations into account (i.e. `doc(hidden)`)
-pub struct LibEmbargoVisitor<'a, 'b: 'a, 'tcx: 'b> {
-    cx: &'a ::core::DocContext<'b, 'tcx>,
+pub struct LibEmbargoVisitor<'a, 'tcx: 'a, 'rcx: 'a> {
+    cx: &'a ::core::DocContext<'a, 'tcx, 'rcx>,
     // Accessibility levels for reachable nodes
     access_levels: RefMut<'a, AccessLevels<DefId>>,
     // Previous accessibility level, None means unreachable
@@ -32,8 +32,8 @@ pub struct LibEmbargoVisitor<'a, 'b: 'a, 'tcx: 'b> {
     visited_mods: FxHashSet<DefId>,
 }
 
-impl<'a, 'b, 'tcx> LibEmbargoVisitor<'a, 'b, 'tcx> {
-    pub fn new(cx: &'a ::core::DocContext<'b, 'tcx>) -> LibEmbargoVisitor<'a, 'b, 'tcx> {
+impl<'a, 'tcx, 'rcx> LibEmbargoVisitor<'a, 'tcx, 'rcx> {
+    pub fn new(cx: &'a ::core::DocContext<'a, 'tcx, 'rcx>) -> LibEmbargoVisitor<'a, 'tcx, 'rcx> {
         LibEmbargoVisitor {
             cx,
             access_levels: cx.access_levels.borrow_mut(),
diff --git a/src/test/rustdoc/intra-links.rs b/src/test/rustdoc/intra-links.rs
new file mode 100644
index 00000000000..aa6f5538754
--- /dev/null
+++ b/src/test/rustdoc/intra-links.rs
@@ -0,0 +1,60 @@
+// 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.
+
+// @has intra_links/index.html
+// @has - '//a/@href' '../intra_links/struct.ThisType.html'
+// @has - '//a/@href' '../intra_links/enum.ThisEnum.html'
+// @has - '//a/@href' '../intra_links/trait.ThisTrait.html'
+// @has - '//a/@href' '../intra_links/type.ThisAlias.html'
+// @has - '//a/@href' '../intra_links/union.ThisUnion.html'
+// @has - '//a/@href' '../intra_links/fn.this_function.html'
+// @has - '//a/@href' '../intra_links/constant.THIS_CONST.html'
+// @has - '//a/@href' '../intra_links/static.THIS_STATIC.html'
+// @has - '//a/@href' '../intra_links/macro.this_macro.html'
+// @has - '//a/@href' '../intra_links/trait.SoAmbiguous.html'
+// @has - '//a/@href' '../intra_links/fn.SoAmbiguous.html'
+//! In this crate we would like to link to:
+//!
+//! * [`ThisType`](ThisType)
+//! * [`ThisEnum`](ThisEnum)
+//! * [`ThisTrait`](ThisTrait)
+//! * [`ThisAlias`](ThisAlias)
+//! * [`ThisUnion`](ThisUnion)
+//! * [`this_function`](this_function())
+//! * [`THIS_CONST`](const@THIS_CONST)
+//! * [`THIS_STATIC`](static@THIS_STATIC)
+//! * [`this_macro`](this_macro!)
+//!
+//! In addition, there's some specifics we want to look at. There's [a trait called
+//! SoAmbiguous][ambig-trait], but there's also [a function called SoAmbiguous][ambig-fn] too!
+//! Whatever shall we do?
+//!
+//! [ambig-trait]: trait@SoAmbiguous
+//! [ambig-fn]: SoAmbiguous()
+
+#[macro_export]
+macro_rules! this_macro {
+    () => {};
+}
+
+pub struct ThisType;
+pub enum ThisEnum { ThisVariant, }
+pub trait ThisTrait {}
+pub type ThisAlias = Result<(), ()>;
+pub union ThisUnion { this_field: usize, }
+
+pub fn this_function() {}
+pub const THIS_CONST: usize = 5usize;
+pub static THIS_STATIC: usize = 5usize;
+
+pub trait SoAmbiguous {}
+
+#[allow(bad_style)]
+pub fn SoAmbiguous() {}
diff --git a/src/tools/error_index_generator/main.rs b/src/tools/error_index_generator/main.rs
index aedae366c41..8454e71fa3f 100644
--- a/src/tools/error_index_generator/main.rs
+++ b/src/tools/error_index_generator/main.rs
@@ -100,7 +100,7 @@ impl Formatter for HTMLFormatter {
 
         // Description rendered as markdown.
         match info.description {
-            Some(ref desc) => write!(output, "{}", Markdown(desc, RenderType::Hoedown))?,
+            Some(ref desc) => write!(output, "{}", Markdown(desc, &[], RenderType::Hoedown))?,
             None => write!(output, "<p>No description.</p>\n")?,
         }