Skip to main content

rustdoc/passes/
stripper.rs

1//! A collection of utility functions for the `strip_*` passes.
2
3use std::mem;
4
5use rustc_hir::def_id::DefId;
6use rustc_middle::ty::{TyCtxt, Visibility};
7use tracing::debug;
8
9use crate::clean::utils::inherits_doc_hidden;
10use crate::clean::{self, Item, ItemId, ItemIdSet};
11use crate::fold::{DocFolder, strip_item};
12use crate::formats::cache::Cache;
13use crate::visit_lib::RustdocEffectiveVisibilities;
14
15pub(crate) struct Stripper<'a, 'tcx> {
16    pub(crate) retained: &'a mut ItemIdSet,
17    pub(crate) effective_visibilities: &'a RustdocEffectiveVisibilities,
18    pub(crate) update_retained: bool,
19    pub(crate) is_json_output: bool,
20    pub(crate) tcx: TyCtxt<'tcx>,
21}
22
23// We need to handle this differently for the JSON output because some non exported items could
24// be used in public API. And so, we need these items as well. `is_exported` only checks if they
25// are in the public API, which is not enough.
26#[inline]
27fn is_item_reachable(
28    tcx: TyCtxt<'_>,
29    is_json_output: bool,
30    effective_visibilities: &RustdocEffectiveVisibilities,
31    item_id: ItemId,
32) -> bool {
33    if is_json_output {
34        effective_visibilities.is_reachable(tcx, item_id.expect_def_id())
35    } else {
36        effective_visibilities.is_exported(tcx, item_id.expect_def_id())
37    }
38}
39
40impl DocFolder for Stripper<'_, '_> {
41    fn fold_item(&mut self, i: Item) -> Option<Item> {
42        match i.kind {
43            clean::StrippedItem(..) => {
44                // We need to recurse into stripped modules to strip things
45                // like impl methods but when doing so we must not add any
46                // items to the `retained` set.
47                debug!("Stripper: recursing into stripped {:?} {:?}", i.type_(), i.name);
48                let old = mem::replace(&mut self.update_retained, false);
49                let ret = self.fold_item_recur(i);
50                self.update_retained = old;
51                return Some(ret);
52            }
53            // These items can all get re-exported
54            clean::TypeAliasItem(..)
55            | clean::StaticItem(..)
56            | clean::StructItem(..)
57            | clean::EnumItem(..)
58            | clean::TraitItem(..)
59            | clean::FunctionItem(..)
60            | clean::VariantItem(..)
61            | clean::ForeignFunctionItem(..)
62            | clean::ForeignStaticItem(..)
63            | clean::ConstantItem(..)
64            | clean::UnionItem(..)
65            | clean::TraitAliasItem(..)
66            | clean::MacroItem(..)
67            | clean::ForeignTypeItem => {
68                let item_id = i.item_id;
69                if item_id.is_local()
70                    && !is_item_reachable(
71                        self.tcx,
72                        self.is_json_output,
73                        self.effective_visibilities,
74                        item_id,
75                    )
76                {
77                    debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name);
78                    return None;
79                }
80            }
81
82            clean::MethodItem(..)
83            | clean::ProvidedAssocConstItem(..)
84            | clean::ImplAssocConstItem(..)
85            | clean::AssocTypeItem(..) => {
86                let item_id = i.item_id;
87                if item_id.is_local()
88                    && !self.effective_visibilities.is_reachable(self.tcx, item_id.expect_def_id())
89                {
90                    debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name);
91                    return None;
92                }
93            }
94
95            clean::StructFieldItem(..) => {
96                if i.visibility(self.tcx) != Some(Visibility::Public) {
97                    return Some(strip_item(i));
98                }
99            }
100
101            clean::ModuleItem(..) => {
102                if i.item_id.is_local()
103                    && !is_item_reachable(
104                        self.tcx,
105                        self.is_json_output,
106                        self.effective_visibilities,
107                        i.item_id,
108                    )
109                {
110                    debug!("Stripper: stripping module {:?}", i.name);
111                    let old = mem::replace(&mut self.update_retained, false);
112                    let ret = strip_item(self.fold_item_recur(i));
113                    self.update_retained = old;
114                    return Some(ret);
115                }
116            }
117
118            // handled in the `strip-priv-imports` pass
119            clean::ExternCrateItem { .. } | clean::ImportItem(_) => {}
120
121            clean::ImplItem(..) => {}
122
123            // Since the `doc_cfg` propagation was handled before the current pass, we can (and
124            // should) remove all placeholder impl items.
125            clean::PlaceholderImplItem => return None,
126
127            // tymethods etc. have no control over privacy
128            clean::RequiredMethodItem(..)
129            | clean::RequiredAssocConstItem(..)
130            | clean::RequiredAssocTypeItem(..) => {}
131
132            // Proc-macros are always public
133            clean::ProcMacroItem(..) => {}
134
135            // Primitives are never stripped
136            clean::PrimitiveItem(..) => {}
137
138            // Keywords are never stripped
139            clean::KeywordItem => {}
140            // Attributes are never stripped
141            clean::AttributeItem => {}
142        }
143
144        let fastreturn = match i.kind {
145            // nothing left to do for traits (don't want to filter their
146            // methods out, visibility controlled by the trait)
147            clean::TraitItem(..) => true,
148
149            // implementations of traits are always public.
150            clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
151            // Variant fields have inherited visibility
152            clean::VariantItem(clean::Variant {
153                kind: clean::VariantKind::Struct(..) | clean::VariantKind::Tuple(..),
154                ..
155            }) => true,
156            _ => false,
157        };
158
159        let i = if fastreturn {
160            if self.update_retained {
161                self.retained.insert(i.item_id);
162            }
163            return Some(i);
164        } else {
165            self.fold_item_recur(i)
166        };
167
168        if self.update_retained {
169            self.retained.insert(i.item_id);
170        }
171        Some(i)
172    }
173}
174
175/// This stripper discards all impls which reference stripped items
176pub(crate) struct ImplStripper<'a, 'tcx> {
177    pub(crate) tcx: TyCtxt<'tcx>,
178    pub(crate) retained: &'a ItemIdSet,
179    pub(crate) cache: &'a Cache,
180    pub(crate) is_json_output: bool,
181    pub(crate) document_private: bool,
182    pub(crate) document_hidden: bool,
183}
184
185impl ImplStripper<'_, '_> {
186    #[inline]
187    fn should_keep_impl(&self, item: &Item, for_def_id: DefId) -> bool {
188        if !for_def_id.is_local() || self.retained.contains(&for_def_id.into()) {
189            true
190        } else if self.is_json_output {
191            // If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we
192            // need to keep it.
193            self.cache.effective_visibilities.is_exported(self.tcx, for_def_id)
194                && (self.document_hidden
195                    || ((!item.is_doc_hidden()
196                        && for_def_id
197                            .as_local()
198                            .map(|def_id| !inherits_doc_hidden(self.tcx, def_id, None))
199                            .unwrap_or(true))
200                        || self.cache.inlined_items.contains(&for_def_id)))
201        } else {
202            false
203        }
204    }
205}
206
207impl DocFolder for ImplStripper<'_, '_> {
208    fn fold_item(&mut self, i: Item) -> Option<Item> {
209        if let clean::ImplItem(ref imp) = i.kind {
210            // Impl blocks can be skipped if they are: empty; not a trait impl; and have no
211            // documentation.
212            //
213            // There is one special case: if the impl block contains only private items.
214            if imp.trait_.is_none() {
215                // If the only items present are private ones and we're not rendering private items,
216                // we don't document it.
217                if !imp.items.is_empty()
218                    && !self.document_private
219                    && imp.items.iter().all(|i| {
220                        let item_id = i.item_id;
221                        item_id.is_local()
222                            && !self
223                                .cache
224                                .effective_visibilities
225                                .is_reachable(self.tcx, item_id.expect_def_id())
226                    })
227                {
228                    debug!("ImplStripper: no public item; removing {imp:?}");
229                    return None;
230                } else if imp.items.is_empty() && i.doc_value().is_empty() {
231                    debug!("ImplStripper: no item and no doc; removing {imp:?}");
232                    return None;
233                }
234            }
235            // Because we don't inline in `maybe_inline_local` if the output format is JSON,
236            // we need to make a special check for JSON output: we want to keep it unless it has
237            // a `#[doc(hidden)]` attribute if the `for_` type is exported.
238            if let Some(did) = imp.for_.def_id(self.cache)
239                && !imp.for_.is_assoc_ty()
240                && !self.should_keep_impl(&i, did)
241            {
242                debug!("ImplStripper: impl item for stripped type; removing {imp:?}");
243                return None;
244            }
245            if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id())
246                && !self.should_keep_impl(&i, did)
247            {
248                debug!("ImplStripper: impl item for stripped trait; removing {imp:?}");
249                return None;
250            }
251            if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) {
252                for typaram in generics {
253                    if let Some(did) = typaram.def_id(self.cache)
254                        && !self.should_keep_impl(&i, did)
255                    {
256                        debug!("ImplStripper: stripped item in trait's generics; removing {imp:?}");
257                        return None;
258                    }
259                }
260            }
261        }
262        Some(self.fold_item_recur(i))
263    }
264}
265
266/// This stripper discards all private import statements (`use`, `extern crate`)
267pub(crate) struct ImportStripper<'tcx> {
268    pub(crate) tcx: TyCtxt<'tcx>,
269    pub(crate) is_json_output: bool,
270    pub(crate) document_hidden: bool,
271}
272
273impl ImportStripper<'_> {
274    fn import_should_be_hidden(&self, i: &Item, imp: &clean::Import) -> bool {
275        if self.is_json_output {
276            // FIXME: This should be handled the same way as for HTML output.
277            imp.imported_item_is_doc_hidden(self.tcx)
278        } else {
279            i.is_doc_hidden()
280        }
281    }
282}
283
284impl DocFolder for ImportStripper<'_> {
285    fn fold_item(&mut self, i: Item) -> Option<Item> {
286        match &i.kind {
287            clean::ImportItem(imp)
288                if !self.document_hidden && self.import_should_be_hidden(&i, imp) =>
289            {
290                None
291            }
292            // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None,
293            clean::ExternCrateItem { .. } | clean::ImportItem(..)
294                if i.visibility(self.tcx) != Some(Visibility::Public) =>
295            {
296                None
297            }
298            _ => Some(self.fold_item_recur(i)),
299        }
300    }
301}