about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-04-19 05:00:10 -0700
committerbors <bors@rust-lang.org>2016-04-19 05:00:10 -0700
commit478a33dabc4e6f2f501f476c79b56178d9df4f37 (patch)
treea3c02c01a6d1b0eefbb5e28a932e6968b8fc9b14
parente8c0aeb88ba1e12d0c0a1d0ed7b4a90b44460a29 (diff)
parent77b409a674f62e3be13ec9339b5c8272c9b5c0a9 (diff)
downloadrust-478a33dabc4e6f2f501f476c79b56178d9df4f37.tar.gz
rust-478a33dabc4e6f2f501f476c79b56178d9df4f37.zip
Auto merge of #33002 - mitaa:rdoc-cross-impls, r=alexcrichton
rustdoc: refine cross-crate impl inlining

This changes the current rule that impls within `doc(hidden)` modules aren't inlined, to only inlining impls where the implemented trait and type are reachable in documentation.

fixes #14586
fixes #31948

.. and also applies the reachability checking to cross-crate links.

fixes #28480

r? @alexcrichton
-rw-r--r--src/librustc/middle/cstore.rs1
-rw-r--r--src/librustc/middle/privacy.rs9
-rw-r--r--src/librustdoc/clean/inline.rs38
-rw-r--r--src/librustdoc/clean/mod.rs28
-rw-r--r--src/librustdoc/core.rs79
-rw-r--r--src/librustdoc/html/format.rs4
-rw-r--r--src/librustdoc/html/render.rs54
-rw-r--r--src/librustdoc/lib.rs22
-rw-r--r--src/librustdoc/passes.rs5
-rw-r--r--src/librustdoc/test.rs9
-rw-r--r--src/librustdoc/visit_ast.rs48
-rw-r--r--src/librustdoc/visit_lib.rs108
-rw-r--r--src/test/auxiliary/rustdoc-hidden-sig.rs22
-rw-r--r--src/test/auxiliary/rustdoc-nonreachable-impls.rs44
-rw-r--r--src/test/rustdoc/inline_cross/issue-28480.rs23
-rw-r--r--src/test/rustdoc/inline_cross/issue-31948-1.rs37
-rw-r--r--src/test/rustdoc/inline_cross/issue-31948-2.rs31
-rw-r--r--src/test/rustdoc/inline_cross/issue-31948.rs39
18 files changed, 472 insertions, 129 deletions
diff --git a/src/librustc/middle/cstore.rs b/src/librustc/middle/cstore.rs
index d3db0804c24..c1a8f747de1 100644
--- a/src/librustc/middle/cstore.rs
+++ b/src/librustc/middle/cstore.rs
@@ -113,6 +113,7 @@ pub enum InlinedItemRef<'a> {
 /// LOCAL_CRATE in their DefId.
 pub const LOCAL_CRATE: ast::CrateNum = 0;
 
+#[derive(Copy, Clone)]
 pub struct ChildItem {
     pub def: DefLike,
     pub name: ast::Name,
diff --git a/src/librustc/middle/privacy.rs b/src/librustc/middle/privacy.rs
index c1dc727449a..478f662d096 100644
--- a/src/librustc/middle/privacy.rs
+++ b/src/librustc/middle/privacy.rs
@@ -15,10 +15,11 @@
 use util::nodemap::{DefIdSet, FnvHashMap};
 
 use std::hash::Hash;
+use std::fmt;
 use syntax::ast::NodeId;
 
 // Accessibility levels, sorted in ascending order
-#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum AccessLevel {
     // Exported items + items participating in various kinds of public interfaces,
     // but not directly nameable. For example, if function `fn f() -> T {...}` is
@@ -56,6 +57,12 @@ impl<Id: Hash + Eq> Default for AccessLevels<Id> {
     }
 }
 
+impl<Id: Hash + Eq + fmt::Debug> fmt::Debug for AccessLevels<Id> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Debug::fmt(&self.map, f)
+    }
+}
+
 /// A set containing all exported definitions from external crates.
 /// The set does not contain any entries from local crates.
 pub type ExternalExports = DefIdSet;
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 1e9796c5f66..fd57a452e0a 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -26,9 +26,9 @@ use rustc::middle::stability;
 
 use rustc_const_eval::lookup_const_by_id;
 
-use core::DocContext;
+use core::{DocContext, DocAccessLevels};
 use doctree;
-use clean::{self, Attributes, GetDefId};
+use clean::{self, GetDefId};
 
 use super::{Clean, ToSource};
 
@@ -116,7 +116,7 @@ fn try_inline_def(cx: &DocContext, tcx: &TyCtxt,
         }
         _ => return None,
     };
-    cx.inlined.borrow_mut().as_mut().unwrap().insert(did);
+    cx.renderinfo.borrow_mut().inlined.insert(did);
     ret.push(clean::Item {
         source: clean::Span::empty(),
         name: Some(tcx.item_name(did).to_string()),
@@ -146,7 +146,7 @@ pub fn record_extern_fqn(cx: &DocContext, did: DefId, kind: clean::TypeKind) {
             elem.data.to_string()
         });
         let fqn = once(crate_name).chain(relative).collect();
-        cx.external_paths.borrow_mut().as_mut().unwrap().insert(did, (fqn, kind));
+        cx.renderinfo.borrow_mut().external_paths.insert(did, (fqn, kind));
     }
 }
 
