Make the rendering process less pass-aware

Instead of hardcoding knowledge about the strip-private pass into the
rendering process we represent (some) stripped items as `ItemEnum::StrippedItem`.

Rustdoc will, for example, generate redirect pages for public items
contained in private modules which have been re-exported to somewhere
externally reachable - this will now not only work for the `strip-private`
pass, but for other passes as well, such as the `strip-hidden` pass.
This commit is contained in:
mitaa 2016-03-31 18:15:54 +02:00
parent 4583dc9b13
commit b1543a1aac
8 changed files with 247 additions and 149 deletions

View File

@ -53,6 +53,7 @@ use std::env::current_dir;
use core::DocContext;
use doctree;
use visit_ast;
use html::item_type::ItemType;
/// A stable identifier to the particular version of JSON output.
/// Increment this when the `Crate` and related structures change.
@ -273,36 +274,40 @@ impl Item {
}
pub fn is_crate(&self) -> bool {
match self.inner {
ModuleItem(Module { items: _, is_crate: true }) => true,
_ => false
StrippedItem(box ModuleItem(Module { is_crate: true, ..})) |
ModuleItem(Module { is_crate: true, ..}) => true,
_ => false,
}
}
pub fn is_mod(&self) -> bool {
match self.inner { ModuleItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::Module
}
pub fn is_trait(&self) -> bool {
match self.inner { TraitItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::Trait
}
pub fn is_struct(&self) -> bool {
match self.inner { StructItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::Struct
}
pub fn is_enum(&self) -> bool {
match self.inner { EnumItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::Module
}
pub fn is_fn(&self) -> bool {
match self.inner { FunctionItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::Function
}
pub fn is_associated_type(&self) -> bool {
match self.inner { AssociatedTypeItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::AssociatedType
}
pub fn is_associated_const(&self) -> bool {
match self.inner { AssociatedConstItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::AssociatedConst
}
pub fn is_method(&self) -> bool {
match self.inner { MethodItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::Method
}
pub fn is_ty_method(&self) -> bool {
match self.inner { TyMethodItem(..) => true, _ => false }
ItemType::from_item(self) == ItemType::TyMethod
}
pub fn is_stripped(&self) -> bool {
match self.inner { StrippedItem(..) => true, _ => false }
}
pub fn stability_class(&self) -> String {
@ -352,6 +357,8 @@ pub enum ItemEnum {
AssociatedConstItem(Type, Option<String>),
AssociatedTypeItem(Vec<TyParamBound>, Option<Type>),
DefaultImplItem(DefaultImpl),
/// An item that has been stripped by a rustdoc pass
StrippedItem(Box<ItemEnum>),
}
#[derive(Clone, RustcEncodable, RustcDecodable, Debug)]

View File

@ -10,28 +10,50 @@
use clean::*;
pub enum FoldItem {
Retain(Item),
Strip(Item),
Erase,
}
impl FoldItem {
pub fn fold(self) -> Option<Item> {
match self {
FoldItem::Erase => None,
FoldItem::Retain(i) => Some(i),
FoldItem::Strip(item@ Item { inner: StrippedItem(..), .. } ) => Some(item),
FoldItem::Strip(mut i) => {
i.inner = StrippedItem(box i.inner);
Some(i)
}
}
}
}
pub trait DocFolder : Sized {
fn fold_item(&mut self, item: Item) -> Option<Item> {
self.fold_item_recur(item)
}
/// don't override!
fn fold_item_recur(&mut self, item: Item) -> Option<Item> {
let Item { attrs, name, source, visibility, def_id, inner, stability, deprecation } = item;
let inner = match inner {
fn fold_inner_recur(&mut self, inner: ItemEnum) -> ItemEnum {
match inner {
StrippedItem(..) => unreachable!(),
ModuleItem(i) => {
ModuleItem(self.fold_mod(i))
},
StructItem(mut i) => {
let num_fields = i.fields.len();
i.fields = i.fields.into_iter().filter_map(|x| self.fold_item(x)).collect();
i.fields_stripped |= num_fields != i.fields.len();
i.fields_stripped |= num_fields != i.fields.len() ||
i.fields.iter().any(|f| f.is_stripped());
StructItem(i)
},
ModuleItem(i) => {
ModuleItem(self.fold_mod(i))
},
EnumItem(mut i) => {
let num_variants = i.variants.len();
i.variants = i.variants.into_iter().filter_map(|x| self.fold_item(x)).collect();
i.variants_stripped |= num_variants != i.variants.len();
i.variants_stripped |= num_variants != i.variants.len() ||
i.variants.iter().any(|f| f.is_stripped());
EnumItem(i)
},
TraitItem(mut i) => {
@ -48,13 +70,24 @@ pub trait DocFolder : Sized {
StructVariant(mut j) => {
let num_fields = j.fields.len();
j.fields = j.fields.into_iter().filter_map(|x| self.fold_item(x)).collect();
j.fields_stripped |= num_fields != j.fields.len();
j.fields_stripped |= num_fields != j.fields.len() ||
j.fields.iter().any(|f| f.is_stripped());
VariantItem(Variant {kind: StructVariant(j), ..i2})
},
_ => VariantItem(i2)
}
},
x => x
}
}
/// don't override!
fn fold_item_recur(&mut self, item: Item) -> Option<Item> {
let Item { attrs, name, source, visibility, def_id, inner, stability, deprecation } = item;
let inner = match inner {
StrippedItem(box i) => StrippedItem(box self.fold_inner_recur(i)),
_ => self.fold_inner_recur(inner),
};
Some(Item { attrs: attrs, name: name, source: source, inner: inner,
@ -70,9 +103,8 @@ pub trait DocFolder : Sized {
}
fn fold_crate(&mut self, mut c: Crate) -> Crate {
c.module = c.module.and_then(|module| {
self.fold_item(module)
});
c.module = c.module.and_then(|module| self.fold_item(module));
c.external_traits = c.external_traits.into_iter().map(|(k, mut v)| {
v.items = v.items.into_iter().filter_map(|i| self.fold_item(i)).collect();
(k, v)

View File

@ -44,7 +44,12 @@ pub enum ItemType {
impl ItemType {
pub fn from_item(item: &clean::Item) -> ItemType {
match item.inner {
let inner = match item.inner {
clean::StrippedItem(box ref item) => item,
ref inner@_ => inner,
};
match *inner {
clean::ModuleItem(..) => ItemType::Module,
clean::ExternCrateItem(..) => ItemType::ExternCrate,
clean::ImportItem(..) => ItemType::Import,
@ -67,6 +72,7 @@ impl ItemType {
clean::AssociatedConstItem(..) => ItemType::AssociatedConst,
clean::AssociatedTypeItem(..) => ItemType::AssociatedType,
clean::DefaultImplItem(..) => ItemType::Impl,
clean::StrippedItem(..) => unreachable!(),
}
}

View File

@ -245,8 +245,7 @@ pub struct Cache {
parent_stack: Vec<DefId>,
parent_is_trait_impl: bool,
search_index: Vec<IndexItem>,
privmod: bool,
remove_priv: bool,
stripped_mod: bool,
access_levels: AccessLevels<DefId>,
deref_trait_did: Option<DefId>,
@ -492,8 +491,7 @@ pub fn run(mut krate: clean::Crate,
parent_is_trait_impl: false,
extern_locations: HashMap::new(),
primitive_locations: HashMap::new(),
remove_priv: cx.passes.contains("strip-private"),
privmod: false,
stripped_mod: false,
access_levels: access_levels,
orphan_methods: Vec::new(),
traits: mem::replace(&mut krate.external_traits, HashMap::new()),
@ -874,7 +872,6 @@ impl<'a> DocFolder for SourceCollector<'a> {
}
};
}
self.fold_item_recur(item)
}
}
@ -938,14 +935,15 @@ impl<'a> SourceCollector<'a> {
impl DocFolder for Cache {
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
// If this is a private module, we don't want it in the search index.
let orig_privmod = match item.inner {
clean::ModuleItem(..) => {
let prev = self.privmod;
self.privmod = prev || (self.remove_priv && item.visibility != Some(hir::Public));
// If this is a stripped module,
// we don't want it or its children in the search index.
let orig_stripped_mod = match item.inner {
clean::StrippedItem(box clean::ModuleItem(..)) => {
let prev = self.stripped_mod;
self.stripped_mod = true;
prev
}
_ => self.privmod,
_ => self.stripped_mod,
};
// Register any generics to their corresponding string. This is used
@ -983,6 +981,7 @@ impl DocFolder for Cache {
// Index this method for searching later on
if let Some(ref s) = item.name {
let (parent, is_method) = match item.inner {
clean::StrippedItem(..) => ((None, None), false),
clean::AssociatedConstItem(..) |
clean::TypedefItem(_, true) if self.parent_is_trait_impl => {
// skip associated items in trait impls
@ -1027,7 +1026,7 @@ impl DocFolder for Cache {
};
match parent {
(parent, Some(path)) if is_method || (!self.privmod && !hidden_field) => {
(parent, Some(path)) if is_method || (!self.stripped_mod && !hidden_field) => {
// Needed to determine `self` type.
let parent_basename = self.parent_stack.first().and_then(|parent| {
match self.paths.get(parent) {
@ -1035,6 +1034,7 @@ impl DocFolder for Cache {
_ => None
}
});
debug_assert!(!item.is_stripped());
// A crate has a module at its root, containing all items,
// which should not be indexed. The crate-item itself is
@ -1051,7 +1051,7 @@ impl DocFolder for Cache {
});
}
}
(Some(parent), None) if is_method || (!self.privmod && !hidden_field)=> {
(Some(parent), None) if is_method || (!self.stripped_mod && !hidden_field)=> {
if parent.is_local() {
// We have a parent, but we don't know where they're
// defined yet. Wait for later to index this item.
@ -1075,7 +1075,7 @@ impl DocFolder for Cache {
clean::StructItem(..) | clean::EnumItem(..) |
clean::TypedefItem(..) | clean::TraitItem(..) |
clean::FunctionItem(..) | clean::ModuleItem(..) |
clean::ForeignFunctionItem(..) if !self.privmod => {
clean::ForeignFunctionItem(..) if !self.stripped_mod => {
// Reexported items mean that the same id can show up twice
// in the rustdoc ast that we're looking at. We know,
// however, that a reexported item doesn't show up in the
@ -1093,7 +1093,7 @@ impl DocFolder for Cache {
}
// link variants to their parent enum because pages aren't emitted
// for each variant
clean::VariantItem(..) if !self.privmod => {
clean::VariantItem(..) if !self.stripped_mod => {
let mut stack = self.stack.clone();
stack.pop();
self.paths.insert(item.def_id, (stack, ItemType::Enum));
@ -1176,7 +1176,7 @@ impl DocFolder for Cache {
if pushed { self.stack.pop().unwrap(); }
if parent_pushed { self.parent_stack.pop().unwrap(); }
self.privmod = orig_privmod;
self.stripped_mod = orig_stripped_mod;
self.parent_is_trait_impl = orig_parent_is_trait_impl;
return ret;
}
@ -1233,15 +1233,12 @@ impl Context {
// render the crate documentation
let mut work = vec!((self, item));
loop {
match work.pop() {
Some((mut cx, item)) => cx.item(item, |cx, item| {
work.push((cx.clone(), item));
})?,
None => break,
}
}
while let Some((mut cx, item)) = work.pop() {
cx.item(item, |cx, item| {
work.push((cx.clone(), item))
})?
}
Ok(())
}
@ -1296,79 +1293,72 @@ impl Context {
layout::render(&mut writer, &cx.layout, &page,
&Sidebar{ cx: cx, item: it },
&Item{ cx: cx, item: it })?;
} else {
let mut url = repeat("../").take(cx.current.len())
.collect::<String>();
match cache().paths.get(&it.def_id) {
Some(&(ref names, _)) => {
for name in &names[..names.len() - 1] {
url.push_str(name);
url.push_str("/");
}
url.push_str(&item_path(it));
layout::redirect(&mut writer, &url)?;
if let Some(&(ref names, _)) = cache().paths.get(&it.def_id) {
for name in &names[..names.len() - 1] {
url.push_str(name);
url.push_str("/");
}
None => {}
url.push_str(&item_path(it));
layout::redirect(&mut writer, &url)?;
}
}
writer.flush()
}
// Private modules may survive the strip-private pass if they
// contain impls for public types. These modules can also
// Stripped modules survive the rustdoc passes (i.e. `strip-private`)
// if they contain impls for public types. These modules can also
// contain items such as publicly reexported structures.
//
// External crates will provide links to these structures, so
// these modules are recursed into, but not rendered normally (a
// flag on the context).
// these modules are recursed into, but not rendered normally
// (a flag on the context).
if !self.render_redirect_pages {
self.render_redirect_pages = self.ignore_private_item(&item);
self.render_redirect_pages = self.maybe_ignore_item(&item);
}
match item.inner {
if item.is_mod() {
// modules are special because they add a namespace. We also need to
// recurse into the items of the module as well.
clean::ModuleItem(..) => {
let name = item.name.as_ref().unwrap().to_string();
let mut item = Some(item);
self.recurse(name, |this| {
let item = item.take().unwrap();
let joint_dst = this.dst.join("index.html");
let dst = try_err!(File::create(&joint_dst), &joint_dst);
try_err!(render(dst, this, &item, false), &joint_dst);
let m = match item.inner {
clean::ModuleItem(m) => m,
_ => unreachable!()
};
// render sidebar-items.js used throughout this module
{
let items = this.build_sidebar_items(&m);
let js_dst = this.dst.join("sidebar-items.js");
let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst));
try_err!(write!(&mut js_out, "initSidebarItems({});",
as_json(&items)), &js_dst);
}
for item in m.items {
f(this,item);
}
Ok(())
})
}
// Things which don't have names (like impls) don't get special
// pages dedicated to them.
_ if item.name.is_some() => {
let joint_dst = self.dst.join(&item_path(&item));
let name = item.name.as_ref().unwrap().to_string();
let mut item = Some(item);
self.recurse(name, |this| {
let item = item.take().unwrap();
let joint_dst = this.dst.join("index.html");
let dst = try_err!(File::create(&joint_dst), &joint_dst);
try_err!(render(dst, self, &item, true), &joint_dst);
Ok(())
}
try_err!(render(dst, this, &item, false), &joint_dst);
_ => Ok(())
let m = match item.inner {
clean::StrippedItem(box clean::ModuleItem(m)) |
clean::ModuleItem(m) => m,
_ => unreachable!()
};
// render sidebar-items.js used throughout this module
{
let items = this.build_sidebar_items(&m);
let js_dst = this.dst.join("sidebar-items.js");
let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst));
try_err!(write!(&mut js_out, "initSidebarItems({});",
as_json(&items)), &js_dst);
}
for item in m.items {
f(this,item);
}
Ok(())
})
} else if item.name.is_some() {
let joint_dst = self.dst.join(&item_path(&item));
let dst = try_err!(File::create(&joint_dst), &joint_dst);
try_err!(render(dst, self, &item, true), &joint_dst);
Ok(())
} else {
Ok(())
}
}
@ -1376,7 +1366,7 @@ impl Context {
// BTreeMap instead of HashMap to get a sorted output
let mut map = BTreeMap::new();
for item in &m.items {
if self.ignore_private_item(item) { continue }
if self.maybe_ignore_item(item) { continue }
let short = shortty(item).to_static_str();
let myname = match item.name {
@ -1394,27 +1384,18 @@ impl Context {
return map;
}
fn ignore_private_item(&self, it: &clean::Item) -> bool {
fn maybe_ignore_item(&self, it: &clean::Item) -> bool {
match it.inner {
clean::StrippedItem(..) => true,
clean::ModuleItem(ref m) => {
(m.items.is_empty() &&
it.doc_value().is_none() &&
it.visibility != Some(hir::Public)) ||
(self.passes.contains("strip-private") && it.visibility != Some(hir::Public))
}
clean::PrimitiveItem(..) => it.visibility != Some(hir::Public),
it.doc_value().is_none() && m.items.is_empty() && it.visibility != Some(hir::Public)
},
_ => false,
}
}
}
impl<'a> Item<'a> {
fn ismodule(&self) -> bool {
match self.item.inner {
clean::ModuleItem(..) => true, _ => false
}
}
/// Generate a url appropriate for an `href` attribute back to the source of
/// this item.
///
@ -1495,6 +1476,7 @@ impl<'a> Item<'a> {
impl<'a> fmt::Display for Item<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_assert!(!self.item.is_stripped());
// Write the breadcrumb trail header for the top
write!(fmt, "\n<h1 class='fqn'><span class='in-band'>")?;
match self.item.inner {
@ -1516,7 +1498,7 @@ impl<'a> fmt::Display for Item<'a> {
};
if !is_primitive {
let cur = &self.cx.current;
let amt = if self.ismodule() { cur.len() - 1 } else { cur.len() };
let amt = if self.item.is_mod() { cur.len() - 1 } else { cur.len() };
for (i, component) in cur.iter().enumerate().take(amt) {
write!(fmt, "<a href='{}index.html'>{}</a>::<wbr>",
repeat("../").take(cur.len() - i - 1)
@ -1575,15 +1557,12 @@ impl<'a> fmt::Display for Item<'a> {
}
fn item_path(item: &clean::Item) -> String {
match item.inner {
clean::ModuleItem(..) => {
format!("{}/index.html", item.name.as_ref().unwrap())
}
_ => {
format!("{}.{}.html",
shortty(item).to_static_str(),
*item.name.as_ref().unwrap())
}
if item.is_mod() {
format!("{}/index.html", item.name.as_ref().unwrap())
} else {
format!("{}.{}.html",
shortty(item).to_static_str(),
*item.name.as_ref().unwrap())
}
}
@ -1626,7 +1605,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
document(w, cx, item)?;
let mut indices = (0..items.len()).filter(|i| {
!cx.ignore_private_item(&items[*i])
!cx.maybe_ignore_item(&items[*i])
}).collect::<Vec<usize>>();
// the order of item types in the listing
@ -1670,6 +1649,9 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
let mut curty = None;
for &idx in &indices {
let myitem = &items[idx];
if myitem.is_stripped() {
continue;
}
let myty = Some(shortty(myitem));
if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) {
@ -2146,6 +2128,7 @@ fn render_assoc_item(w: &mut fmt::Formatter,
where_clause = WhereClause(g))
}
match item.inner {
clean::StrippedItem(..) => Ok(()),
clean::TyMethodItem(ref m) => {
method(w, item, m.unsafety, hir::Constness::NotConst,
m.abi, &m.generics, &m.self_, &m.decl, link)
@ -2540,6 +2523,7 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
assoc_type(w, item, bounds, default.as_ref(), link)?;
write!(w, "</code></h4>\n")?;
}
clean::StrippedItem(..) => return Ok(()),
_ => panic!("can't make docs for trait item with name {:?}", item.name)
}

View File

@ -21,6 +21,7 @@ use clean::Item;
use plugins;
use fold;
use fold::DocFolder;
use fold::FoldItem::Strip;
/// Strip items marked `#[doc(hidden)]`
pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
@ -45,12 +46,10 @@ pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
..i
});
}
_ => {
return None;
}
clean::ModuleItem(..) => return Strip(i).fold(),
_ => return None,
}
}
self.fold_item_recur(i)
}
}
@ -125,6 +124,7 @@ struct Stripper<'a> {
impl<'a> fold::DocFolder for Stripper<'a> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
match i.inner {
clean::StrippedItem(..) => return Some(i),
// These items can all get re-exported
clean::TypedefItem(..) | clean::StaticItem(..) |
clean::StructItem(..) | clean::EnumItem(..) |
@ -153,8 +153,11 @@ impl<'a> fold::DocFolder for Stripper<'a> {
}
}
// handled below
clean::ModuleItem(..) => {}
clean::ModuleItem(..) => {
if i.def_id.is_local() && i.visibility != Some(hir::Public) {
return Strip(self.fold_item_recur(i).unwrap()).fold()
}
}
// trait impls for private items should be stripped
clean::ImplItem(clean::Impl{
@ -165,7 +168,7 @@ impl<'a> fold::DocFolder for Stripper<'a> {
}
}
// handled in the `strip-priv-imports` pass
clean::ExternCrateItem(..) | clean::ImportItem(_) => {}
clean::ExternCrateItem(..) | clean::ImportItem(..) => {}
clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
@ -187,7 +190,6 @@ impl<'a> fold::DocFolder for Stripper<'a> {
// implementations of traits are always public.
clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
// Struct variant fields have inherited visibility
clean::VariantItem(clean::Variant {
kind: clean::StructVariant(..)
@ -202,19 +204,17 @@ impl<'a> fold::DocFolder for Stripper<'a> {
self.fold_item_recur(i)
};
i.and_then(|i| {
match i.inner {
// emptied modules/impls have no need to exist
clean::ModuleItem(ref m)
if m.items.is_empty() &&
i.doc_value().is_none() => None,
clean::ImplItem(ref i) if i.items.is_empty() => None,
_ => {
self.retained.insert(i.def_id);
Some(i)
}
i.and_then(|i| { match i.inner {
// emptied modules/impls have no need to exist
clean::ModuleItem(ref m)
if m.items.is_empty() &&
i.doc_value().is_none() => None,
clean::ImplItem(ref i) if i.items.is_empty() => None,
_ => {
self.retained.insert(i.def_id);
Some(i)
}
})
}})
}
}

View File

@ -431,7 +431,7 @@ impl Collector {
// compiler failures are test failures
should_panic: testing::ShouldPanic::No,
},
testfn: testing::DynTestFn(Box::new(move|| {
testfn: testing::DynTestFn(box move|| {
runtest(&test,
&cratename,
cfgs,
@ -442,7 +442,7 @@ impl Collector {
as_test_harness,
compile_fail,
&opts);
}))
})
});
}

View File

@ -0,0 +1,21 @@
// 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 use private::Quz;
pub use hidden::Bar;
mod private {
pub struct Quz;
}
#[doc(hidden)]
pub mod hidden {
pub struct Bar;
}

View File

@ -0,0 +1,48 @@
// 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:reexp_stripped.rs
// build-aux-docs
// ignore-cross-compile
extern crate reexp_stripped;
pub trait Foo {}
// @has redirect/index.html
// @has - '//code' 'pub use reexp_stripped::Bar'
// @has - '//code/a' 'Bar'
// @has reexp_stripped/hidden/struct.Bar.html
// @has - '//p/a' '../../reexp_stripped/struct.Bar.html'
// @has 'reexp_stripped/struct.Bar.html'
#[doc(no_inline)]
pub use reexp_stripped::Bar;
impl Foo for Bar {}
// @has redirect/index.html
// @has - '//code' 'pub use reexp_stripped::Quz'
// @has - '//code/a' 'Quz'
// @has reexp_stripped/private/struct.Quz.html
// @has - '//p/a' '../../reexp_stripped/struct.Quz.html'
// @has 'reexp_stripped/struct.Quz.html'
#[doc(no_inline)]
pub use reexp_stripped::Quz;
impl Foo for Quz {}
mod private_no_inline {
pub struct Qux;
impl ::Foo for Qux {}
}
// @has redirect/index.html
// @has - '//code' 'pub use private_no_inline::Qux'
// @!has - '//code/a' 'Qux'
#[doc(no_inline)]
pub use private_no_inline::Qux;