1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//! A collection of utility functions for the `strip_*` passes.
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{TyCtxt, Visibility};
use std::mem;

use crate::clean::utils::inherits_doc_hidden;
use crate::clean::{self, Item, ItemId, ItemIdSet};
use crate::fold::{strip_item, DocFolder};
use crate::formats::cache::Cache;
use crate::visit_lib::RustdocEffectiveVisibilities;

pub(crate) struct Stripper<'a, 'tcx> {
    pub(crate) retained: &'a mut ItemIdSet,
    pub(crate) effective_visibilities: &'a RustdocEffectiveVisibilities,
    pub(crate) update_retained: bool,
    pub(crate) is_json_output: bool,
    pub(crate) tcx: TyCtxt<'tcx>,
}

// We need to handle this differently for the JSON output because some non exported items could
// be used in public API. And so, we need these items as well. `is_exported` only checks if they
// are in the public API, which is not enough.
#[inline]
fn is_item_reachable(
    tcx: TyCtxt<'_>,
    is_json_output: bool,
    effective_visibilities: &RustdocEffectiveVisibilities,
    item_id: ItemId,
) -> bool {
    if is_json_output {
        effective_visibilities.is_reachable(tcx, item_id.expect_def_id())
    } else {
        effective_visibilities.is_exported(tcx, item_id.expect_def_id())
    }
}

impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
    fn fold_item(&mut self, i: Item) -> Option<Item> {
        match *i.kind {
            clean::StrippedItem(..) => {
                // We need to recurse into stripped modules to strip things
                // like impl methods but when doing so we must not add any
                // items to the `retained` set.
                debug!("Stripper: recursing into stripped {:?} {:?}", i.type_(), i.name);
                let old = mem::replace(&mut self.update_retained, false);
                let ret = self.fold_item_recur(i);
                self.update_retained = old;
                return Some(ret);
            }
            // These items can all get re-exported
            clean::OpaqueTyItem(..)
            | clean::TypeAliasItem(..)
            | clean::StaticItem(..)
            | clean::StructItem(..)
            | clean::EnumItem(..)
            | clean::TraitItem(..)
            | clean::FunctionItem(..)
            | clean::VariantItem(..)
            | clean::ForeignFunctionItem(..)
            | clean::ForeignStaticItem(..)
            | clean::ConstantItem(..)
            | clean::UnionItem(..)
            | clean::TraitAliasItem(..)
            | clean::MacroItem(..)
            | clean::ForeignTypeItem => {
                let item_id = i.item_id;
                if item_id.is_local()
                    && !is_item_reachable(
                        self.tcx,
                        self.is_json_output,
                        self.effective_visibilities,
                        item_id,
                    )
                {
                    debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name);
                    return None;
                }
            }

            clean::MethodItem(..) | clean::AssocConstItem(..) | clean::AssocTypeItem(..) => {
                let item_id = i.item_id;
                if item_id.is_local()
                    && !self.effective_visibilities.is_reachable(self.tcx, item_id.expect_def_id())
                {
                    debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name);
                    return None;
                }
            }

            clean::StructFieldItem(..) => {
                if i.visibility(self.tcx) != Some(Visibility::Public) {
                    return Some(strip_item(i));
                }
            }

            clean::ModuleItem(..) => {
                if i.item_id.is_local() && i.visibility(self.tcx) != Some(Visibility::Public) {
                    debug!("Stripper: stripping module {:?}", i.name);
                    let old = mem::replace(&mut self.update_retained, false);
                    let ret = strip_item(self.fold_item_recur(i));
                    self.update_retained = old;
                    return Some(ret);
                }
            }

            // handled in the `strip-priv-imports` pass
            clean::ExternCrateItem { .. } | clean::ImportItem(_) => {}

            clean::ImplItem(..) => {}

            // tymethods etc. have no control over privacy
            clean::TyMethodItem(..) | clean::TyAssocConstItem(..) | clean::TyAssocTypeItem(..) => {}

            // Proc-macros are always public
            clean::ProcMacroItem(..) => {}

            // Primitives are never stripped
            clean::PrimitiveItem(..) => {}

            // Keywords are never stripped
            clean::KeywordItem => {}
        }

        let fastreturn = match *i.kind {
            // nothing left to do for traits (don't want to filter their
            // methods out, visibility controlled by the trait)
            clean::TraitItem(..) => true,

            // implementations of traits are always public.
            clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
            // Variant fields have inherited visibility
            clean::VariantItem(clean::Variant {
                kind: clean::VariantKind::Struct(..) | clean::VariantKind::Tuple(..),
                ..
            }) => true,
            _ => false,
        };

        let i = if fastreturn {
            if self.update_retained {
                self.retained.insert(i.item_id);
            }
            return Some(i);
        } else {
            self.fold_item_recur(i)
        };

        if self.update_retained {
            self.retained.insert(i.item_id);
        }
        Some(i)
    }
}

