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_hir::attrs::{AttributeKind, DocAttribute};
7use rustc_hir::def_id::LOCAL_CRATE;
8use rustc_hir::{Attribute, find_attr};
9use rustc_middle::ty;
10
11use super::Pass;
12use crate::clean::*;
13use crate::core::DocContext;
14use crate::formats::cache::Cache;
15use crate::visit::DocVisitor;
16
17pub(crate) const COLLECT_TRAIT_IMPLS: Pass = Pass {
18    name: "collect-trait-impls",
19    run: Some(collect_trait_impls),
20    description: "retrieves trait impls for items in the crate",
21};
22
23pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> Crate {
24    let tcx = cx.tcx;
25    // We need to check if there are errors before running this pass because it would crash when
26    // we try to get auto and blanket implementations.
27    if tcx.dcx().has_errors().is_some() {
28        return krate;
29    }
30
31    let synth_impls = cx.sess().time("collect_synthetic_impls", || {
32        let mut synth = SyntheticImplCollector { cx, impls: Vec::new() };
33        synth.visit_crate(&krate);
34        synth.impls
35    });
36
37    let local_crate = ExternalCrate { crate_num: LOCAL_CRATE };
38    let prims: FxHashSet<PrimitiveType> = local_crate.primitives(tcx).map(|(_, p)| p).collect();
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                cx.with_param_env(impl_def_id, |cx| {
55                    let opt_trait_ref = tcx.impl_opt_trait_ref(impl_def_id);
56                    let self_ty = tcx.type_of(impl_def_id).instantiate_identity().skip_norm_wip();
57                    let self_ty =
58                        clean_middle_ty(ty::Binder::dummy(self_ty), cx, Some(impl_def_id), None);
59                    if self_ty.is_full_generic()
60                        || self_ty
61                            .primitive_type()
62                            .is_some_and(|primitive| prims.contains(&primitive))
63                        || self_ty
64                            .def_id(&cx.cache)
65                            .is_some_and(|did| crate_items.contains(&ItemId::DefId(did)))
66                        || opt_trait_ref.is_some_and(|trait_ref| {
67                            crate_items.contains(&ItemId::DefId(trait_ref.def_id()))
68                                || Some(trait_ref.def_id()) == tcx.lang_items().deref_trait()
69                                || tcx.is_doc_notable_trait(trait_ref.def_id())
70                        })
71                    {
72                        inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
73                    }
74                });
75            }
76        }
77    }
78
79    // Local trait impls.
80    {
81        let _prof_timer = tcx.sess.prof.generic_activity("build_local_trait_impls");
82        let mut attr_buf = Vec::new();
83        for &impl_def_id in tcx.trait_impls_in_crate(LOCAL_CRATE) {
84            let mut parent = Some(tcx.parent(impl_def_id));
85            while let Some(did) = parent {
86                attr_buf.extend(find_attr!(tcx, did, Doc(d) if !d.cfg.is_empty() => {
87                    let mut new_attr = DocAttribute::default();
88                    new_attr.cfg = d.cfg.clone();
89                    Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr)))
90                }));
91                parent = tcx.opt_parent(did);
92            }
93            cx.with_param_env(impl_def_id, |cx| {
94                inline::build_impl(cx, impl_def_id, Some((&attr_buf, None)), &mut new_items_local);
95            });
96            attr_buf.clear();
97        }
98    }
99
100    tcx.sess.prof.generic_activity("build_primitive_trait_impls").run(|| {
101        for def_id in PrimitiveType::all_impls(tcx) {
102            // Try to inline primitive impls from other crates.
103            if !def_id.is_local() {
104                cx.with_param_env(def_id, |cx| {
105                    inline::build_impl(cx, def_id, None, &mut new_items_external);
106                });
107            }
108        }
109        for (prim, did) in PrimitiveType::primitive_locations(tcx) {
110            // Do not calculate blanket impl list for docs that are not going to be rendered.
111            // While the `impl` blocks themselves are only in `libcore`, the module with `doc`
112            // attached is directly included in `libstd` as well.
113            if did.is_local() {
114                for def_id in prim.impls(tcx).filter(|&def_id| {
115                    // Avoid including impl blocks with filled-in generics.
116                    // https://github.com/rust-lang/rust/issues/94937
117                    //
118                    // FIXME(notriddle): https://github.com/rust-lang/rust/issues/97129
119                    //
120                    // This tactic of using inherent impl blocks for getting
121                    // auto traits and blanket impls is a hack. What we really
122                    // want is to check if `[T]` impls `Send`, which has
123                    // nothing to do with the inherent impl.
124                    //
125                    // Rustdoc currently uses these `impl` block as a source of
126                    // the `Ty`, as well as the `ParamEnv`, `GenericArgsRef`, and
127                    // `Generics`. To avoid relying on the `impl` block, these
128                    // things would need to be created from wholecloth, in a
129                    // form that is valid for use in type inference.
130                    let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
131                    match ty.kind() {
132                        ty::Slice(ty) | ty::Ref(_, ty, _) | ty::RawPtr(ty, _) => {
133                            matches!(ty.kind(), ty::Param(..))
134                        }
135                        ty::Tuple(tys) => tys.iter().all(|ty| matches!(ty.kind(), ty::Param(..))),
136                        _ => true,
137                    }
138                }) {
139                    let impls = synthesize_auto_trait_and_blanket_impls(cx, def_id);
140                    new_items_external.extend(impls.filter(|i| cx.inlined.insert(i.item_id)));
141                }
142            }
143        }
144    });
145
146    if let ModuleItem(Module { items, .. }) = &mut krate.module.inner.kind {
147        items.extend(synth_impls);
148        items.extend(new_items_external);
149        items.extend(new_items_local);
150    } else {
151        panic!("collect-trait-impls can't run");
152    };
153
154    krate.external_traits.extend(cx.external_traits.drain(..));
155
156    krate
157}
158
159struct SyntheticImplCollector<'a, 'tcx> {
160    cx: &'a mut DocContext<'tcx>,
161    impls: Vec<Item>,
162}
163
164impl DocVisitor<'_> for SyntheticImplCollector<'_, '_> {
165    fn visit_item(&mut self, i: &Item) {
166        if i.is_struct() || i.is_enum() || i.is_union() {
167            // FIXME(eddyb) is this `doc(hidden)` check needed?
168            if !self.cx.tcx.is_doc_hidden(i.item_id.expect_def_id()) {
169                self.impls.extend(synthesize_auto_trait_and_blanket_impls(
170                    self.cx,
171                    i.item_id.expect_def_id(),
172                ));
173            }
174        }
175
176        self.visit_item_recur(i)
177    }
178}
179
180struct ItemAndAliasCollector<'cache> {
181    items: FxHashSet<ItemId>,
182    cache: &'cache Cache,
183}
184
185impl<'cache> ItemAndAliasCollector<'cache> {
186    fn new(cache: &'cache Cache) -> Self {
187        ItemAndAliasCollector { items: FxHashSet::default(), cache }
188    }
189}
190
191impl DocVisitor<'_> for ItemAndAliasCollector<'_> {
192    fn visit_item(&mut self, i: &Item) {
193        self.items.insert(i.item_id);
194
195        if let TypeAliasItem(alias) = &i.inner.kind
196            && let Some(did) = alias.type_.def_id(self.cache)
197        {
198            self.items.insert(ItemId::DefId(did));
199        }
200
201        self.visit_item_recur(i)
202    }
203}