Skip to content

Commit 701a651

Browse files
committed
Auto merge of rust-lang#159854 - camelid:selfty-head, r=notriddle,GuillaumeGomez
rustdoc: Only analyze head of self type when deciding impl inlining We only care about whether the self type is a generic or an item (inlined) in the current crate, so we don't actually need to compute the param_env, which is expensive when done to every external impl. This PR avoids computing the param_env until we actually decide to inline the impl. Moreover, it adds specialized cleaning logic for types that stops after the "head" (the top-level structure) is constructed, which is enough for the self type-based impl inlining analysis.
2 parents 26ae60a + 27c97d1 commit 701a651

3 files changed

Lines changed: 156 additions & 25 deletions

File tree

src/librustdoc/passes/collect_trait_impls.rs

Lines changed: 136 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
//! struct implements that trait.
44
55
use rustc_data_structures::fx::FxHashSet;
6+
use rustc_errors::FatalError;
67
use rustc_hir::attrs::{AttributeKind, DocAttribute};
7-
use rustc_hir::def_id::LOCAL_CRATE;
8+
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
89
use rustc_hir::{Attribute, find_attr};
9-
use rustc_middle::ty;
10+
use rustc_middle::ty::{self, Ty, TyCtxt};
11+
use rustc_span::kw;
12+
use tracing::debug;
1013

1114
use super::Pass;
1215
use crate::clean::*;
@@ -48,32 +51,33 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
4851
let _prof_timer = tcx.sess.prof.generic_activity("build_extern_trait_impls");
4952
for &cnum in tcx.crates(()) {
5053
for &impl_def_id in tcx.trait_impls_in_crate(cnum) {
51-
cx.with_param_env(impl_def_id, |cx| {
52-
let opt_trait_ref = tcx.impl_opt_trait_ref(impl_def_id);
53-
if opt_trait_ref.is_some_and(|trait_ref| {
54-
crate_items.contains(&ItemId::DefId(trait_ref.def_id()))
55-
|| Some(trait_ref.def_id()) == tcx.lang_items().deref_trait()
56-
|| tcx.is_doc_notable_trait(trait_ref.def_id())
57-
}) {
54+
let trait_ref = tcx.impl_trait_ref(impl_def_id);
55+
debug!("considering extern trait impl {trait_ref:?}");
56+
if crate_items.contains(&ItemId::DefId(trait_ref.def_id()))
57+
|| Some(trait_ref.def_id()) == tcx.lang_items().deref_trait()
58+
|| tcx.is_doc_notable_trait(trait_ref.def_id())
59+
{
60+
debug!("-> inlining due to trait");
61+
cx.with_param_env(impl_def_id, |cx| {
5862
inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
59-
} else {
60-
let self_ty =
61-
tcx.type_of(impl_def_id).instantiate_identity().skip_norm_wip();
62-
let self_ty = clean_middle_ty(
63-
ty::Binder::dummy(self_ty),
64-
cx,
65-
Some(impl_def_id),
66-
None,
67-
);
68-
if self_ty.is_full_generic()
69-
|| self_ty
70-
.def_id(&cx.cache)
71-
.is_some_and(|did| crate_items.contains(&ItemId::DefId(did)))
72-
{
63+
});
64+
} else {
65+
let self_ty = tcx.type_of(impl_def_id).instantiate_identity().skip_norm_wip();
66+
debug!(?self_ty);
67+
let self_ty_head = SelfTyHead::of(ty::Binder::dummy(self_ty), tcx, impl_def_id);
68+
debug!(?self_ty_head);
69+
let keep_impl = match self_ty_head {
70+
SelfTyHead::Generic => true,
71+
SelfTyHead::Item(def_id) => crate_items.contains(&ItemId::DefId(def_id)),
72+
SelfTyHead::Primitive | SelfTyHead::Other => false,
73+
};
74+
if keep_impl {
75+
debug!("-> inlining due to self ty");
76+
cx.with_param_env(impl_def_id, |cx| {
7377
inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
74-
}
78+
});
7579
}
76-
});
80+
}
7781
}
7882
}
7983
}
@@ -160,6 +164,113 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
160164
krate
161165
}
162166