/// This stripper discards all impls which reference stripped items
pub(crate) struct ImplStripper<'a, 'tcx> {
    pub(crate) tcx: TyCtxt<'tcx>,
    pub(crate) retained: &'a ItemIdSet,
    pub(crate) cache: &'a Cache,
    pub(crate) is_json_output: bool,
    pub(crate) document_private: bool,
    pub(crate) document_hidden: bool,
}

impl<'a> ImplStripper<'a, '_> {
    #[inline]
    fn should_keep_impl(&self, item: &Item, for_def_id: DefId) -> bool {
        if !for_def_id.is_local() || self.retained.contains(&for_def_id.into()) {
            true
        } else if self.is_json_output {
            // If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we
            // need to keep it.
            self.cache.effective_visibilities.is_exported(self.tcx, for_def_id)
                && (self.document_hidden
                    || ((!item.is_doc_hidden()
                        && for_def_id
                            .as_local()
                            .map(|def_id| !inherits_doc_hidden(self.tcx, def_id, None))
                            .unwrap_or(true))
                        || self.cache.inlined_items.contains(&for_def_id)))
        } else {
            false
        }
    }
}

impl<'a> DocFolder for ImplStripper<'a, '_> {
    fn fold_item(&mut self, i: Item) -> Option<Item> {
        if let clean::ImplItem(ref imp) = *i.kind {
            // Impl blocks can be skipped if they are: empty; not a trait impl; and have no
            // documentation.
            //
            // There is one special case: if the impl block contains only private items.
            if imp.trait_.is_none() {
                // If the only items present are private ones and we're not rendering private items,
                // we don't document it.
                if !imp.items.is_empty()
                    && !self.document_private
                    && imp.items.iter().all(|i| {
                        let item_id = i.item_id;
                        item_id.is_local()
                            && !self
                                .cache
                                .effective_visibilities
                                .is_reachable(self.tcx, item_id.expect_def_id())
                    })
                {
                    debug!("ImplStripper: no public item; removing {imp:?}");
                    return None;
                } else if imp.items.is_empty() && i.doc_value().is_empty() {
                    debug!("ImplStripper: no item and no doc; removing {imp:?}");
                    return None;
                }
            }
            // Because we don't inline in `maybe_inline_local` if the output format is JSON,
            // we need to make a special check for JSON output: we want to keep it unless it has
            // a `#[doc(hidden)]` attribute if the `for_` type is exported.
            if let Some(did) = imp.for_.def_id(self.cache)
                && !imp.for_.is_assoc_ty()
                && !self.should_keep_impl(&i, did)
            {
                debug!("ImplStripper: impl item for stripped type; removing {imp:?}");
                return None;
            }
            if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id())
                && !self.should_keep_impl(&i, did)
            {
                debug!("ImplStripper: impl item for stripped trait; removing {imp:?}");
                return None;
            }
            if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) {
                for typaram in generics {
                    if let Some(did) = typaram.def_id(self.cache)
                        && !self.should_keep_impl(&i, did)
                    {
                        debug!("ImplStripper: stripped item in trait's generics; removing {imp:?}");
                        return None;
                    }
                }
            }
        }
        Some(self.fold_item_recur(i))
    }
}

/// This stripper discards all private import statements (`use`, `extern crate`)
pub(crate) struct ImportStripper<'tcx> {
    pub(crate) tcx: TyCtxt<'tcx>,
    pub(crate) is_json_output: bool,
    pub(crate) document_hidden: bool,
}

impl<'tcx> ImportStripper<'tcx> {
    fn import_should_be_hidden(&self, i: &Item, imp: &clean::Import) -> bool {
        if self.is_json_output {
            // FIXME: This should be handled the same way as for HTML output.
            imp.imported_item_is_doc_hidden(self.tcx)
        } else {
            i.is_doc_hidden()
        }
    }
}

impl<'tcx> DocFolder for ImportStripper<'tcx> {
    fn fold_item(&mut self, i: Item) -> Option<Item> {
        match *i.kind {
            clean::ImportItem(imp)
                if !self.document_hidden && self.import_should_be_hidden(&i, &imp) =>
            {
                None
            }
            // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None,
            clean::ExternCrateItem { .. } | clean::ImportItem(..)
                if i.visibility(self.tcx) != Some(Visibility::Public) =>
            {
                None
            }
            _ => Some(self.fold_item_recur(i)),
        }
    }
}