Skip to main content

rustdoc/passes/
collect_trait_impls.rs

1//! Collects trait impls for each item in the crate.
2//!
3//! For example, if a crate defines a struct that implements a trait,
4//! this pass will note that the struct implements that trait.
5
6use rustc_data_structures::fx::FxHashSet;
7use rustc_errors::FatalError;
8use rustc_hir::attrs::{AttributeKind, DocAttribute};
9use rustc_hir::def_id::{DefId, LOCAL_CRATE};
10use rustc_hir::{Attribute, find_attr};
11use rustc_middle::ty::{self, Ty, TyCtxt};
12use rustc_span::kw;
13use tracing::debug;
14
15use crate::clean::*;
16use crate::core::DocContext;
17use crate::formats::cache::Cache;
18use crate::visit::DocVisitor;
19
20pub(super) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> Crate {
21    let tcx = cx.tcx;
22    // We need to check if there are errors before running this pass because it would crash when
23    // we try to get auto and blanket implementations.
24    if tcx.dcx().has_errors().is_some() {
25        return krate;
26    }
27
28    let synth_impls = cx.sess().time("collect_synthetic_impls", || {
29        let mut synth = SyntheticImplCollector { cx, impls: Vec::new() };
30        synth.visit_crate(&krate);
31        synth.impls
32    });
33
34    let crate_items = {
35        let mut coll = ItemAndAliasCollector::new(&cx.cache);
36        cx.sess().time("collect_items_for_trait_impls", || coll.visit_crate(&krate));
37        coll.items
38    };
39
40    let mut new_items_external = Vec::new();
41    let mut new_items_local = Vec::new();
42
43    // External trait impls.
44    {
45        let _prof_timer = tcx.sess.prof.generic_activity("build_extern_trait_impls");
46        for &cnum in tcx.crates(()) {
47            for &impl_def_id in tcx.trait_impls_in_crate(cnum) {
48                let trait_ref = tcx.impl_trait_ref(impl_def_id);
49                debug!("considering extern trait impl {trait_ref:?}");
50                if crate_items.contains(&ItemId::DefId(trait_ref.def_id()))
51                    || Some(trait_ref.def_id()) == tcx.lang_items().deref_trait()
52                    || tcx.is_doc_notable_trait(trait_ref.def_id())
53                {
54                    debug!("-> inlining due to trait");
55                    cx.with_param_env(impl_def_id, |cx| {
56                        inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
57                    });
58                } else {
59                    let self_ty = tcx.type_of(impl_def_id).instantiate_identity().skip_norm_wip();
60                    debug!(?self_ty);
61                    let self_ty_head = SelfTyHead::of(ty::Binder::dummy(self_ty), tcx, impl_def_id);
62                    debug!(?self_ty_head);
63                    let keep_impl = match self_ty_head {
64                        SelfTyHead::Generic => true,
65                        SelfTyHead::Item(def_id) => crate_items.contains(&ItemId::DefId(def_id)),
66                        SelfTyHead::Primitive | SelfTyHead::Other => false,
67                    };
68                    if keep_impl {
69                        debug!("-> inlining due to self ty");
70                        cx.with_param_env(impl_def_id, |cx| {
71                            inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
72                        });
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 (prim, did) in PrimitiveType::primitive_locations(tcx) {
102            // Do not calculate blanket impl list for docs that are not going to be rendered.
103            // While the `impl` blocks themselves are only in `libcore`, the module with `doc`
104            // attached is directly included in `libstd` as well.
105            if did.is_local() {
106                for impl_def_id in prim.impls(tcx) {
107                    // Try to inline primitive impls from other crates.
108                    if !impl_def_id.is_local() {
109                        cx.with_param_env(impl_def_id, |cx| {
110                            inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
111                        });
112                    }
113                }
114
115                // HACK: this is all one massive hack that is very hard to get rid of (see comment below)
116                for def_id in prim.impls(tcx).filter(|&def_id| {
117                    // Avoid including impl blocks with filled-in generics.
118                    // https://github.com/rust-lang/rust/issues/94937
119                    //
120                    // FIXME(notriddle): https://github.com/rust-lang/rust/issues/97129
121                    //
122                    // This tactic of using inherent impl blocks for getting
123                    // auto traits and blanket impls is a hack. What we really
124                    // want is to check if `[T]` impls `Send`, which has
125                    // nothing to do with the inherent impl.
126                    //
127                    // Rustdoc currently uses these `impl` block as a source of
128                    // the `Ty`, as well as the `ParamEnv`, `GenericArgsRef`, and
129                    // `Generics`. To avoid relying on the `impl` block, these
130                    // things would need to be created from wholecloth, in a
131                    // form that is valid for use in type inference.
132                    let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
133                    match ty.kind() {
134                        ty::Slice(ty) | ty::Ref(_, ty, _) | ty::RawPtr(ty, _) => {
135                            matches!(ty.kind(), ty::Param(..))
136                        }
137                        ty::Tuple(tys) => tys.iter().all(|ty| matches!(ty.kind(), ty::Param(..))),
138                        _ => true,
139                    }
140                }) {
141                    let impls = synthesize_auto_trait_and_blanket_impls(cx, def_id);
142                    new_items_external.extend(impls.filter(|i| cx.inlined.insert(i.item_id)));
143                }
144            }
145        }
146    });
147
148    if let ModuleItem(Module { items, .. }) = &mut krate.module.inner.kind {
149        items.extend(synth_impls);
150        items.extend(new_items_external);
151        items.extend(new_items_local);
152    } else {
153        panic!("collect-trait-impls can't run");
154    };
155
156    krate.external_traits.extend(cx.external_traits.drain(..));
157
158    krate
159}
160
161#[derive(Debug)]
162enum SelfTyHead {
163    Generic,
164    Primitive,
165    Item(DefId),
166    Other,
167}
168
169impl SelfTyHead {
170    /// Compute the "head" (top-level structure) of a type.
171    ///
172    /// When deciding whether to inline an impl, one of the things we look at is
173    /// whether the Self type (the `Foo` in `impl Foo` or `impl Tr for Foo`) is
174    /// present in the current crate (usually itself through inlining). However,
175    /// constructing a full [`clean::Type`](Type) is expensive and more than we need,
176    /// so this function computes just enough information to determine if the type
177    /// is in the current crate.
178    // FIXME: once -Znormalize-docs works properly / becomes the default,
179    // this should invoke normalization where needed (e.g. if the head is an Alias).
180    // we'll need to fetch the param_env too.
181    fn of<'tcx>(bound_ty: ty::Binder<'tcx, Ty<'tcx>>, tcx: TyCtxt<'tcx>, parent: DefId) -> Self {
182        match *bound_ty.skip_binder().kind() {
183            ty::Never
184            | ty::Bool
185            | ty::Char
186            | ty::Int(..)
187            | ty::Uint(..)
188            | ty::Float(..)
189            | ty::Str
190            | ty::Slice(..)
191            | ty::Array(..)
192            | ty::RawPtr(..)
193            | ty::FnDef(..)
194            | ty::FnPtr(..)
195            | ty::Tuple(_) => Self::Primitive,
196            ty::Pat(ty, _) => Self::of(bound_ty.rebind(ty), tcx, parent),
197            ty::Ref(_, ty, _) => match Self::of(bound_ty.rebind(ty), tcx, parent) {
198                Self::Generic => Self::Primitive,
199                head => head,
200            },
201            // FIXME(unsafe_binders): this should probably recurse through the unsafe binder,
202            // but clean_middle_ty doesn't handle this correctly yet either
203            ty::UnsafeBinder(_) => Self::Other,
204            ty::Adt(def, _) => Self::Item(def.did()),
205            ty::Foreign(did) => Self::Item(did),
206            ty::Dynamic(obj, _) => {
207                // HACK: pick the first `did` as the `did` of the trait object. Someone
208                // might want to implement "native" support for marker-trait-only
209                // trait objects.
210                let mut dids = obj.auto_traits();
211                let did = obj
212                    .principal_def_id()
213                    .or_else(|| dids.next())
214                    .unwrap_or_else(|| panic!("found trait object `{obj:?}` with no traits?"));
215                Self::Item(did)
216            }
217
218            ty::Alias(_, alias_ty @ ty::AliasTy { kind: ty::Projection { def_id }, .. }) => {
219                debug_assert!(!tcx.is_impl_trait_in_trait(def_id));
220                Self::of(bound_ty.rebind(alias_ty.self_ty()), tcx, parent)
221            }
222
223            ty::Alias(_, alias_ty @ ty::AliasTy { kind: ty::Inherent { .. }, .. }) => {
224                let alias_ty = bound_ty.rebind(alias_ty);
225                Self::of(alias_ty.map_bound(|ty| ty.self_ty()), tcx, parent)
226            }
227
228            ty::Alias(_, ty::AliasTy { kind: ty::Free { def_id }, args, .. }) => {
229                if tcx.features().checked_type_aliases() {
230                    // Free type alias `data` represents the `type X` in `type X = Y`. If we need `Y`,
231                    // we need to use `type_of`.
232                    Self::Item(def_id)
233                } else {
234                    let ty = tcx.type_of(def_id).instantiate(tcx, args).skip_norm_wip();
235                    Self::of(bound_ty.rebind(ty), tcx, parent)
236                }
237            }
238
239            ty::Param(ref p) => {
240                // FIXME: there's a slight behavior difference from clean_middle_ty here
241                // since here we represent impl traits as Generic not ImplTrait.
242                // probably doesn't matter for collect trait impls since impl trait
243                // can't be a self ty
244                if p.name == kw::SelfUpper { Self::Other } else { Self::Generic }
245            }
246
247            ty::Bound(_, ref ty) => match ty.kind {
248                ty::BoundTyKind::Param(_) => Self::Generic,
249                ty::BoundTyKind::Anon => panic!("unexpected anonymous bound type variable"),
250            },
251
252            ty::Alias(_, ty::AliasTy { kind: ty::Opaque { .. }, .. }) => {
253                panic!("{bound_ty} should not appear as impl self ty")
254            }
255
256            ty::Closure(..)
257            | ty::CoroutineClosure(..)
258            | ty::Coroutine(..)
259            | ty::Placeholder(..)
260            | ty::CoroutineWitness(..)
261            | ty::Infer(..) => panic!("unexpected impl self ty {bound_ty}"),
262
263            ty::Error(_) => FatalError.raise(),
264        }
265    }
266}
267
268struct SyntheticImplCollector<'a, 'tcx> {
269    cx: &'a mut DocContext<'tcx>,
270    impls: Vec<Item>,
271}
272
273impl DocVisitor<'_> for SyntheticImplCollector<'_, '_> {
274    fn visit_item(&mut self, i: &Item) {
275        if i.is_struct() || i.is_enum() || i.is_union() {
276            let item_def_id = i.item_id.expect_def_id();
277            // FIXME(eddyb) is this `doc(hidden)` check needed?
278            // FIXME(camelid) should we skip the `doc(hidden)` check if --document-hidden-items is passed?
279            if (self.cx.document_private()
280                || self.cx.cache.effective_visibilities.is_reachable(self.cx.tcx, item_def_id))
281                && !self.cx.tcx.is_doc_hidden(item_def_id)
282            {
283                self.impls.extend(synthesize_auto_trait_and_blanket_impls(self.cx, item_def_id));
284            }
285        }
286
287        self.visit_item_recur(i)
288    }
289}
290
291struct ItemAndAliasCollector<'cache> {
292    items: FxHashSet<ItemId>,
293    cache: &'cache Cache,
294}
295
296impl<'cache> ItemAndAliasCollector<'cache> {
297    fn new(cache: &'cache Cache) -> Self {
298        ItemAndAliasCollector { items: FxHashSet::default(), cache }
299    }
300}
301
302impl DocVisitor<'_> for ItemAndAliasCollector<'_> {
303    fn visit_item(&mut self, i: &Item) {
304        self.items.insert(i.item_id);
305
306        if let TypeAliasItem(alias) = &i.inner.kind
307            && let Some(did) = alias.type_.def_id(self.cache)
308        {
309            self.items.insert(ItemId::DefId(did));
310        }
311
312        self.visit_item_recur(i)
313    }
314}