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