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, ItemKind};
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            ItemKind::Stripped(..) => {
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            ItemKind::TypeAlias(..)
55            | ItemKind::Static(..)
56            | ItemKind::Struct(..)
57            | ItemKind::Enum(..)
58            | ItemKind::Trait(..)
59            | ItemKind::Function(..)
60            | ItemKind::Variant(..)
61            | ItemKind::ForeignFunction(..)
62            | ItemKind::ForeignStatic(..)
63            | ItemKind::Constant(..)
64            | ItemKind::Union(..)
65            | ItemKind::TraitAlias(..)
66            | ItemKind::DeclMacro(..)
67            | ItemKind::ForeignType => {
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            ItemKind::AssocFunction(clean::AssocFunction { body: Some(_), .. })
83            | ItemKind::AssocConstant(clean::AssocConstant { body: Some(_), .. })
84            | ItemKind::AssocType(clean::AssocType { type_: Some(_), .. }) => {
85                let item_id = i.item_id;
86                if item_id.is_local()
87                    && !self.effective_visibilities.is_reachable(self.tcx, item_id.expect_def_id())
88                {
89                    debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name);
90                    return None;
91                }
92            }
93
94            ItemKind::StructField(..) => {
95                if i.visibility(self.tcx) != Some(Visibility::Public) {
96                    return Some(strip_item(i));
97                }
98            }
99
100            ItemKind::Module(..) => {
101                if i.item_id.is_local()
102                    && !is_item_reachable(
103                        self.tcx,
104                        self.is_json_output,
105                        self.effective_visibilities,
106                        i.item_id,
107                    )
108                {
109                    debug!("Stripper: stripping module {:?}", i.name);
110                    let old = mem::replace(&mut self.update_retained, false);
111                    let ret = strip_item(self.fold_item_recur(i));
112                    self.update_retained = old;
113                    return Some(ret);
114                }
115            }
116
117            // handled in the `strip-priv-imports` pass
118            ItemKind::ExternCrate { .. } | ItemKind::Import(_) => {}
119
120            ItemKind::Impl(..) => {}
121
122            // Since the `doc_cfg` propagation was handled before the current pass, we can (and
123            // should) remove all placeholder impl items.
124            ItemKind::PlaceholderImpl => return None,
125
126            // They have no control over privacy
127            ItemKind::AssocFunction(clean::AssocFunction { body: None, .. })
128            | ItemKind::AssocConstant(clean::AssocConstant { body: None, .. })
129            | ItemKind::AssocType(clean::AssocType { type_: None, .. }) => {}
130
131            // Proc-macros are always public
132            ItemKind::ProcMacro(..) => {}
133
134            // Primitives are never stripped
135            ItemKind::PrimitiveType(..) => {}
136
137            // Keywords are never stripped
138            ItemKind::Keyword => {}
139            // Attributes are never stripped
140            ItemKind::Attribute => {}
141        }
142
143        let fastreturn = match i.kind {
144            // nothing left to do for traits (don't want to filter their
145            // methods out, visibility controlled by the trait)
146            ItemKind::Trait(..) => true,
147
148            // implementations of traits are always public.
149            ItemKind::Impl(ref imp) if imp.trait_.is_some() => true,
150            // Variant fields have inherited visibility
151            ItemKind::Variant(clean::Variant {
152                kind: clean::VariantKind::Struct(..) | clean::VariantKind::Tuple(..),
153                ..
154            }) => true,
155            _ => false,
156        };
157
158        let i = if fastreturn {
159            if self.update_retained {
160                self.retained.insert(i.item_id);
161            }
162            return Some(i);
163        } else {
164            self.fold_item_recur(i)
165        };
166
167        if self.update_retained {
168            self.retained.insert(i.item_id);
169        }
170        Some(i)
171    }
172}
173
174/// This stripper discards all impls which reference stripped items
175pub(crate) struct ImplStripper<'a, 'tcx> {
176    pub(crate) tcx: TyCtxt<'tcx>,
177    pub(crate) retained: &'a ItemIdSet,
178    pub(crate) cache: &'a Cache,
179    pub(crate) is_json_output: bool,
180    pub(crate) document_private: bool,
181    pub(crate) document_hidden: bool,
182}
183
184impl ImplStripper<'_, '_> {
185    #[inline]
186    fn should_keep_impl(&self, item: &Item, for_def_id: DefId) -> bool {
187        if !for_def_id.is_local() || self.retained.contains(&for_def_id.into()) {
188            true
189        } else if self.is_json_output {
190            // If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we
191            // need to keep it.
192            self.cache.effective_visibilities.is_exported(self.tcx, for_def_id)
193                && (self.document_hidden
194                    || ((!item.is_doc_hidden()
195                        && for_def_id
196                            .as_local()
197                            .map(|def_id| !inherits_doc_hidden(self.tcx, def_id, None))
198                            .unwrap_or(true))
199                        || self.cache.inlined_items.contains(&for_def_id)))
200        } else {
201            false
202        }
203    }
204}
205
206impl DocFolder for ImplStripper<'_, '_> {
207    fn fold_item(&mut self, i: Item) -> Option<Item> {
208        if let ItemKind::Impl(ref imp) = i.kind {
209            // Impl blocks can be skipped if they are: empty; not a trait impl; and have no
210            // documentation.
211            //
212            // There is one special case: if the impl block contains only private items.
213            if imp.trait_.is_none() {
214                // If the only items present are private ones and we're not rendering private items,
215                // we don't document it.
216                if !imp.items.is_empty()
217                    && !self.document_private
218                    && imp.items.iter().all(|i| {
219                        let item_id = i.item_id;
220                        item_id.is_local()
221                            && !self
222                                .cache
223                                .effective_visibilities
224                                .is_reachable(self.tcx, item_id.expect_def_id())
225                    })
226                {
227                    debug!("ImplStripper: no public item; removing {imp:?}");
228                    return None;
229                } else if imp.items.is_empty() && i.doc_value().is_empty() {
230                    debug!("ImplStripper: no item and no doc; removing {imp:?}");
231                    return None;
232                }
233            }
234            // Because we don't inline in `maybe_inline_local` if the output format is JSON,
235            // we need to make a special check for JSON output: we want to keep it unless it has
236            // a `#[doc(hidden)]` attribute if the `for_` type is exported.
237            if let Some(did) = imp.for_.def_id(self.cache)
238                && !imp.for_.is_assoc_ty()
239                && !self.should_keep_impl(&i, did)
240            {
241                debug!("ImplStripper: impl item for stripped type; removing {imp:?}");
242                return None;
243            }
244            if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id())
245                && !self.should_keep_impl(&i, did)
246            {
247                debug!("ImplStripper: impl item for stripped trait; removing {imp:?}");
248                return None;
249            }
250            if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) {
251                for typaram in generics {
252                    if let Some(did) = typaram.def_id(self.cache)
253                        && !self.should_keep_impl(&i, did)
254                    {
255                        debug!("ImplStripper: stripped item in trait's generics; removing {imp:?}");
256                        return None;
257                    }
258                }
259            }
260        }
261        Some(self.fold_item_recur(i))
262    }
263}
264
265/// This stripper discards all private import statements (`use`, `extern crate`)
266pub(crate) struct ImportStripper<'tcx> {
267    pub(crate) tcx: TyCtxt<'tcx>,
268    pub(crate) is_json_output: bool,
269    pub(crate) document_hidden: bool,
270    pub(crate) document_private: 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            ItemKind::Import(imp)
288                if !self.document_hidden && self.import_should_be_hidden(&i, imp) =>
289            {
290                debug!("ImportStripper: stripping {:?}", i.name);
291                None
292            }
293            ItemKind::Import(imp) if self.document_private => Some(self.fold_item_recur(i)),
294            ItemKind::ExternCrate { .. } | ItemKind::Import(..)
295                if i.visibility(self.tcx) != Some(Visibility::Public) =>
296            {
297                debug!("ImportStripper: stripping {:?}", i.name);
298                None
299            }
300            _ => Some(self.fold_item_recur(i)),
301        }
302    }
303}