@@ -260,11 +260,6 @@ pub fn build_impls(cx: &DocContext,
             match def {
                 cstore::DlImpl(did) => build_impl(cx, tcx, did, impls),
                 cstore::DlDef(Def::Mod(did)) => {
-                    // Don't recurse if this is a #[doc(hidden)] module
-                    if load_attrs(cx, tcx, did).list("doc").has_word("hidden") {
-                        return;
-                    }
-
                     for item in tcx.sess.cstore.item_children(did) {
                         populate_impls(cx, tcx, item.def, impls)
                     }
@@ -295,16 +290,17 @@ pub fn build_impl(cx: &DocContext,
                   tcx: &TyCtxt,
                   did: DefId,
                   ret: &mut Vec<clean::Item>) {
-    if !cx.inlined.borrow_mut().as_mut().unwrap().insert(did) {
+    if !cx.renderinfo.borrow_mut().inlined.insert(did) {
         return
     }
 
     let attrs = load_attrs(cx, tcx, did);
     let associated_trait = tcx.impl_trait_ref(did);
-    if let Some(ref t) = associated_trait {
-        // If this is an impl for a #[doc(hidden)] trait, be sure to not inline
-        let trait_attrs = load_attrs(cx, tcx, t.def_id);
-        if trait_attrs.list("doc").has_word("hidden") {
+
+    // Only inline impl if the implemented trait is
+    // reachable in rustdoc generated documentation
+    if let Some(traitref) = associated_trait {
+        if !cx.access_levels.borrow().is_doc_reachable(traitref.def_id) {
             return
         }
     }
@@ -330,6 +326,17 @@ pub fn build_impl(cx: &DocContext,
         });
     }
 
+    let ty = tcx.lookup_item_type(did);
+    let for_ = ty.ty.clean(cx);
+
+    // Only inline impl if the implementing type is
+    // reachable in rustdoc generated documentation
+    if let Some(did) = for_.def_id() {
+        if !cx.access_levels.borrow().is_doc_reachable(did) {
+            return
+        }
+    }
+
     let predicates = tcx.lookup_predicates(did);
     let trait_items = tcx.sess.cstore.impl_items(did)
             .iter()
@@ -412,7 +419,6 @@ pub fn build_impl(cx: &DocContext,
         }
     }).collect::<Vec<_>>();
     let polarity = tcx.trait_impl_polarity(did);
-    let ty = tcx.lookup_item_type(did);
     let trait_ = associated_trait.clean(cx).map(|bound| {
         match bound {
             clean::TraitBound(polyt, _) => polyt.trait_,
@@ -436,7 +442,7 @@ pub fn build_impl(cx: &DocContext,
             derived: clean::detect_derived(&attrs),
             provided_trait_methods: provided,
             trait_: trait_,
-            for_: ty.ty.clean(cx),
+            for_: for_,
             generics: (&ty.generics, &predicates, subst::TypeSpace).clean(cx),
             items: trait_items,
             polarity: polarity.map(|p| { p.clean(cx) }),
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 023da1612e6..c9f40a1adae 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -36,6 +36,7 @@ use syntax::ptr::P;
 
 use rustc_trans::back::link;
 use rustc::middle::cstore::{self, CrateStore};
+use rustc::middle::privacy::AccessLevels;
 use rustc::hir::def::Def;
 use rustc::hir::def_id::{DefId, DefIndex};
 use rustc::ty::subst::{self, ParamSpace, VecPerParamSpace};
@@ -47,15 +48,17 @@ use rustc::hir;
 use std::collections::{HashMap, HashSet};
 use std::path::PathBuf;
 use std::rc::Rc;
+use std::sync::Arc;
 use std::u32;
 use std::env::current_dir;
+use std::mem;
 
 use core::DocContext;
 use doctree;
 use visit_ast;
 use html::item_type::ItemType;
 
-mod inline;
+pub mod inline;
 mod simplify;
 
 // extract the stability index for a node from tcx, if possible
@@ -113,13 +116,16 @@ impl<T: Clean<U>, U> Clean<Vec<U>> for P<[T]> {
     }
 }
 
-#[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
+#[derive(Clone, Debug)]
 pub struct Crate {
     pub name: String,
     pub src: PathBuf,
     pub module: Option<Item>,
     pub externs: Vec<(ast::CrateNum, ExternalCrate)>,
     pub primitives: Vec<PrimitiveType>,
+    pub access_levels: Arc<AccessLevels<DefId>>,
+    // These are later on moved into `CACHEKEY`, leaving the map empty.
+    // Only here so that they can be filtered through the rustdoc passes.
     pub external_traits: HashMap<DefId, Trait>,
 }
 
@@ -128,14 +134,20 @@ struct CrateNum(ast::CrateNum);
 impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
     fn clean(&self, cx: &DocContext) -> Crate {
         use rustc::session::config::Input;
+        use ::visit_lib::LibEmbargoVisitor;
 
         if let Some(t) = cx.tcx_opt() {
             cx.deref_trait_did.set(t.lang_items.deref_trait());
+            cx.renderinfo.borrow_mut().deref_trait_did = cx.deref_trait_did.get();
         }
 
         let mut externs = Vec::new();
         for cnum in cx.sess().cstore.crates() {
             externs.push((cnum, CrateNum(cnum).clean(cx)));
+            if cx.tcx_opt().is_some() {
+                // Analyze doc-reachability for extern items
+                LibEmbargoVisitor::new(cx).visit_lib(cnum);
+            }
         }
         externs.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
 
@@ -205,14 +217,17 @@ impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
             Input::Str { ref name, .. } => PathBuf::from(name.clone()),
         };
 
+        let mut access_levels = cx.access_levels.borrow_mut();
+        let mut external_traits = cx.external_traits.borrow_mut();
+
         Crate {
             name: name.to_string(),
             src: src,
             module: Some(module),
             externs: externs,
             primitives: primitives,
-            external_traits: cx.external_traits.borrow_mut().take()
-                               .unwrap_or(HashMap::new()),
+            access_levels: Arc::new(mem::replace(&mut access_levels, Default::default())),
+            external_traits: mem::replace(&mut external_traits, Default::default()),
         }
     }
 }
@@ -541,8 +556,7 @@ impl Clean<TyParam> for hir::TyParam {
 
 impl<'tcx> Clean<TyParam> for ty::TypeParameterDef<'tcx> {
     fn clean(&self, cx: &DocContext) -> TyParam {
-        cx.external_typarams.borrow_mut().as_mut().unwrap()
-          .insert(self.def_id, self.name.clean(cx));
+        cx.renderinfo.borrow_mut().external_typarams.insert(self.def_id, self.name.clean(cx));
         TyParam {
             name: self.name.clean(cx),
             did: self.def_id,
@@ -2685,7 +2699,7 @@ fn register_def(cx: &DocContext, def: Def) -> DefId {
     inline::record_extern_fqn(cx, did, kind);
     if let TypeTrait = kind {
         let t = inline::build_external_trait(cx, tcx, did);
-        cx.external_traits.borrow_mut().as_mut().unwrap().insert(did, t);
+        cx.external_traits.borrow_mut().insert(did, t);
     }
     did
 }
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 6b7aa103e1d..b7c60b8a524 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -29,12 +29,13 @@ use syntax::feature_gate::UnstableFeatures;
 use syntax::parse::token;
 
 use std::cell::{RefCell, Cell};
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 use std::rc::Rc;
 
 use visit_ast::RustdocVisitor;
 use clean;
 use clean::Clean;
+use html::render::RenderInfo;
 
 pub use rustc::session::config::Input;
 pub use rustc::session::search_paths::SearchPaths;
@@ -45,19 +46,24 @@ pub enum MaybeTyped<'a, 'tcx: 'a> {
     NotTyped(&'a session::Session)
 }
 
-pub type ExternalPaths = RefCell<Option<HashMap<DefId,
-                                                (Vec<String>, clean::TypeKind)>>>;
+pub type Externs = HashMap<String, Vec<String>>;
+pub type ExternalPaths = HashMap<DefId, (Vec<String>, clean::TypeKind)>;
 
 pub struct DocContext<'a, 'tcx: 'a> {
     pub map: &'a hir_map::Map<'tcx>,
     pub maybe_typed: MaybeTyped<'a, 'tcx>,
     pub input: Input,
-    pub external_paths: ExternalPaths,
-    pub external_traits: RefCell<Option<HashMap<DefId, clean::Trait>>>,
-    pub external_typarams: RefCell<Option<HashMap<DefId, String>>>,
-    pub inlined: RefCell<Option<HashSet<DefId>>>,
     pub all_crate_impls: RefCell<HashMap<ast::CrateNum, Vec<clean::Item>>>,
     pub deref_trait_did: Cell<Option<DefId>>,
+    // 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
+    // the access levels from crateanalysis.
+    /// Later on moved into `clean::Crate`
+    pub access_levels: RefCell<AccessLevels<DefId>>,
+    /// Later on moved into `html::render::CACHE_KEY`
+    pub renderinfo: RefCell<RenderInfo>,
+    /// Later on moved through `clean::Crate` into `html::render::CACHE_KEY`
+    pub external_traits: RefCell<HashMap<DefId, clean::Trait>>,
 }
 
 impl<'b, 'tcx> DocContext<'b, 'tcx> {
@@ -81,20 +87,23 @@ impl<'b, 'tcx> DocContext<'b, 'tcx> {
     }
 }
 
-pub struct CrateAnalysis {
-    pub access_levels: AccessLevels<DefId>,
-    pub external_paths: ExternalPaths,
-    pub external_typarams: RefCell<Option<HashMap<DefId, String>>>,
-    pub inlined: RefCell<Option<HashSet<DefId>>>,
-    pub deref_trait_did: Option<DefId>,
+pub trait DocAccessLevels {
+    fn is_doc_reachable(&self, DefId) -> bool;
 }
 
-pub type Externs = HashMap<String, Vec<String>>;
+impl DocAccessLevels for AccessLevels<DefId> {
+    fn is_doc_reachable(&self, did: DefId) -> bool {
+        self.is_public(did)
+    }
+}
 
-pub fn run_core(search_paths: SearchPaths, cfgs: Vec<String>, externs: Externs,
-                input: Input, triple: Option<String>)
-                -> (clean::Crate, CrateAnalysis) {
 
+pub fn run_core(search_paths: SearchPaths,
+                cfgs: Vec<String>,
+                externs: Externs,
+                input: Input,
+                triple: Option<String>) -> (clean::Crate, RenderInfo)
+{
     // Parse, resolve, and typecheck the given crate.
 
     let cpath = match input {
@@ -148,7 +157,7 @@ pub fn run_core(search_paths: SearchPaths, cfgs: Vec<String>, externs: Externs,
     let arenas = ty::CtxtArenas::new();
     let hir_map = driver::make_map(&sess, &mut hir_forest);
 
-    let krate_and_analysis = abort_on_err(driver::phase_3_run_analysis_passes(&sess,
+    abort_on_err(driver::phase_3_run_analysis_passes(&sess,
                                                      &cstore,
                                                      hir_map,
                                                      &arenas,
@@ -175,42 +184,20 @@ pub fn run_core(search_paths: SearchPaths, cfgs: Vec<String>, externs: Externs,
             map: &tcx.map,
             maybe_typed: Typed(tcx),
             input: input,
-            external_traits: RefCell::new(Some(HashMap::new())),
-            external_typarams: RefCell::new(Some(HashMap::new())),
-            external_paths: RefCell::new(Some(HashMap::new())),
-            inlined: RefCell::new(Some(HashSet::new())),
             all_crate_impls: RefCell::new(HashMap::new()),
             deref_trait_did: Cell::new(None),
+            access_levels: RefCell::new(access_levels),
+            external_traits: RefCell::new(HashMap::new()),
+            renderinfo: RefCell::new(Default::default()),
         };
         debug!("crate: {:?}", ctxt.map.krate());
 
-        let mut analysis = CrateAnalysis {
-            access_levels: access_levels,
-            external_paths: RefCell::new(None),
-            external_typarams: RefCell::new(None),
-            inlined: RefCell::new(None),
-            deref_trait_did: None,
-        };
-
         let krate = {
-            let mut v = RustdocVisitor::new(&ctxt, Some(&analysis));
+            let mut v = RustdocVisitor::new(&ctxt);
             v.visit(ctxt.map.krate());
             v.clean(&ctxt)
         };
 
-        let external_paths = ctxt.external_paths.borrow_mut().take();
-        *analysis.external_paths.borrow_mut() = external_paths;
-
-        let map = ctxt.external_typarams.borrow_mut().take();
-        *analysis.external_typarams.borrow_mut() = map;
-
-        let map = ctxt.inlined.borrow_mut().take();
-        *analysis.inlined.borrow_mut() = map;
-
-        analysis.deref_trait_did = ctxt.deref_trait_did.get();
-
-        Some((krate, analysis))
-    }), &sess);
-
-    krate_and_analysis.unwrap()
+        Some((krate, ctxt.renderinfo.into_inner()))
+    }), &sess).unwrap()
 }
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 1a228b4af8b..d4212bba590 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -24,6 +24,7 @@ use syntax::abi::Abi;
 use rustc::hir;
 
 use clean;
+use core::DocAccessLevels;
 use html::item_type::ItemType;
 use html::render;
 use html::render::{cache, CURRENT_LOCATION_KEY};
@@ -298,6 +299,9 @@ pub fn href(did: DefId) -> Option<(String, ItemType, Vec<String>)> {
     let mut url = if did.is_local() || cache.inlined.contains(&did) {
         repeat("../").take(loc.len()).collect::<String>()
     } else {
+        if !cache.access_levels.is_doc_reachable(did) {
+            return None
+        }
         match cache.extern_locations[&did.krate] {
             (_, render::Remote(ref s)) => s.to_string(),
             (_, render::Local) => repeat("../").take(loc.len()).collect(),
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index b845ee6ee0a..c08d917589d 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -247,6 +247,11 @@ pub struct Cache {
     /// Set of definitions which have been inlined from external crates.
     pub inlined: HashSet<DefId>,
 
+    // 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
+    // the access levels from crateanalysis.
+    pub access_levels: Arc<AccessLevels<DefId>>,
+
     // Private fields only used when initially crawling a crate to build a cache
 
     stack: Vec<String>,
@@ -254,7 +259,6 @@ pub struct Cache {
     parent_is_trait_impl: bool,
     search_index: Vec<IndexItem>,
     stripped_mod: bool,
-    access_levels: AccessLevels<DefId>,
     deref_trait_did: Option<DefId>,
 
     // In rare case where a structure is defined in one module but implemented
@@ -265,6 +269,16 @@ pub struct Cache {
     orphan_methods: Vec<(DefId, clean::Item)>,
 }
 
+/// Temporary storage for data obtained during `RustdocVisitor::clean()`.
+/// Later on moved into `CACHE_KEY`.
+#[derive(Default)]
+pub struct RenderInfo {
+    pub inlined: HashSet<DefId>,
+    pub external_paths: ::core::ExternalPaths,
+    pub external_typarams: HashMap<DefId, String>,
+    pub deref_trait_did: Option<DefId>,
+}
+
 /// Helper struct to render all source code to HTML pages
 struct SourceCollector<'a> {
     scx: &'a mut SharedContext,
@@ -416,7 +430,8 @@ pub fn run(mut krate: clean::Crate,
            external_html: &ExternalHtml,
            dst: PathBuf,
            passes: HashSet<String>,
-           css_file_extension: Option<PathBuf>) -> Result<(), Error> {
+           css_file_extension: Option<PathBuf>,
+           renderinfo: RenderInfo) -> Result<(), Error> {
     let src_root = match krate.src.parent() {
         Some(p) => p.to_path_buf(),
         None => PathBuf::new(),
@@ -483,19 +498,20 @@ pub fn run(mut krate: clean::Crate,
     };
 
     // Crawl the crate to build various caches used for the output
-    let analysis = ::ANALYSISKEY.with(|a| a.clone());
-    let analysis = analysis.borrow();
-    let access_levels = analysis.as_ref().map(|a| a.access_levels.clone());
-    let access_levels = access_levels.unwrap_or(Default::default());
-    let paths: HashMap<DefId, (Vec<String>, ItemType)> =
-      analysis.as_ref().map(|a| {
-        let paths = a.external_paths.borrow_mut().take().unwrap();
-        paths.into_iter().map(|(k, (v, t))| (k, (v, ItemType::from_type_kind(t)))).collect()
-      }).unwrap_or(HashMap::new());
+    let RenderInfo {
+        inlined,
+        external_paths,
+        external_typarams,
+        deref_trait_did,
+    } = renderinfo;
+
+    let paths = external_paths.into_iter()
+                              .map(|(k, (v, t))| (k, (v, ItemType::from_type_kind(t))))
+                              .collect::<HashMap<_, _>>();
+
     let mut cache = Cache {
         impls: HashMap::new(),
-        external_paths: paths.iter().map(|(&k, v)| (k, v.0.clone()))
-                             .collect(),
+        external_paths: paths.iter().map(|(&k, v)| (k, v.0.clone())).collect(),
         paths: paths,
         implementors: HashMap::new(),
         stack: Vec::new(),
@@ -505,16 +521,12 @@ pub fn run(mut krate: clean::Crate,
         extern_locations: HashMap::new(),
         primitive_locations: HashMap::new(),
         stripped_mod: false,
-        access_levels: access_levels,
+        access_levels: krate.access_levels.clone(),
         orphan_methods: Vec::new(),
         traits: mem::replace(&mut krate.external_traits, HashMap::new()),
-        deref_trait_did: analysis.as_ref().and_then(|a| a.deref_trait_did),
-        typarams: analysis.as_ref().map(|a| {
-            a.external_typarams.borrow_mut().take().unwrap()
-        }).unwrap_or(HashMap::new()),
-        inlined: analysis.as_ref().map(|a| {
-            a.inlined.borrow_mut().take().unwrap()
-        }).unwrap_or(HashSet::new()),
+        deref_trait_did: deref_trait_did,
+        typarams: external_typarams,
+        inlined: inlined,
     };
 
     // Cache where all our extern crates are located
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index bc7c7c5e0ca..86aad10e02f 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -48,14 +48,11 @@ extern crate rustc_unicode;
 
 extern crate serialize as rustc_serialize; // used by deriving
 
-use std::cell::RefCell;
 use std::collections::HashMap;
 use std::default::Default;
 use std::env;
-use std::io::Read;
 use std::path::PathBuf;
 use std::process;
-use std::rc::Rc;
 use std::sync::mpsc::channel;
 
 use externalfiles::ExternalHtml;
@@ -83,6 +80,7 @@ pub mod markdown;
 pub mod passes;
 pub mod plugins;
 pub mod visit_ast;
+pub mod visit_lib;
 pub mod test;
 mod flock;
 
@@ -113,12 +111,9 @@ const DEFAULT_PASSES: &'static [&'static str] = &[
     "unindent-comments",
 ];
 
-thread_local!(pub static ANALYSISKEY: Rc<RefCell<Option<core::CrateAnalysis>>> = {
-    Rc::new(RefCell::new(None))
-});
-
 struct Output {
     krate: clean::Crate,
+    renderinfo: html::render::RenderInfo,
     passes: Vec<String>,
 }
 
@@ -302,14 +297,15 @@ pub fn main_args(args: &[String]) -> isize {
             return 1;
         }
     };
-    let Output { krate, passes, } = out;
+    let Output { krate, passes, renderinfo } = out;
     info!("going to format");
     match matches.opt_str("w").as_ref().map(|s| &**s) {
         Some("html") | None => {
             html::render::run(krate, &external_html,
                               output.unwrap_or(PathBuf::from("doc")),
                               passes.into_iter().collect(),
-                              css_file_extension)
+                              css_file_extension,
+                              renderinfo)
                 .expect("failed to generate documentation")
         }
         Some(s) => {
@@ -380,12 +376,8 @@ fn rust_input(cratefile: &str, externs: core::Externs, matches: &getopts::Matche
         tx.send(core::run_core(paths, cfgs, externs, Input::File(cr),
                                triple)).unwrap();
     });
-    let (mut krate, analysis) = rx.recv().unwrap();
+    let (mut krate, renderinfo) = rx.recv().unwrap();
     info!("finished with rustc");
-    let mut analysis = Some(analysis);
-    ANALYSISKEY.with(|s| {
-        *s.borrow_mut() = analysis.take();
-    });
 
     if let Some(name) = matches.opt_str("crate-name") {
         krate.name = name
@@ -443,5 +435,5 @@ fn rust_input(cratefile: &str, externs: core::Externs, matches: &getopts::Matche
     // Run everything!
     info!("Executing passes/plugins");
     let krate = pm.run_plugins(krate);
-    Output { krate: krate, passes: passes }
+    Output { krate: krate, renderinfo: renderinfo, passes: passes }
 }
diff --git a/src/librustdoc/passes.rs b/src/librustdoc/passes.rs
index 0042afb1fd5..ac1b5452fa7 100644
--- a/src/librustdoc/passes.rs
+++ b/src/librustdoc/passes.rs
@@ -87,10 +87,7 @@ pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
 pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
     // This stripper collects all *retained* nodes.
     let mut retained = DefIdSet();
-    let analysis = super::ANALYSISKEY.with(|a| a.clone());
-    let analysis = analysis.borrow();
-    let analysis = analysis.as_ref().unwrap();
-    let access_levels = analysis.access_levels.clone();
+    let access_levels = krate.access_levels.clone();
 
     // strip all private items
     {
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index 982f477fc4a..5a7050fb42f 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -108,15 +108,14 @@ pub fn run(input: &str,
         map: &map,
         maybe_typed: core::NotTyped(&sess),
         input: input,
-        external_paths: RefCell::new(Some(HashMap::new())),
-        external_traits: RefCell::new(None),
-        external_typarams: RefCell::new(None),
-        inlined: RefCell::new(None),
+        external_traits: RefCell::new(HashMap::new()),
         all_crate_impls: RefCell::new(HashMap::new()),
         deref_trait_did: Cell::new(None),
+        access_levels: Default::default(),
+        renderinfo: Default::default(),
     };
 
-    let mut v = RustdocVisitor::new(&ctx, None);
+    let mut v = RustdocVisitor::new(&ctx);
     v.visit(ctx.map.krate());
     let mut krate = v.clean(&ctx);
     if let Some(name) = crate_name {
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 5c36c38abc5..2bce8f4c2a1 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -21,12 +21,14 @@ use syntax::attr::AttrMetaMethods;
 use syntax::codemap::Span;
 
 use rustc::hir::map as hir_map;
+use rustc::hir::def::Def;
 use rustc::middle::stability;
+use rustc::middle::privacy::AccessLevel;
 
 use rustc::hir;
 
 use core;
-use clean::{Clean, Attributes};
+use clean::{self, Clean, Attributes};
 use doctree::*;
 
 // looks to me like the first two of these are actually
@@ -41,14 +43,12 @@ pub struct RustdocVisitor<'a, 'tcx: 'a> {
     pub module: Module,
     pub attrs: hir::HirVec<ast::Attribute>,
     pub cx: &'a core::DocContext<'a, 'tcx>,
-    pub analysis: Option<&'a core::CrateAnalysis>,
     view_item_stack: HashSet<ast::NodeId>,
     inlining_from_glob: bool,
 }
 
 impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
-    pub fn new(cx: &'a core::DocContext<'a, 'tcx>,
-               analysis: Option<&'a core::CrateAnalysis>) -> RustdocVisitor<'a, 'tcx> {
+    pub fn new(cx: &'a core::DocContext<'a, 'tcx>) -> RustdocVisitor<'a, 'tcx> {
         // If the root is reexported, terminate all recursion.
         let mut stack = HashSet::new();
         stack.insert(ast::CRATE_NODE_ID);
@@ -56,7 +56,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             module: Module::new(None),
             attrs: hir::HirVec::new(),
             cx: cx,
-            analysis: analysis,
             view_item_stack: stack,
             inlining_from_glob: false,
         }
@@ -243,19 +242,40 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             Some(tcx) => tcx,
             None => return false
         };
-        let def = tcx.def_map.borrow()[&id].def_id();
-        let def_node_id = match tcx.map.as_local_node_id(def) {
-            Some(n) => n, None => return false
-        };
-        let analysis = match self.analysis {
-            Some(analysis) => analysis, None => return false
-        };
+        let def = tcx.def_map.borrow()[&id];
+        let def_did = def.def_id();
 
         let use_attrs = tcx.map.attrs(id).clean(self.cx);
+        let is_no_inline = use_attrs.list("doc").has_word("no_inline");
+
+        // For cross-crate impl inlining we need to know whether items are
+        // reachable in documentation - a previously nonreachable item can be
+        // made reachable by cross-crate inlining which we're checking here.
+        // (this is done here because we need to know this upfront)
+        if !def.def_id().is_local() && !is_no_inline {
+            let attrs = clean::inline::load_attrs(self.cx, tcx, def_did);
+            let self_is_hidden = attrs.list("doc").has_word("hidden");
+            match def.base_def {
+                Def::Trait(did) |
+                Def::Struct(did) |
+                Def::Enum(did) |
+                Def::TyAlias(did) if !self_is_hidden => {
+                    self.cx.access_levels.borrow_mut().map.insert(did, AccessLevel::Public);
+                },
+                Def::Mod(did) => if !self_is_hidden {
+                    ::visit_lib::LibEmbargoVisitor::new(self.cx).visit_mod(did);
+                },
+                _ => {},
+            }
+            return false
+        }
+
+        let def_node_id = match tcx.map.as_local_node_id(def_did) {
+            Some(n) => n, None => return false
+        };
 
-        let is_private = !analysis.access_levels.is_public(def);
+        let is_private = !self.cx.access_levels.borrow().is_public(def_did);
         let is_hidden = inherits_doc_hidden(self.cx, def_node_id);
-        let is_no_inline = use_attrs.list("doc").has_word("no_inline");
 
         // Only inline if requested or if the item would otherwise be stripped
         if (!please_inline && !is_private && !is_hidden) || is_no_inline {
diff --git a/src/librustdoc/visit_lib.rs b/src/librustdoc/visit_lib.rs
new file mode 100644
index 00000000000..f56f4c306f9
--- /dev/null
+++ b/src/librustdoc/visit_lib.rs
@@ -0,0 +1,108 @@
+// Copyright 2016 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.
+
+use rustc::middle::cstore::{CrateStore, ChildItem, DefLike};
+use rustc::middle::privacy::{AccessLevels, AccessLevel};
+use rustc::hir::def::Def;
+use rustc::hir::def_id::{CRATE_DEF_INDEX, DefId};
+use rustc::ty::Visibility;
+use syntax::ast;
+
+use std::cell::RefMut;
+
+use clean::{Attributes, Clean};
+
+// FIXME: this may not be exhaustive, but is sufficient for rustdocs current uses
+
+/// 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>,
+    cstore: &'a CrateStore<'tcx>,
+    // Accessibility levels for reachable nodes
+    access_levels: RefMut<'a, AccessLevels<DefId>>,
+    // Previous accessibility level, None means unreachable
+    prev_level: Option<AccessLevel>,
+}
+
+impl<'a, 'b, 'tcx> LibEmbargoVisitor<'a, 'b, 'tcx> {
+    pub fn new(cx: &'a ::core::DocContext<'b, 'tcx>) -> LibEmbargoVisitor<'a, 'b, 'tcx> {
+        LibEmbargoVisitor {
+            cx: cx,
+            cstore: &*cx.sess().cstore,
+            access_levels: cx.access_levels.borrow_mut(),
+            prev_level: Some(AccessLevel::Public),
+        }
+    }
+
+    pub fn visit_lib(&mut self, cnum: ast::CrateNum) {
+        let did = DefId { krate: cnum, index: CRATE_DEF_INDEX };
+        self.visit_mod(did);
+    }
+
+    // Updates node level and returns the updated level
+    fn update(&mut self, did: DefId, level: Option<AccessLevel>) -> Option<AccessLevel> {
+        let attrs: Vec<_> = self.cx.tcx().get_attrs(did).iter()
+                                                        .map(|a| a.clean(self.cx))
+                                                        .collect();
+        let is_hidden = attrs.list("doc").has_word("hidden");
+
+        let old_level = self.access_levels.map.get(&did).cloned();
+        // Accessibility levels can only grow
+        if level > old_level && !is_hidden {
+            self.access_levels.map.insert(did, level.unwrap());
+            level
+        } else {
+            old_level
+        }
+    }
+
+    pub fn visit_mod(&mut self, did: DefId) {
+        for item in self.cstore.item_children(did) {
+            if let DefLike::DlDef(def) = item.def {
+                match def {
+                    Def::Mod(did) |
+                    Def::ForeignMod(did) |
+                    Def::Trait(did) |
+                    Def::Struct(did) |
+                    Def::Enum(did) |
+                    Def::TyAlias(did) |
+                    Def::Fn(did) |
+                    Def::Method(did) |
+                    Def::Static(did, _) |
+                    Def::Const(did) => self.visit_item(did, item),
+                    _ => {}
+                }
+            }
+        }
+    }
+
+    fn visit_item(&mut self, did: DefId, item: ChildItem) {
+        let inherited_item_level = match item.def {
+            DefLike::DlImpl(..) | DefLike::DlField => unreachable!(),
+            DefLike::DlDef(def) => {
+                match def {
+                    Def::ForeignMod(..) => self.prev_level,
+                    _ => if item.vis == Visibility::Public { self.prev_level } else { None }
+                }
+            }
+        };
+
+        let item_level = self.update(did, inherited_item_level);
+
+        if let DefLike::DlDef(Def::Mod(did)) = item.def {
+            let orig_level = self.prev_level;
+
+            self.prev_level = item_level;
+            self.visit_mod(did);
+            self.prev_level = orig_level;
+        }
+    }
+}
diff --git a/src/test/auxiliary/rustdoc-hidden-sig.rs b/src/test/auxiliary/rustdoc-hidden-sig.rs
new file mode 100644
index 00000000000..e2bc153ce0d
--- /dev/null
+++ b/src/test/auxiliary/rustdoc-hidden-sig.rs
@@ -0,0 +1,22 @@
+// Copyright 2016 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.
+
+pub struct Bar;
+
+impl Bar {
+    pub fn bar(_: u8) -> hidden::Hidden {
+        hidden::Hidden
+    }
+}
+
+#[doc(hidden)]
+pub mod hidden {
+    pub struct Hidden;
+}
diff --git a/src/test/auxiliary/rustdoc-nonreachable-impls.rs b/src/test/auxiliary/rustdoc-nonreachable-impls.rs
new file mode 100644
index 00000000000..22a311d5797
--- /dev/null
+++ b/src/test/auxiliary/rustdoc-nonreachable-impls.rs
@@ -0,0 +1,44 @@
+// Copyright 2016 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.
+
+pub struct Foo;
+
+pub trait Woof {}
+pub trait Bark {}
+
+mod private {
+    // should be shown
+    impl ::Woof for ::Foo {}
+
+    pub trait Bar {}
+    pub struct Wibble;
+
+    // these should not be shown
+    impl Bar for ::Foo {}
+    impl Bar for Wibble {}
+    impl ::Bark for Wibble {}
+    impl ::Woof for Wibble {}
+}
+
+#[doc(hidden)]
+pub mod hidden {
+    // should be shown
+    impl ::Bark for ::Foo {}
+
+    pub trait Qux {}
+    pub struct Wobble;
+
+
+    // these should only be shown if they're reexported correctly
+    impl Qux for ::Foo {}
+    impl Qux for Wobble {}
+    impl ::Bark for Wobble {}
+    impl ::Woof for Wobble {}
+}
diff --git a/src/test/rustdoc/inline_cross/issue-28480.rs b/src/test/rustdoc/inline_cross/issue-28480.rs
new file mode 100644
index 00000000000..6b5c5b20147
--- /dev/null
+++ b/src/test/rustdoc/inline_cross/issue-28480.rs
@@ -0,0 +1,23 @@
+// Copyright 2016 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.
+
+// aux-build:rustdoc-hidden-sig.rs
+// build-aux-docs
+// ignore-cross-compile
+
+// @has rustdoc_hidden_sig/struct.Bar.html
+// @!has -  '//a/@title' 'Hidden'
+// @has -  '//a' 'u8'
+extern crate rustdoc_hidden_sig;
+
+// @has issue_28480/struct.Bar.html
+// @!has -  '//a/@title' 'Hidden'
+// @has -  '//a' 'u8'
+pub use rustdoc_hidden_sig::Bar;
diff --git a/src/test/rustdoc/inline_cross/issue-31948-1.rs b/src/test/rustdoc/inline_cross/issue-31948-1.rs
new file mode 100644
index 00000000000..e953b66fce1
--- /dev/null
+++ b/src/test/rustdoc/inline_cross/issue-31948-1.rs
@@ -0,0 +1,37 @@
+// Copyright 2016 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.
+
+// aux-build:rustdoc-nonreachable-impls.rs
+// build-aux-docs
+// ignore-cross-compile
+
+extern crate rustdoc_nonreachable_impls;
+
+// @has issue_31948_1/struct.Wobble.html
+// @has - '//*[@class="impl"]//code' 'Bark for'
+// @has - '//*[@class="impl"]//code' 'Woof for'
+// @!has - '//*[@class="impl"]//code' 'Bar for'
+// @!has - '//*[@class="impl"]//code' 'Qux for'
+pub use rustdoc_nonreachable_impls::hidden::Wobble;
+
+// @has issue_31948_1/trait.Bark.html
+// FIXME(33025): has - '//code' 'for Foo'
+// @has - '//code' 'for Wobble'
+// @!has - '//code' 'for Wibble'
+pub use rustdoc_nonreachable_impls::Bark;
+
+// @has issue_31948_1/trait.Woof.html
+// FIXME(33025): has - '//code' 'for Foo'
+// @has - '//code' 'for Wobble'
+// @!has - '//code' 'for Wibble'
+pub use rustdoc_nonreachable_impls::Woof;
+
+// @!has issue_31948_1/trait.Bar.html
+// @!has issue_31948_1/trait.Qux.html
diff --git a/src/test/rustdoc/inline_cross/issue-31948-2.rs b/src/test/rustdoc/inline_cross/issue-31948-2.rs
new file mode 100644
index 00000000000..0423fa1de3b
--- /dev/null
+++ b/src/test/rustdoc/inline_cross/issue-31948-2.rs
@@ -0,0 +1,31 @@
+// Copyright 2016 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.
+
+// aux-build:rustdoc-nonreachable-impls.rs
+// build-aux-docs
+// ignore-cross-compile
+
+extern crate rustdoc_nonreachable_impls;
+
+// @has issue_31948_2/struct.Wobble.html
+// @has - '//*[@class="impl"]//code' 'Qux for'
+// @has - '//*[@class="impl"]//code' 'Bark for'
+// @has - '//*[@class="impl"]//code' 'Woof for'
+// @!has - '//*[@class="impl"]//code' 'Bar for'
+pub use rustdoc_nonreachable_impls::hidden::Wobble;
+
+// @has issue_31948_2/trait.Qux.html
+// FIXME(33025): has - '//code' 'for Foo'
+// @has - '//code' 'for Wobble'
+pub use rustdoc_nonreachable_impls::hidden::Qux;
+
+// @!has issue_31948_2/trait.Bar.html
+// @!has issue_31948_2/trait.Woof.html
+// @!has issue_31948_2/trait.Bark.html
diff --git a/src/test/rustdoc/inline_cross/issue-31948.rs b/src/test/rustdoc/inline_cross/issue-31948.rs
new file mode 100644
index 00000000000..073e8eb50fb
--- /dev/null
+++ b/src/test/rustdoc/inline_cross/issue-31948.rs
@@ -0,0 +1,39 @@
+// Copyright 2016 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.
+
+// aux-build:rustdoc-nonreachable-impls.rs
+// build-aux-docs
+// ignore-cross-compile
+
+extern crate rustdoc_nonreachable_impls;
+
+// @has issue_31948/struct.Foo.html
+// @has - '//*[@class="impl"]//code' 'Bark for'
+// @has - '//*[@class="impl"]//code' 'Woof for'
+// @!has - '//*[@class="impl"]//code' 'Bar for'
+// @!has - '//*[@class="impl"]//code' 'Qux for'
+pub use rustdoc_nonreachable_impls::Foo;
+
+// @has issue_31948/trait.Bark.html
+// @has - '//code' 'for Foo'
+// @!has - '//code' 'for Wibble'
+// @!has - '//code' 'for Wobble'
+pub use rustdoc_nonreachable_impls::Bark;
+
+// @has issue_31948/trait.Woof.html
+// @has - '//code' 'for Foo'
+// @!has - '//code' 'for Wibble'
+// @!has - '//code' 'for Wobble'
+pub use rustdoc_nonreachable_impls::Woof;
+
+// @!has issue_31948/trait.Bar.html
+// @!has issue_31948/trait.Qux.html
+// @!has issue_31948/struct.Wibble.html
+// @!has issue_31948/struct.Wobble.html