mod.rs - source (original) (raw)

rustdoc/json/

mod.rs

1//! Rustdoc's JSON backend
2//!
3//! This module contains the logic for rendering a crate as JSON rather than the normal static HTML
4//! output. See [the RFC](https://github.com/rust-lang/rfcs/pull/2963) and the [`types`] module
5//! docs for usage and details.
6
7mod conversions;
8mod ids;
9mod import_finder;
10
11use std::cell::RefCell;
12use std::fs::{File, create_dir_all};
13use std::io::{BufWriter, Write, stdout};
14use std::path::PathBuf;
15use std::rc::Rc;
16
17use rustc_data_structures::fx::FxHashSet;
18use rustc_hir::def_id::{DefId, DefIdSet};
19use rustc_middle::ty::TyCtxt;
20use rustc_session::Session;
21use rustc_span::def_id::LOCAL_CRATE;
22use rustdoc_json_types as types;
23// It's important to use the FxHashMap from rustdoc_json_types here, instead of
24// the one from rustc_data_structures, as they're different types due to sysroots.
25// See #110051 and #127456 for details
26use rustdoc_json_types::FxHashMap;
27use tracing::{debug, trace};
28
29use crate::clean::ItemKind;
30use crate::clean::types::{ExternalCrate, ExternalLocation};
31use crate::config::RenderOptions;
32use crate::docfs::PathError;
33use crate::error::Error;
34use crate::formats::FormatRenderer;
35use crate::formats::cache::Cache;
36use crate::json::conversions::IntoJson;
37use crate::{clean, try_err};
38
39#[derive(Clone)]
40pub(crate) struct JsonRenderer<'tcx> {
41    tcx: TyCtxt<'tcx>,
42    /// A mapping of IDs that contains all local items for this crate which gets output as a top
43    /// level field of the JSON blob.
44    index: Rc<RefCell<FxHashMap<types::Id, types::Item>>>,
45    /// The directory where the JSON blob should be written to.
46    ///
47    /// If this is `None`, the blob will be printed to `stdout` instead.
48    out_dir: Option<PathBuf>,
49    cache: Rc<Cache>,
50    imported_items: DefIdSet,
51    id_interner: Rc<RefCell<ids::IdInterner>>,
52}
53
54impl<'tcx> JsonRenderer<'tcx> {
55    fn sess(&self) -> &'tcx Session {
56        self.tcx.sess
57    }
58
59    fn get_trait_implementors(&mut self, id: DefId) -> Vec<types::Id> {
60        Rc::clone(&self.cache)
61            .implementors
62            .get(&id)
63            .map(|implementors| {
64                implementors
65                    .iter()
66                    .map(|i| {
67                        let item = &i.impl_item;
68                        self.item(item.clone()).unwrap();
69                        self.id_from_item(item)
70                    })
71                    .collect()
72            })
73            .unwrap_or_default()
74    }
75
76    fn get_impls(&mut self, id: DefId) -> Vec<types::Id> {
77        Rc::clone(&self.cache)
78            .impls
79            .get(&id)
80            .map(|impls| {
81                impls
82                    .iter()
83                    .filter_map(|i| {
84                        let item = &i.impl_item;
85
86                        // HACK(hkmatsumoto): For impls of primitive types, we index them
87                        // regardless of whether they're local. This is because users can
88                        // document primitive items in an arbitrary crate by using
89                        // `rustc_doc_primitive`.
90                        let mut is_primitive_impl = false;
91                        if let clean::types::ItemKind::ImplItem(ref impl_) = item.kind
92                            && impl_.trait_.is_none()
93                            && let clean::types::Type::Primitive(_) = impl_.for_
94                        {
95                            is_primitive_impl = true;
96                        }
97
98                        if item.item_id.is_local() || is_primitive_impl {
99                            self.item(item.clone()).unwrap();
100                            Some(self.id_from_item(item))
101                        } else {
102                            None
103                        }
104                    })
105                    .collect()
106            })
107            .unwrap_or_default()
108    }
109
110    fn serialize_and_write<T: Write>(
111        &self,
112        output_crate: types::Crate,
113        mut writer: BufWriter<T>,
114        path: &str,
115    ) -> Result<(), Error> {
116        self.sess().time("rustdoc_json_serialize_and_write", || {
117            try_err!(
118                serde_json::ser::to_writer(&mut writer, &output_crate).map_err(|e| e.to_string()),
119                path
120            );
121            try_err!(writer.flush(), path);
122            Ok(())
123        })
124    }
125}
126
127fn target(sess: &rustc_session::Session) -> types::Target {
128    // Build a set of which features are enabled on this target
129    let globally_enabled_features: FxHashSet<&str> =
130        sess.unstable_target_features.iter().map(|name| name.as_str()).collect();
131
132    // Build a map of target feature stability by feature name
133    use rustc_target::target_features::Stability;
134    let feature_stability: FxHashMap<&str, Stability> = sess
135        .target
136        .rust_target_features()
137        .into_iter()
138        .copied()
139        .map(|(name, stability, _)| (name, stability))
140        .collect();
141
142    types::Target {
143        triple: sess.opts.target_triple.tuple().into(),
144        target_features: sess
145            .target
146            .rust_target_features()
147            .into_iter()
148            .copied()
149            .filter(|(_, stability, _)| {
150                // Describe only target features which the user can toggle
151                stability.toggle_allowed().is_ok()
152            })
153            .map(|(name, stability, implied_features)| {
154                types::TargetFeature {
155                    name: name.into(),
156                    unstable_feature_gate: match stability {
157                        Stability::Unstable(feature_gate) => Some(feature_gate.as_str().into()),
158                        _ => None,
159                    },
160                    implies_features: implied_features
161                        .into_iter()
162                        .copied()
163                        .filter(|name| {
164                            // Imply only target features which the user can toggle
165                            feature_stability
166                                .get(name)
167                                .map(|stability| stability.toggle_allowed().is_ok())
168                                .unwrap_or(false)
169                        })
170                        .map(String::from)
171                        .collect(),
172                    globally_enabled: globally_enabled_features.contains(name),
173                }
174            })
175            .collect(),
176    }
177}
178
179impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
180    fn descr() -> &'static str {
181        "json"
182    }
183
184    const RUN_ON_MODULE: bool = false;
185    type ModuleData = ();
186
187    fn init(
188        krate: clean::Crate,
189        options: RenderOptions,
190        cache: Cache,
191        tcx: TyCtxt<'tcx>,
192    ) -> Result<(Self, clean::Crate), Error> {
193        debug!("Initializing json renderer");
194
195        let (krate, imported_items) = import_finder::get_imports(krate);
196
197        Ok((
198            JsonRenderer {
199                tcx,
200                index: Rc::new(RefCell::new(FxHashMap::default())),
201                out_dir: if options.output_to_stdout { None } else { Some(options.output) },
202                cache: Rc::new(cache),
203                imported_items,
204                id_interner: Default::default(),
205            },
206            krate,
207        ))
208    }
209
210    fn save_module_data(&mut self) -> Self::ModuleData {
211        unreachable!("RUN_ON_MODULE = false, should never call save_module_data")
212    }
213    fn restore_module_data(&mut self, _info: Self::ModuleData) {
214        unreachable!("RUN_ON_MODULE = false, should never call set_back_info")
215    }
216
217    /// Inserts an item into the index. This should be used rather than directly calling insert on
218    /// the hashmap because certain items (traits and types) need to have their mappings for trait
219    /// implementations filled out before they're inserted.
220    fn item(&mut self, item: clean::Item) -> Result<(), Error> {
221        let item_type = item.type_();
222        let item_name = item.name;
223        trace!("rendering {item_type} {item_name:?}");
224
225        // Flatten items that recursively store other items. We include orphaned items from
226        // stripped modules and etc that are otherwise reachable.
227        if let ItemKind::StrippedItem(inner) = &item.kind {
228            inner.inner_items().for_each(|i| self.item(i.clone()).unwrap());
229        }
230
231        // Flatten items that recursively store other items
232        item.kind.inner_items().for_each(|i| self.item(i.clone()).unwrap());
233
234        let item_id = item.item_id;
235        if let Some(mut new_item) = self.convert_item(item) {
236            let can_be_ignored = match new_item.inner {
237                types::ItemEnum::Trait(ref mut t) => {
238                    t.implementations = self.get_trait_implementors(item_id.expect_def_id());
239                    false
240                }
241                types::ItemEnum::Struct(ref mut s) => {
242                    s.impls = self.get_impls(item_id.expect_def_id());
243                    false
244                }
245                types::ItemEnum::Enum(ref mut e) => {
246                    e.impls = self.get_impls(item_id.expect_def_id());
247                    false
248                }
249                types::ItemEnum::Union(ref mut u) => {
250                    u.impls = self.get_impls(item_id.expect_def_id());
251                    false
252                }
253                types::ItemEnum::Primitive(ref mut p) => {
254                    p.impls = self.get_impls(item_id.expect_def_id());
255                    false
256                }
257
258                types::ItemEnum::Function(_)
259                | types::ItemEnum::Module(_)
260                | types::ItemEnum::Use(_)
261                | types::ItemEnum::AssocConst { .. }
262                | types::ItemEnum::AssocType { .. } => true,
263                types::ItemEnum::ExternCrate { .. }
264                | types::ItemEnum::StructField(_)
265                | types::ItemEnum::Variant(_)
266                | types::ItemEnum::TraitAlias(_)
267                | types::ItemEnum::Impl(_)
268                | types::ItemEnum::TypeAlias(_)
269                | types::ItemEnum::Constant { .. }
270                | types::ItemEnum::Static(_)
271                | types::ItemEnum::ExternType
272                | types::ItemEnum::Macro(_)
273                | types::ItemEnum::ProcMacro(_) => false,
274            };
275            let removed = self.index.borrow_mut().insert(new_item.id, new_item.clone());
276
277            // FIXME(adotinthevoid): Currently, the index is duplicated. This is a sanity check
278            // to make sure the items are unique. The main place this happens is when an item, is
279            // reexported in more than one place. See `rustdoc-json/reexport/in_root_and_mod`
280            if let Some(old_item) = removed {
281                // In case of generic implementations (like `impl<T> Trait for T {}`), all the
282                // inner items will be duplicated so we can ignore if they are slightly different.
283                if !can_be_ignored {
284                    assert_eq!(old_item, new_item);
285                }
286                trace!("replaced {old_item:?}\nwith {new_item:?}");
287            }
288        }
289
290        trace!("done rendering {item_type} {item_name:?}");
291        Ok(())
292    }
293
294    fn mod_item_in(&mut self, _item: &clean::Item) -> Result<(), Error> {
295        unreachable!("RUN_ON_MODULE = false, should never call mod_item_in")
296    }
297
298    fn after_krate(&mut self) -> Result<(), Error> {
299        debug!("Done with crate");
300
301        let e = ExternalCrate { crate_num: LOCAL_CRATE };
302        let index = (*self.index).clone().into_inner();
303
304        // Note that tcx.rust_target_features is inappropriate here because rustdoc tries to run for
305        // multiple targets: https://github.com/rust-lang/rust/pull/137632
306        //
307        // We want to describe a single target, so pass tcx.sess rather than tcx.
308        let target = target(self.tcx.sess);
309
310        debug!("Constructing Output");
311        let output_crate = types::Crate {
312            root: self.id_from_item_default(e.def_id().into()),
313            crate_version: self.cache.crate_version.clone(),
314            includes_private: self.cache.document_private,
315            index,
316            paths: self
317                .cache
318                .paths
319                .iter()
320                .chain(&self.cache.external_paths)
321                .map(|(&k, &(ref path, kind))| {
322                    (
323                        self.id_from_item_default(k.into()),
324                        types::ItemSummary {
325                            crate_id: k.krate.as_u32(),
326                            path: path.iter().map(|s| s.to_string()).collect(),
327                            kind: kind.into_json(self),
328                        },
329                    )
330                })
331                .collect(),
332            external_crates: self
333                .cache
334                .extern_locations
335                .iter()
336                .map(|(crate_num, external_location)| {
337                    let e = ExternalCrate { crate_num: *crate_num };
338                    (
339                        crate_num.as_u32(),
340                        types::ExternalCrate {
341                            name: e.name(self.tcx).to_string(),
342                            html_root_url: match external_location {
343                                ExternalLocation::Remote(s) => Some(s.clone()),
344                                _ => None,
345                            },
346                        },
347                    )
348                })
349                .collect(),
350            target,
351            format_version: types::FORMAT_VERSION,
352        };
353        if let Some(ref out_dir) = self.out_dir {
354            try_err!(create_dir_all(out_dir), out_dir);
355
356            let mut p = out_dir.clone();
357            p.push(output_crate.index.get(&output_crate.root).unwrap().name.clone().unwrap());
358            p.set_extension("json");
359
360            self.serialize_and_write(
361                output_crate,
362                try_err!(File::create_buffered(&p), p),
363                &p.display().to_string(),
364            )
365        } else {
366            self.serialize_and_write(output_crate, BufWriter::new(stdout().lock()), "<stdout>")
367        }
368    }
369
370    fn cache(&self) -> &Cache {
371        &self.cache
372    }
373}