167+
#[derive(Debug)]
168+
enum SelfTyHead {
169+
Generic,
170+
Primitive,
171+
Item(DefId),
172+
Other,
173+
}
174+
175+
impl SelfTyHead {
176+
/// Compute the "head" (top-level structure) of a type.
177+
///
178+
/// When deciding whether to inline an impl, one of the things we look at is
179+
/// whether the Self type (the `Foo` in `impl Foo` or `impl Tr for Foo`) is
180+
/// present in the current crate (usually itself through inlining). However,
181+
/// constructing a full [`clean::Type`](Type) is expensive and more than we need,
182+
/// so this function computes just enough information to determine if the type
183+
/// is in the current crate.
184+
// FIXME: once -Znormalize-docs works properly / becomes the default,
185+
// this should invoke normalization where needed (e.g. if the head is an Alias).
186+
// we'll need to fetch the param_env too.
187+
fn of<'tcx>(bound_ty: ty::Binder<'tcx, Ty<'tcx>>, tcx: TyCtxt<'tcx>, parent: DefId) -> Self {
188+
match *bound_ty.skip_binder().kind() {
189+
ty::Never
190+
| ty::Bool
191+
| ty::Char
192+
| ty::Int(..)
193+
| ty::Uint(..)
194+
| ty::Float(..)
195+
| ty::Str
196+
| ty::Slice(..)
197+
| ty::Array(..)
198+
| ty::RawPtr(..)
199+
| ty::FnDef(..)
200+
| ty::FnPtr(..)
201+
| ty::Tuple(_) => Self::Primitive,
202+
ty::Pat(ty, _) => Self::of(bound_ty.rebind(ty), tcx, parent),
203+
ty::Ref(_, ty, _) => match Self::of(bound_ty.rebind(ty), tcx, parent) {
204+
Self::Generic => Self::Primitive,
205+
head => head,
206+
},
207+
// FIXME(unsafe_binders): this should probably recurse through the unsafe binder,
208+
// but clean_middle_ty doesn't handle this correctly yet either
209+
ty::UnsafeBinder(_) => Self::Other,
210+
ty::Adt(def, _) => Self::Item(def.did()),
211+
ty::Foreign(did) => Self::Item(did),
212+
ty::Dynamic(obj, _) => {
213+
// HACK: pick the first `did` as the `did` of the trait object. Someone
214+
// might want to implement "native" support for marker-trait-only
215+
// trait objects.
216+
let mut dids = obj.auto_traits();
217+
let did = obj
218+
.principal_def_id()
219+
.or_else(|| dids.next())
220+
.unwrap_or_else(|| panic!("found trait object `{obj:?}` with no traits?"));
221+
Self::Item(did)
222+
}
223+
224+
ty::Alias(_, alias_ty @ ty::AliasTy { kind: ty::Projection { def_id }, .. }) => {
225+
debug_assert!(!tcx.is_impl_trait_in_trait(def_id));
226+
Self::of(bound_ty.rebind(alias_ty.self_ty()), tcx, parent)
227+
}
228+
229+
ty::Alias(_, alias_ty @ ty::AliasTy { kind: ty::Inherent { .. }, .. }) => {
230+
let alias_ty = bound_ty.rebind(alias_ty);
231+
Self::of(alias_ty.map_bound(|ty| ty.self_ty()), tcx, parent)
232+
}
233+
234+
ty::Alias(_, ty::AliasTy { kind: ty::Free { def_id }, args, .. }) => {
235+
if tcx.features().checked_type_aliases() {
236+
// Free type alias `data` represents the `type X` in `type X = Y`. If we need `Y`,
237+
// we need to use `type_of`.
238+
Self::Item(def_id)
239+
} else {
240+
let ty = tcx.type_of(def_id).instantiate(tcx, args).skip_norm_wip();
241+
Self::of(bound_ty.rebind(ty), tcx, parent)
242+
}
243+
}
244+
245+
ty::Param(ref p) => {
246+
// FIXME: there's a slight behavior difference from clean_middle_ty here
247+
// since here we represent impl traits as Generic not ImplTrait.
248+
// probably doesn't matter for collect trait impls since impl trait
249+
// can't be a self ty
250+
if p.name == kw::SelfUpper { Self::Other } else { Self::Generic }
251+
}
252+
253+
ty::Bound(_, ref ty) => match ty.kind {
254+
ty::BoundTyKind::Param(_) => Self::Generic,
255+
ty::BoundTyKind::Anon => panic!("unexpected anonymous bound type variable"),
256+
},
257+
258+
ty::Alias(_, ty::AliasTy { kind: ty::Opaque { .. }, .. }) => {
259+
panic!("{bound_ty} should not appear as impl self ty")
260+
}
261+
262+
ty::Closure(..)
263+
| ty::CoroutineClosure(..)
264+
| ty::Coroutine(..)
265+
| ty::Placeholder(..)
266+
| ty::CoroutineWitness(..)
267+
| ty::Infer(..) => panic!("unexpected impl self ty {bound_ty}"),
268+
269+
ty::Error(_) => FatalError.raise(),
270+
}
271+
}
272+
}
273+
163274
struct SyntheticImplCollector<'a, 'tcx> {
164275
cx: &'a mut DocContext<'tcx>,
165276
impls: Vec<Item>,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![crate_name = "foo"]
2+
3+
pub struct Struct;
4+
5+
pub trait Tr1 {}
6+
pub trait Tr2 {
7+
type Assoc;
8+
}
9+
impl Tr2 for () {
10+
type Assoc = Struct;
11+
}
12+
13+
impl Tr1 for <() as Tr2>::Assoc {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//@ aux-build:impl-for-projection.rs
2+
3+
extern crate foo;
4+
5+
// FIXME: because rustdoc doesn't normalize types, it doesn't inline the impl in foo
6+
// that is for a projection that resolves to `Struct`
7+
pub use foo::Struct;

0 commit comments

Comments
 (0)