Skip to main content

rustdoc/passes/
collect_trait_impls.rs

1//! Collects trait impls for each item in the crate. For example, if a crate
2//! defines a struct that implements a trait, this pass will note that the
3//! struct implements that trait.
4
5use rustc_data_structures::fx::FxHashSet;
6use rustc_errors::FatalError;
7use rustc_hir::attrs::{AttributeKind, DocAttribute};
8use rustc_hir::def_id::{DefId, LOCAL_CRATE};
9use rustc_hir::{Attribute, find_attr};
10use rustc_middle::ty::{self, Ty, TyCtxt};
11use rustc_span::kw;
12use tracing::debug;
13
14use super::Pass;
15use crate::clean::*;
16use crate::core::DocContext;
17use crate::formats::cache::Cache;
18use crate::visit::DocVisitor;
19
20pub(crate) const COLLECT_TRAIT_IMPLS: Pass = Pass {
21    name: "collect-trait-impls",
22    run: Some(collect_trait_impls),
23    description: "retrieves trait impls for items in the crate",
24};
25
26pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> Crate {
27    let tcx = cx.tcx;
28    // We need to check if there are errors before running this pass because it would crash when
29    // we try to get auto and blanket implementations.
30    if tcx.dcx().has_errors().is_some() {
31        return krate;
32    }
33
34    let synth_impls = cx.sess().time("collect_synthetic_impls", || {
35        let mut synth = SyntheticImplCollector { cx, impls: Vec::new() };
36        synth.visit_crate(&krate);
37        synth.impls
38    });
39
40    let crate_items = {
41        let mut coll = ItemAndAliasCollector::new(&cx.cache);
42        cx.sess().time("collect_items_for_trait_impls", || coll.visit_crate(&krate));
43        coll.items
44    };
45
46    let mut new_items_external = Vec::new();
47    let mut new_items_local = Vec::new();
48
49    // External trait impls.
50    {
51        let _prof_timer = tcx.sess.prof.generic_activity("build_extern_trait_impls");
52        for &cnum in tcx.crates(()) {
53            for &impl_def_id in tcx.trait_impls_in_crate(cnum) {
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| {
62                        inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
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| {
77                            inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
78                        });
79                    }
80                }
81            }
82        }
83    }
84
85    // Local trait impls.
86    {
87        let _prof_timer = tcx.sess.prof.generic_activity("build_local_trait_impls");
88        let mut attr_buf = Vec::new();
89        for &impl_def_id in tcx.trait_impls_in_crate(LOCAL_CRATE) {
90            let mut parent = Some(tcx.parent(impl_def_id));
91            while let Some(did) = parent {
92                attr_buf.extend(find_attr!(tcx, did, Doc(d) if !d.cfg.is_empty() => {
93                    let mut new_attr = DocAttribute::default();
94                    new_attr.cfg = d.cfg.clone();
95                    Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr)))
96                }));
97                parent = tcx.opt_parent(did);
98            }
99            cx.with_param_env(impl_def_id, |cx| {
100                inline::build_impl(cx, impl_def_id, Some((&attr_buf, None)), &mut new_items_local);
101            });
102            attr_buf.clear();
103        }
104    }
105
106    tcx.sess.prof.generic_activity("build_primitive_trait_impls").run(|| {
107        for (prim, did) in PrimitiveType::primitive_locations(tcx) {
108            // Do not calculate blanket impl list for docs that are not going to be rendered.
109            // While the `impl` blocks themselves are only in `libcore`, the module with `doc`
110            // attached is directly included in `libstd` as well.
111            if did.is_local() {
112                for impl_def_id in prim.impls(tcx) {
113                    // Try to inline primitive impls from other crates.
114                    if !impl_def_id.is_local() {
115                        cx.with_param_env(impl_def_id, |cx| {
116                            inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
117                        });
118                    }
119                }
120
121                // HACK: this is all one massive hack that is very hard to get rid of (see comment below)
122                for def_id in prim.impls(tcx).filter(|&def_id| {
123                    // Avoid including impl blocks with filled-in generics.
124                    // https://github.com/rust-lang/rust/issues/94937
125                    //
126                    // FIXME(notriddle): https://github.com/rust-lang/rust/issues/97129
127                    //
128                    // This tactic of using inherent impl blocks for getting
129                    // auto traits and blanket impls is a hack. What we really
130                    // want is to check if `[T]` impls `Send`, which has
131                    // nothing to do with the inherent impl.
132                    //
133                    // Rustdoc currently uses these `impl` block as a source of
134                    // the `Ty`, as well as the `ParamEnv`, `GenericArgsRef`, and
135                    // `Generics`. To avoid relying on the `impl` block, these
136                    // things would need to be created from wholecloth, in a
137                    // form that is valid for use in type inference.
138                    let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
139                    match ty.kind() {
140                        ty::Slice(ty) | ty::Ref(_, ty, _) | ty::RawPtr(ty, _) => {
141                            matches!(ty.kind(), ty::Param(..))
142                        }
143                        ty::Tuple(tys) => tys.iter().all(|ty| matches!(ty.kind(), ty::Param(..))),
144                        _ => true,
145                    }
146                }) {
147                    let impls = synthesize_auto_trait_and_blanket_impls(cx, def_id);
148                    new_items_external.extend(impls.filter(|i| cx.inlined.insert(i.item_id)));
149                }
150            }
151        }
152    });
153
154    if let ModuleItem(Module { items, .. }) = &mut krate.module.inner.kind {
155        items.extend(synth_impls);
156        items.extend(new_items_external);
157        items.extend(new_items_local);
158    } else {
159        panic!("collect-trait-impls can't run");
160    };
161
162    krate.external_traits.extend(cx.external_traits.drain(..));
163
164    krate
165}
166
167#[derive(Debug)]
168enum SelfTyHead {
169    Generic,
170    Primitive,
171    Item(DefId),
172    Other,
173}
174
175impl 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
274struct SyntheticImplCollector<'a, 'tcx> {
275    cx: &'a mut DocContext<'tcx>,
276    impls: Vec<Item>,
277}
278
279impl DocVisitor<'_> for SyntheticImplCollector<'_, '_> {
280    fn visit_item(&mut self, i: &Item) {
281        if i.is_struct() || i.is_enum() || i.is_union() {
282            let item_def_id = i.item_id.expect_def_id();
283            // FIXME(eddyb) is this `doc(hidden)` check needed?
284            // FIXME(camelid) should we skip the `doc(hidden)` check if --document-hidden-items is passed?
285            if (self.cx.document_private()
286                || self.cx.cache.effective_visibilities.is_reachable(self.cx.tcx, item_def_id))
287                && !self.cx.tcx.is_doc_hidden(item_def_id)
288            {
289                self.impls.extend(synthesize_auto_trait_and_blanket_impls(self.cx, item_def_id));
290            }
291        }
292
293        self.visit_item_recur(i)
294    }
295}
296
297struct ItemAndAliasCollector<'cache> {
298    items: FxHashSet<ItemId>,
299    cache: &'cache Cache,
300}
301
302impl<'cache> ItemAndAliasCollector<'cache> {
303    fn new(cache: &'cache Cache) -> Self {
304        ItemAndAliasCollector { items: FxHashSet::default(), cache }
305    }
306}
307
308impl DocVisitor<'_> for ItemAndAliasCollector<'_> {
309    fn visit_item(&mut self, i: &Item) {
310        self.items.insert(i.item_id);
311
312        if let TypeAliasItem(alias) = &i.inner.kind
313            && let Some(did) = alias.type_.def_id(self.cache)
314        {
315            self.items.insert(ItemId::DefId(did));
316        }
317
318        self.visit_item_recur(i)
319    }
320}