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::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE};
7use rustc_middle::ty;
8use rustc_span::symbol::sym;
9use tracing::debug;
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).iter().map(|p| p.1).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                    inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
56                });
57            }
58        }
59    }
60
61    // Local trait impls.
62    {
63        let _prof_timer = tcx.sess.prof.generic_activity("build_local_trait_impls");
64        let mut attr_buf = Vec::new();
65        for &impl_def_id in tcx.trait_impls_in_crate(LOCAL_CRATE) {
66            let mut parent = Some(tcx.parent(impl_def_id));
67            while let Some(did) = parent {
68                attr_buf.extend(
69                    tcx.get_attrs(did, sym::doc)
70                        .filter(|attr| {
71                            if let Some([attr]) = attr.meta_item_list().as_deref() {
72                                attr.has_name(sym::cfg)
73                            } else {
74                                false
75                            }
76                        })
77                        .cloned(),
78                );
79                parent = tcx.opt_parent(did);
80            }
81            cx.with_param_env(impl_def_id, |cx| {
82                inline::build_impl(cx, impl_def_id, Some((&attr_buf, None)), &mut new_items_local);
83            });
84            attr_buf.clear();
85        }
86    }
87
88    tcx.sess.prof.generic_activity("build_primitive_trait_impls").run(|| {
89        for def_id in PrimitiveType::all_impls(tcx) {
90            // Try to inline primitive impls from other crates.
91            if !def_id.is_local() {
92                cx.with_param_env(def_id, |cx| {
93                    inline::build_impl(cx, def_id, None, &mut new_items_external);
94                });
95            }
96        }
97        for (prim, did) in PrimitiveType::primitive_locations(tcx) {
98            // Do not calculate blanket impl list for docs that are not going to be rendered.
99            // While the `impl` blocks themselves are only in `libcore`, the module with `doc`
100            // attached is directly included in `libstd` as well.
101            if did.is_local() {
102                for def_id in prim.impls(tcx).filter(|def_id| {
103                    // Avoid including impl blocks with filled-in generics.
104                    // https://github.com/rust-lang/rust/issues/94937
105                    //
106                    // FIXME(notriddle): https://github.com/rust-lang/rust/issues/97129
107                    //
108                    // This tactic of using inherent impl blocks for getting
109                    // auto traits and blanket impls is a hack. What we really
110                    // want is to check if `[T]` impls `Send`, which has
111                    // nothing to do with the inherent impl.
112                    //
113                    // Rustdoc currently uses these `impl` block as a source of
114                    // the `Ty`, as well as the `ParamEnv`, `GenericArgsRef`, and
115                    // `Generics`. To avoid relying on the `impl` block, these
116                    // things would need to be created from wholecloth, in a
117                    // form that is valid for use in type inference.
118                    let ty = tcx.type_of(def_id).instantiate_identity();
119                    match ty.kind() {
120                        ty::Slice(ty) | ty::Ref(_, ty, _) | ty::RawPtr(ty, _) => {
121                            matches!(ty.kind(), ty::Param(..))
122                        }
123                        ty::Tuple(tys) => tys.iter().all(|ty| matches!(ty.kind(), ty::Param(..))),
124                        _ => true,
125                    }
126                }) {
127                    let impls = synthesize_auto_trait_and_blanket_impls(cx, def_id);
128                    new_items_external.extend(impls.filter(|i| cx.inlined.insert(i.item_id)));
129                }
130            }
131        }
132    });
133
134    let mut cleaner = BadImplStripper { prims, items: crate_items, cache: &cx.cache };
135    let mut type_did_to_deref_target: DefIdMap<&Type> = DefIdMap::default();
136
137    // Follow all `Deref` targets of included items and recursively add them as valid
138    fn add_deref_target(
139        cx: &DocContext<'_>,
140        map: &DefIdMap<&Type>,
141        cleaner: &mut BadImplStripper<'_>,
142        targets: &mut DefIdSet,
143        type_did: DefId,
144    ) {
145        if let Some(target) = map.get(&type_did) {
146            debug!("add_deref_target: type {:?}, target {:?}", type_did, target);
147            if let Some(target_prim) = target.primitive_type() {
148                cleaner.prims.insert(target_prim);
149            } else if let Some(target_did) = target.def_id(&cx.cache) {
150                // `impl Deref<Target = S> for S`
151                if !targets.insert(target_did) {
152                    // Avoid infinite cycles
153                    return;
154                }
155                cleaner.items.insert(target_did.into());
156                add_deref_target(cx, map, cleaner, targets, target_did);
157            }
158        }
159    }
160
161    // scan through included items ahead of time to splice in Deref targets to the "valid" sets
162    for it in new_items_external.iter().chain(new_items_local.iter()) {
163        if let ImplItem(box Impl { ref for_, ref trait_, ref items, .. }) = it.kind
164            && trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait()
165            && cleaner.keep_impl(for_, true)
166        {
167            let target = items
168                .iter()
169                .find_map(|item| match item.kind {
170                    AssocTypeItem(ref t, _) => Some(&t.type_),
171                    _ => None,
172                })
173                .expect("Deref impl without Target type");
174
175            if let Some(prim) = target.primitive_type() {
176                cleaner.prims.insert(prim);
177            } else if let Some(did) = target.def_id(&cx.cache) {
178                cleaner.items.insert(did.into());
179            }
180            if let Some(for_did) = for_.def_id(&cx.cache)
181                && type_did_to_deref_target.insert(for_did, target).is_none()
182                // Since only the `DefId` portion of the `Type` instances is known to be same for both the
183                // `Deref` target type and the impl for type positions, this map of types is keyed by
184                // `DefId` and for convenience uses a special cleaner that accepts `DefId`s directly.
185                && cleaner.keep_impl_with_def_id(for_did.into())
186            {
187                let mut targets = DefIdSet::default();
188                targets.insert(for_did);
189                add_deref_target(
190                    cx,
191                    &type_did_to_deref_target,
192                    &mut cleaner,
193                    &mut targets,
194                    for_did,
195                );
196            }
197        }
198    }
199
200    // Filter out external items that are not needed
201    new_items_external.retain(|it| {
202        if let ImplItem(box Impl { ref for_, ref trait_, ref kind, .. }) = it.kind {
203            cleaner.keep_impl(
204                for_,
205                trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait(),
206            ) || trait_.as_ref().is_some_and(|t| cleaner.keep_impl_with_def_id(t.def_id().into()))
207                || kind.is_blanket()
208        } else {
209            true
210        }
211    });
212
213    if let ModuleItem(Module { items, .. }) = &mut krate.module.inner.kind {
214        items.extend(synth_impls);
215        items.extend(new_items_external);
216        items.extend(new_items_local);
217    } else {
218        panic!("collect-trait-impls can't run");
219    };
220
221    krate.external_traits.extend(cx.external_traits.drain(..));
222
223    krate
224}
225
226struct SyntheticImplCollector<'a, 'tcx> {
227    cx: &'a mut DocContext<'tcx>,
228    impls: Vec<Item>,
229}
230
231impl DocVisitor<'_> for SyntheticImplCollector<'_, '_> {
232    fn visit_item(&mut self, i: &Item) {
233        if i.is_struct() || i.is_enum() || i.is_union() {
234            // FIXME(eddyb) is this `doc(hidden)` check needed?
235            if !self.cx.tcx.is_doc_hidden(i.item_id.expect_def_id()) {
236                self.impls.extend(synthesize_auto_trait_and_blanket_impls(
237                    self.cx,
238                    i.item_id.expect_def_id(),
239                ));
240            }
241        }
242
243        self.visit_item_recur(i)
244    }
245}
246
247struct ItemAndAliasCollector<'cache> {
248    items: FxHashSet<ItemId>,
249    cache: &'cache Cache,
250}
251
252impl<'cache> ItemAndAliasCollector<'cache> {
253    fn new(cache: &'cache Cache) -> Self {
254        ItemAndAliasCollector { items: FxHashSet::default(), cache }
255    }
256}
257
258impl DocVisitor<'_> for ItemAndAliasCollector<'_> {
259    fn visit_item(&mut self, i: &Item) {
260        self.items.insert(i.item_id);
261
262        if let TypeAliasItem(alias) = &i.inner.kind
263            && let Some(did) = alias.type_.def_id(self.cache)
264        {
265            self.items.insert(ItemId::DefId(did));
266        }
267
268        self.visit_item_recur(i)
269    }
270}
271
272struct BadImplStripper<'a> {
273    prims: FxHashSet<PrimitiveType>,
274    items: FxHashSet<ItemId>,
275    cache: &'a Cache,
276}
277
278impl BadImplStripper<'_> {
279    fn keep_impl(&self, ty: &Type, is_deref: bool) -> bool {
280        if let Generic(_) = ty {
281            // keep impls made on generics
282            true
283        } else if let Some(prim) = ty.primitive_type() {
284            self.prims.contains(&prim)
285        } else if let Some(did) = ty.def_id(self.cache) {
286            is_deref || self.keep_impl_with_def_id(did.into())
287        } else {
288            false
289        }
290    }
291
292    fn keep_impl_with_def_id(&self, item_id: ItemId) -> bool {
293        self.items.contains(&item_id)
294    }
295}