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            // tymethods etc. have no control over privacy
124            clean::RequiredMethodItem(..)
125            | clean::RequiredAssocConstItem(..)
126            | clean::RequiredAssocTypeItem(..) => {}
127
128            // Proc-macros are always public
129            clean::ProcMacroItem(..) => {}
130
131            // Primitives are never stripped
132            clean::PrimitiveItem(..) => {}
133
134            // Keywords are never stripped
135            clean::KeywordItem => {}
136        }
137
138        let fastreturn = match i.kind {
139            // nothing left to do for traits (don't want to filter their
140            // methods out, visibility controlled by the trait)
141            clean::TraitItem(..) => true,
142
143            // implementations of traits are always public.
144            clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
145            // Variant fields have inherited visibility
146            clean::VariantItem(clean::Variant {
147                kind: clean::VariantKind::Struct(..) | clean::VariantKind::Tuple(..),
148                ..
149            }) => true,
150            _ => false,
151        };
152
153        let i = if fastreturn {
154            if self.update_retained {
155                self.retained.insert(i.item_id);
156            }
157            return Some(i);
158        } else {
159            self.fold_item_recur(i)
160        };
161
162        if self.update_retained {
163            self.retained.insert(i.item_id);
164        }
165        Some(i)
166    }
167}
168
169/// This stripper discards all impls which reference stripped items
170pub(crate) struct ImplStripper<'a, 'tcx> {
171    pub(crate) tcx: TyCtxt<'tcx>,
172    pub(crate) retained: &'a ItemIdSet,
173    pub(crate) cache: &'a Cache,
174    pub(crate) is_json_output: bool,
175    pub(crate) document_private: bool,
176    pub(crate) document_hidden: bool,
177}
178
179impl ImplStripper<'_, '_> {
180    #[inline]
181    fn should_keep_impl(&self, item: &Item, for_def_id: DefId) -> bool {
182        if !for_def_id.is_local() || self.retained.contains(&for_def_id.into()) {
183            true
184        } else if self.is_json_output {
185            // If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we
186            // need to keep it.
187            self.cache.effective_visibilities.is_exported(self.tcx, for_def_id)
188                && (self.document_hidden
189                    || ((!item.is_doc_hidden()
190                        && for_def_id
191                            .as_local()
192                            .map(|def_id| !inherits_doc_hidden(self.tcx, def_id, None))
193                            .unwrap_or(true))
194                        || self.cache.inlined_items.contains(&for_def_id)))
195        } else {
196            false
197        }
198    }
199}
200
201impl DocFolder for ImplStripper<'_, '_> {
202    fn fold_item(&mut self, i: Item) -> Option<Item> {
203        if let clean::ImplItem(ref imp) = i.kind {
204            // Impl blocks can be skipped if they are: empty; not a trait impl; and have no
205            // documentation.
206            //
207            // There is one special case: if the impl block contains only private items.
208            if imp.trait_.is_none() {
209                // If the only items present are private ones and we're not rendering private items,
210                // we don't document it.
211                if !imp.items.is_empty()
212                    && !self.document_private
213                    && imp.items.iter().all(|i| {
214                        let item_id = i.item_id;
215                        item_id.is_local()
216                            && !self
217                                .cache
218                                .effective_visibilities
219                                .is_reachable(self.tcx, item_id.expect_def_id())
220                    })
221                {
222                    debug!("ImplStripper: no public item; removing {imp:?}");
223                    return None;
224                } else if imp.items.is_empty() && i.doc_value().is_empty() {
225                    debug!("ImplStripper: no item and no doc; removing {imp:?}");
226                    return None;
227                }
228            }
229            // Because we don't inline in `maybe_inline_local` if the output format is JSON,
230            // we need to make a special check for JSON output: we want to keep it unless it has
231            // a `#[doc(hidden)]` attribute if the `for_` type is exported.
232            if let Some(did) = imp.for_.def_id(self.cache)
233                && !imp.for_.is_assoc_ty()
234                && !self.should_keep_impl(&i, did)
235            {
236                debug!("ImplStripper: impl item for stripped type; removing {imp:?}");
237                return None;
238            }
239            if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id())
240                && !self.should_keep_impl(&i, did)
241            {
242                debug!("ImplStripper: impl item for stripped trait; removing {imp:?}");
243                return None;
244            }
245            if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) {
246                for typaram in generics {
247                    if let Some(did) = typaram.def_id(self.cache)
248                        && !self.should_keep_impl(&i, did)
249                    {
250                        debug!("ImplStripper: stripped item in trait's generics; removing {imp:?}");
251                        return None;
252                    }
253                }
254            }
255        }
256        Some(self.fold_item_recur(i))
257    }
258}
259
260/// This stripper discards all private import statements (`use`, `extern crate`)
261pub(crate) struct ImportStripper<'tcx> {
262    pub(crate) tcx: TyCtxt<'tcx>,
263    pub(crate) is_json_output: bool,
264    pub(crate) document_hidden: bool,
265}
266
267impl ImportStripper<'_> {
268    fn import_should_be_hidden(&self, i: &Item, imp: &clean::Import) -> bool {
269        if self.is_json_output {
270            // FIXME: This should be handled the same way as for HTML output.
271            imp.imported_item_is_doc_hidden(self.tcx)
272        } else {
273            i.is_doc_hidden()
274        }
275    }
276}
277
278impl DocFolder for ImportStripper<'_> {
279    fn fold_item(&mut self, i: Item) -> Option<Item> {
280        match &i.kind {
281            clean::ImportItem(imp)
282                if !self.document_hidden && self.import_should_be_hidden(&i, imp) =>
283            {
284                None
285            }
286            // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None,
287            clean::ExternCrateItem { .. } | clean::ImportItem(..)
288                if i.visibility(self.tcx) != Some(Visibility::Public) =>
289            {
290                None
291            }
292            _ => Some(self.fold_item_recur(i)),
293        }
294    }
295}