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