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