Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rustdoc] Generic impls #52585

Merged
merged 17 commits into from
Jul 28, 2018
Merged
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 144 additions & 38 deletions src/librustdoc/clean/auto_trait.rs
Original file line number Diff line number Diff line change
@@ -8,10 +8,15 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use rustc::traits::auto_trait as auto;
use rustc::ty::TypeFoldable;
use rustc::hir;
use rustc::traits::{self, auto_trait as auto};
use rustc::ty::{self, ToPredicate, TypeFoldable};
use rustc::ty::subst::Subst;
use rustc::infer::InferOk;
use std::fmt::Debug;
use syntax_pos::DUMMY_SP;

use core::DocAccessLevels;

use super::*;

@@ -75,6 +80,33 @@ impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> {
self.get_auto_trait_impls(did, &def_ctor, Some(name))
}

fn get_real_ty<F>(&self, def_id: DefId, def_ctor: &F, real_name: &Option<Ident>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this function signature should follow rustfmt block indent style (especially since it looks like the function below already follows that

generics: &ty::Generics) -> hir::Ty
where F: Fn(DefId) -> Def {
let path = get_path_for_type(self.cx.tcx, def_id, def_ctor);
let mut segments = path.segments.into_vec();
let last = segments.pop().unwrap();

segments.push(hir::PathSegment::new(
real_name.unwrap_or(last.ident),
self.generics_to_path_params(generics.clone()),
false,
));

let new_path = hir::Path {
span: path.span,
def: path.def,
segments: HirVec::from_vec(segments),
};

hir::Ty {
id: ast::DUMMY_NODE_ID,
node: hir::TyKind::Path(hir::QPath::Resolved(None, P(new_path))),
span: DUMMY_SP,
hir_id: hir::DUMMY_HIR_ID,
}
}

pub fn get_auto_trait_impls<F>(
&self,
def_id: DefId,
@@ -99,29 +131,126 @@ impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> {
let tcx = self.cx.tcx;
let generics = self.cx.tcx.generics_of(def_id);

let ty = self.cx.tcx.type_of(def_id);
let mut traits = Vec::new();
if self.cx.crate_name != Some("core".to_string()) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this filter out libcore specifically?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It panics getting libcore information apparently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, at some point since the original implementation, it doesn't panic if you take out this condition any more. I just tried it locally and it made everything just fine.

self.cx.access_levels.borrow().is_doc_reachable(def_id) {
if let ty::TyAdt(_adt, _) = ty.sty {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we only doing this for adts? wouldn't it be practical to also see impls on tuples, array, slices, ...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! But the answer lies on @eddyb side here. Or at least I recall him asking me to only perform this on Adt items. Maybe I got it wrong?

let real_name = name.clone().map(|name| Ident::from_str(&name));
let param_env = self.cx.tcx.param_env(def_id);
for &trait_def_id in self.cx.all_traits.iter() {
if !self.cx.access_levels.borrow().is_doc_reachable(trait_def_id) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to prefilter all_traits during creation by their reachability instead of doing this over and over again for every single type in a crate

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I discussed a bit earlier with @QuietMisdreavus so I'll do it as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I can't, it depends on the context so we have to make this check in here (access_levels is updated through the code).

self.cx.generated_synthetics
.borrow_mut()
.get(&(def_id, trait_def_id))
.is_some() {
continue
}
self.cx.tcx.for_each_relevant_impl(trait_def_id, ty, |impl_def_id| {
self.cx.tcx.infer_ctxt().enter(|infcx| {
let generics = infcx.tcx.generics_of(impl_def_id);
let trait_ref = infcx.tcx.impl_trait_ref(impl_def_id).unwrap();

if !match infcx.tcx.type_of(impl_def_id).sty {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

match infcx.tcx.type_of(impl_def_id).sty {
    ::rustc::ty::TyParam(_) => {},
    _ => return,
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

::rustc::ty::TypeVariants::TyParam(_) => true,
_ => false,
} {
return
}

let substs = infcx.fresh_substs_for_item(DUMMY_SP, def_id);
let ty2 = ty.subst(infcx.tcx, substs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need both ty and ty2, so just name it ty to reduce what the reader needs to keep in the mental cache

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do need ty afterwards:

for_: ty.clean(self.cx),

let param_env = param_env.subst(infcx.tcx, substs);

let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
let trait_ref = trait_ref.subst(infcx.tcx, impl_substs);

// Require the type the impl is implemented on to match
// our type, and ignore the impl if there was a mismatch.
let cause = traits::ObligationCause::dummy();
let eq_result = infcx.at(&cause, param_env)
.eq(trait_ref.self_ty(), ty2);
if let Ok(InferOk { value: (), obligations }) = eq_result {
// FIXME(eddyb) ignoring `obligations` might cause false positives.
drop(obligations);

let may_apply = infcx.predicate_may_hold(&traits::Obligation::new(
cause.clone(),
param_env,
trait_ref.to_predicate(),
));
if !may_apply {
return
}
self.cx.generated_synthetics.borrow_mut()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't we do this way earlier (like where we're early aborting usually)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No because a trait can have multiple generic impls and we only know at this point that the current impl matches.

.insert((def_id, trait_def_id));
let trait_ = hir::TraitRef {
path: get_path_for_type(infcx.tcx,
trait_def_id,
hir::def::Def::Trait),
ref_id: ast::DUMMY_NODE_ID,
};
let provided_trait_methods =
infcx.tcx.provided_trait_methods(trait_def_id)
.into_iter()
.map(|meth| meth.ident.to_string())
.collect();

let ty = self.get_real_ty(def_id, def_ctor, &real_name, generics);

traits.push(Item {
source: Span::empty(),
name: None,
attrs: Default::default(),
visibility: None,
def_id: self.next_def_id(impl_def_id.krate),
stability: None,
deprecation: None,
inner: ImplItem(Impl {
unsafety: hir::Unsafety::Normal,
generics: (generics,
&tcx.predicates_of(impl_def_id)).clean(self.cx),
provided_trait_methods,
trait_: Some(trait_.clean(self.cx)),
for_: ty.clean(self.cx),
items: infcx.tcx.associated_items(impl_def_id)
.collect::<Vec<_>>()
.clean(self.cx),
polarity: None,
synthetic: true,
}),
});
debug!("{:?} => {}", trait_ref, may_apply);
}
});
});
}
}
}

debug!(
"get_auto_trait_impls(def_id={:?}, def_ctor=..., generics={:?}",
def_id, generics
);
let auto_traits: Vec<_> = self.cx
.send_trait
.and_then(|send_trait| {
self.get_auto_trait_impl_for(
def_id,
name.clone(),
generics.clone(),
def_ctor,
send_trait,
)
})
.into_iter()
let auto_traits: Vec<_> =
self.cx.send_trait
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's some funky formatting here. revert this block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to double-check: i think @oli-obk wanted you to revert this section?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn I thought I did! >< Might have been lost in my multiple tests...

.and_then(|send_trait| {
self.get_auto_trait_impl_for(
def_id,
name.clone(),
generics.clone(),
def_ctor,
send_trait,
)
}).into_iter()
.chain(self.get_auto_trait_impl_for(
def_id,
name.clone(),
generics.clone(),
def_ctor,
tcx.require_lang_item(lang_items::SyncTraitLangItem),
).into_iter())
.chain(traits.into_iter())
.collect();

debug!(
@@ -196,31 +325,8 @@ impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> {
}
_ => unreachable!(),
};

let path = get_path_for_type(self.cx.tcx, def_id, def_ctor);
let mut segments = path.segments.into_vec();
let last = segments.pop().unwrap();

let real_name = name.map(|name| Ident::from_str(&name));

segments.push(hir::PathSegment::new(
real_name.unwrap_or(last.ident),
self.generics_to_path_params(generics.clone()),
false,
));

let new_path = hir::Path {
span: path.span,
def: path.def,
segments: HirVec::from_vec(segments),
};

let ty = hir::Ty {
id: ast::DUMMY_NODE_ID,
node: hir::TyKind::Path(hir::QPath::Resolved(None, P(new_path))),
span: DUMMY_SP,
hir_id: hir::DUMMY_HIR_ID,
};
let ty = self.get_real_ty(def_id, def_ctor, &real_name, &generics);

return Some(Item {
source: Span::empty(),
1 change: 0 additions & 1 deletion src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
@@ -275,7 +275,6 @@ pub fn build_impls(cx: &DocContext, did: DefId, auto_traits: bool) -> Vec<clean:
if auto_traits {
let auto_impls = get_auto_traits_with_def_id(cx, did);
let mut renderinfo = cx.renderinfo.borrow_mut();

let new_impls: Vec<clean::Item> = auto_impls.into_iter()
.filter(|i| renderinfo.inlined.insert(i.def_id)).collect();

1 change: 0 additions & 1 deletion src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
@@ -1551,7 +1551,6 @@ impl GenericBound {
}

fn get_trait_type(&self) -> Option<Type> {

if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, _) = *self {
return Some(trait_.clone());
}
4 changes: 3 additions & 1 deletion src/librustdoc/core.rs
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
use rustc_lint;
use rustc_driver::{self, driver, target_features, abort_on_err};
use rustc::session::{self, config};
use rustc::hir::def_id::{DefId, CrateNum};
use rustc::hir::def_id::{DefId, CrateNum, LOCAL_CRATE};
use rustc::hir::def::Def;
use rustc::middle::cstore::CrateStore;
use rustc::middle::privacy::AccessLevels;
@@ -84,6 +84,7 @@ pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a> {
/// Maps (type_id, trait_id) -> auto trait impl
pub generated_synthetics: RefCell<FxHashSet<(DefId, DefId)>>,
pub current_item_name: RefCell<Option<Name>>,
pub all_traits: Vec<DefId>,
}

impl<'a, 'tcx, 'rcx> DocContext<'a, 'tcx, 'rcx> {
@@ -385,6 +386,7 @@ pub fn run_core(search_paths: SearchPaths,
all_fake_def_ids: RefCell::new(FxHashSet()),
generated_synthetics: RefCell::new(FxHashSet()),
current_item_name: RefCell::new(None),
all_traits: tcx.all_traits(LOCAL_CRATE).to_vec(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can filter this down to just the ones with blanket impls? That may require some overhead here but would also cut down on the traits we iterate over when gathering impls.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to understand you. I do this call here because it otherwise would need to be done on every item (and also because @eddyb thinks it's better this way, which I agree).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, i understand why you wanted to cache the traits. I'm asking if it's possible to store less traits in this, by filtering out traits that don't have a blanket/generic impl.

};
debug!("crate: {:?}", tcx.hir.krate());

36 changes: 19 additions & 17 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
@@ -3590,18 +3590,19 @@ fn render_assoc_items(w: &mut fmt::Formatter,
if !non_trait.is_empty() {
let render_mode = match what {
AssocItemRender::All => {
write!(w, "
<h2 id='methods' class='small-section-header'>
Methods<a href='#methods' class='anchor'></a>
</h2>
write!(w, "\
<h2 id='methods' class='small-section-header'>\
Methods<a href='#methods' class='anchor'></a>\
</h2>\
")?;
RenderMode::Normal
}
AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
write!(w, "
<h2 id='deref-methods' class='small-section-header'>
Methods from {}&lt;Target = {}&gt;<a href='#deref-methods' class='anchor'></a>
</h2>
write!(w, "\
<h2 id='deref-methods' class='small-section-header'>\
Methods from {}&lt;Target = {}&gt;\
<a href='#deref-methods' class='anchor'></a>\
</h2>\
", trait_, type_)?;
RenderMode::ForDeref { mut_: deref_mut_ }
}
@@ -3639,19 +3640,20 @@ fn render_assoc_items(w: &mut fmt::Formatter,

let impls = format!("{}", RendererStruct(cx, concrete, containing_item));
if !impls.is_empty() {
write!(w, "
<h2 id='implementations' class='small-section-header'>
Trait Implementations<a href='#implementations' class='anchor'></a>
</h2>
write!(w, "\
<h2 id='implementations' class='small-section-header'>\
Trait Implementations<a href='#implementations' class='anchor'></a>\
</h2>\
<div id='implementations-list'>{}</div>", impls)?;
}

if !synthetic.is_empty() {
write!(w, "
<h2 id='synthetic-implementations' class='small-section-header'>
Auto Trait Implementations<a href='#synthetic-implementations' class='anchor'></a>
</h2>
<div id='synthetic-implementations-list'>
write!(w, "\
<h2 id='synthetic-implementations' class='small-section-header'>\
Auto Trait Implementations\
<a href='#synthetic-implementations' class='anchor'></a>\
</h2>\
<div id='synthetic-implementations-list'>\
")?;
render_impls(cx, w, &synthetic, containing_item)?;
write!(w, "</div>")?;
25 changes: 25 additions & 0 deletions src/test/rustdoc/generic-impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2018 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.

#![crate_name = "foo"]

use std::fmt;

// @!has foo/struct.Bar.html '//h3[@id="impl-ToString"]//code' 'impl<T> ToString for Bar'
pub struct Bar;

// @has foo/struct.Foo.html '//h3[@id="impl-ToString"]//code' 'impl<T> ToString for Foo'
pub struct Foo;

impl fmt::Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Foo")
}
}
1 change: 0 additions & 1 deletion src/test/rustdoc/manual_impl.rs
Original file line number Diff line number Diff line change
@@ -56,7 +56,6 @@ impl T for S1 {
// @!has - '//*[@class="docblock"]' 'Docs associated with the trait a_method definition.'
// @!has - '//*[@class="docblock"]' 'Docs associated with the trait c_method definition.'
// @has - '//*[@class="docblock"]' 'Docs associated with the trait b_method definition.'
// @!has - '//*[@class="docblock"]' 'Read more'
pub struct S2(usize);

/// Docs associated with the S2 trait implementation.
8 changes: 4 additions & 4 deletions src/test/rustdoc/sidebar-items.rs
Original file line number Diff line number Diff line change
@@ -31,11 +31,11 @@ pub trait Foo {
// @has - '//*[@class="sidebar-title"][@href="#fields"]' 'Fields'
// @has - '//*[@class="sidebar-links"]/a[@href="#structfield.f"]' 'f'
// @has - '//*[@class="sidebar-links"]/a[@href="#structfield.u"]' 'u'
// @!has - '//*[@class="sidebar-links"]/a' 'w'
// @!has - '//*[@class="sidebar-links"]/a' 'waza'
pub struct Bar {
pub f: u32,
pub u: u32,
w: u32,
waza: u32,
}

// @has foo/enum.En.html
@@ -51,9 +51,9 @@ pub enum En {
// @has - '//*[@class="sidebar-title"][@href="#fields"]' 'Fields'
// @has - '//*[@class="sidebar-links"]/a[@href="#structfield.f1"]' 'f1'
// @has - '//*[@class="sidebar-links"]/a[@href="#structfield.f2"]' 'f2'
// @!has - '//*[@class="sidebar-links"]/a' 'w'
// @!has - '//*[@class="sidebar-links"]/a' 'waza'
pub union MyUnion {
pub f1: u32,
pub f2: f32,
w: u32,
waza: u32,
}
2 changes: 1 addition & 1 deletion src/test/rustdoc/synthetic_auto/basic.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
// @has - '//code' 'impl<T> Send for Foo<T> where T: Send'
// @has - '//code' 'impl<T> Sync for Foo<T> where T: Sync'
// @count - '//*[@id="implementations-list"]/*[@class="impl"]' 0
// @count - '//*[@id="synthetic-implementations-list"]/*[@class="impl"]' 2
// @count - '//*[@id="synthetic-implementations-list"]/*[@class="impl"]' 9
pub struct Foo<T> {
field: T,
}
Loading