term_rustdoc/tree/
id.rs

1#![allow(non_snake_case)]
2use super::{DModule, DocTree, Show};
3use crate::type_name::{long, long_path};
4use crate::util::{xformat, CompactStringExt, XString};
5use rustdoc_types::{Crate, Id, Item, ItemEnum, ItemKind, ItemSummary};
6use std::{borrow::Borrow, cell::RefCell, collections::HashMap};
7
8/// basic impls for ID
9mod impls;
10
11pub type IDs = Box<[ID]>;
12
13#[derive(
14    Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
15)]
16#[repr(transparent)]
17pub struct ID {
18    pub id: XString,
19}
20
21pub trait IdToID: Sized {
22    fn to_ID(&self) -> ID;
23    fn into_ID(self) -> ID;
24}
25
26impl IdToID for Id {
27    fn to_ID(&self) -> ID {
28        self.0.to_ID()
29    }
30
31    fn into_ID(self) -> ID {
32        String::into_ID(self.0)
33    }
34}
35
36impl IdToID for String {
37    fn to_ID(&self) -> ID {
38        ID {
39            id: self.as_str().into(),
40        }
41    }
42
43    fn into_ID(self) -> ID {
44        ID {
45            id: XString::from_string_buffer(self),
46        }
47    }
48}
49
50impl IdToID for &str {
51    fn to_ID(&self) -> ID {
52        ID::new(self)
53    }
54
55    fn into_ID(self) -> ID {
56        ID::new(self)
57    }
58}
59
60pub trait SliceToIds {
61    fn to_ids(&self) -> IDs;
62}
63impl<T: IdToID> SliceToIds for [T] {
64    fn to_ids(&self) -> IDs {
65        self.iter().map(|id| id.to_ID()).collect()
66    }
67}
68
69pub trait IdAsStr {
70    fn id_str(&self) -> &str;
71}
72impl IdAsStr for str {
73    fn id_str(&self) -> &str {
74        self
75    }
76}
77impl IdAsStr for ID {
78    fn id_str(&self) -> &str {
79        self
80    }
81}
82impl IdAsStr for Id {
83    fn id_str(&self) -> &str {
84        &self.0
85    }
86}
87impl<T: IdAsStr> IdAsStr for &T {
88    fn id_str(&self) -> &str {
89        T::id_str(self)
90    }
91}
92
93/// This is usually used behind a shared reference.
94/// For owned version, use [`CrateDoc`][super::CrateDoc] instead.
95#[derive(serde::Serialize, serde::Deserialize)]
96pub struct IDMap {
97    krate: Crate,
98    dmod: DModule,
99    id_buffer: RefCell<String>,
100}
101
102impl IDMap {
103    pub fn new(krate: Crate) -> IDMap {
104        let mut map = IDMap {
105            krate,
106            // placeholder for DModule: we'll construct it at once
107            dmod: DModule::default(),
108            id_buffer: RefCell::new(String::with_capacity(24)),
109        };
110        map.dmod = DModule::new(&map);
111        info!("IDMap and DModule ready");
112        map
113    }
114
115    pub fn dmodule(&self) -> &DModule {
116        &self.dmod
117    }
118
119    pub fn raw_crate_doc(&self) -> &Crate {
120        &self.krate
121    }
122}
123
124impl Default for IDMap {
125    fn default() -> Self {
126        let (crate_version, includes_private, index, paths, external_crates, format_version) =
127            Default::default();
128        IDMap {
129            krate: Crate {
130                root: rustdoc_types::Id(String::new()),
131                crate_version,
132                includes_private,
133                index,
134                paths,
135                external_crates,
136                format_version,
137            },
138            dmod: DModule::default(),
139            id_buffer: RefCell::default(),
140        }
141    }
142}
143
144// Crate.index
145pub type IndexMap = HashMap<Id, Item>;
146// Crate.paths
147pub type PathMap = HashMap<Id, ItemSummary>;
148
149impl IDMap {
150    /// Use  in a buffered way in hot querys.
151    pub fn use_id<T>(&self, id: &str, f: impl FnOnce(&Id) -> T) -> T {
152        // idbuf always serves as Id used in a query
153        let mut buf = self.id_buffer.take();
154        buf.clear();
155        buf.push_str(id);
156
157        let id = Id(buf);
158        let val = f(&id);
159
160        // put the buffer back to use next time
161        self.id_buffer.replace(id.0);
162
163        val
164    }
165
166    pub fn indexmap(&self) -> &IndexMap {
167        &self.krate.index
168    }
169
170    pub fn pathmap(&self) -> &PathMap {
171        &self.krate.paths
172    }
173}
174
175/// DModule related.
176impl IDMap {
177    /// FIXME: show_prettier should be renamed
178    pub fn dmodule_show_prettier(&self) -> DocTree {
179        self.dmod.show_prettier(self)
180    }
181
182    /// This is the default tree view for most cases.
183    pub fn dmodule_item_tree(&self) -> DocTree {
184        self.dmod.item_tree(self)
185    }
186}
187
188// Documentation on an item.
189impl IDMap {
190    pub fn get_doc(&self, id: &str) -> Option<&str> {
191        self.get_item(id).and_then(|item| match &item.inner {
192            ItemEnum::Import(item) => {
193                if let Some(inner_id) = item.id.as_ref() {
194                    if let Some(reexport_item) = self.get_item(&inner_id.0) {
195                        if matches!(reexport_item.inner, ItemEnum::Import(_)) {
196                            error!(
197                                "Reexport item with Id({id}) shouldn't \
198                                 recursively contains another Import.\n{item:?} "
199                            );
200                        } else {
201                            return reexport_item.docs.as_deref();
202                        }
203                    }
204                }
205                None
206            }
207            _ => item.docs.as_deref(),
208        })
209    }
210}
211
212/// Get the shortest item name only based on IndexMap.
213impl IDMap {
214    pub fn get_item(&self, id: &str) -> Option<&Item> {
215        self.use_id(id, |id| self.indexmap().get(id))
216    }
217
218    // fn use_item(&self, id: &Id, f: impl FnOnce(&Item) -> XString) -> Option<XString> {
219    //     self.get_item(&id.0).map(f)
220    // }
221    //
222
223    // /// If the id doesn't refer to an Item, emit a warn and use the id as the result.
224    // fn use_item_well(&self, id: &str, f: impl FnOnce(&Item) -> XString) -> XString {
225    //     match self.get_item(id).map(f) {
226    //         Some(s) => s,
227    //         None => {
228    //             warn!("Id({id}) doesn't refer to an Item in IndexMap");
229    //             XString::from(id)
230    //         }
231    //     }
232    // }
233
234    /// * If the id refers to an Item with a name, use the name;
235    ///     * if name is None, try getting the name depending on item type (reexported local items
236    ///       may hit this);
237    /// * If id isn't in IndexMap, try searching the PathMap for last path component (reexported
238    ///   external items may hit this);
239    /// * otherwise id.
240    pub fn name<S>(&self, id: &S) -> XString
241    where
242        S: ?Sized + IdAsStr,
243    {
244        let id = id.id_str();
245        if let Some(item) = self.get_item(id) {
246            let name = item.name.as_deref().map(XString::from);
247            name.unwrap_or_else(|| item_name(item).unwrap_or_else(|| id.into()))
248        } else if let Some(path) = self.get_path(id) {
249            path.path.last().map(|p| p.as_str()).unwrap_or(id).into()
250        } else {
251            id.into()
252        }
253    }
254}
255
256/// Deduce the name from its item type.
257fn item_name(item: &Item) -> Option<XString> {
258    match &item.inner {
259        ItemEnum::Impl(item) => {
260            let implementor = item
261                .blanket_impl
262                .as_ref()
263                .map_or_else(|| long(&item.for_), long);
264            if let Some(trait_) = item.trait_.as_ref().map(long_path) {
265                Some(xformat!("{implementor}: {trait_}"))
266            } else {
267                Some(xformat!("{implementor}"))
268            }
269        }
270        ItemEnum::Import(item) => Some(item.name.as_str().into()),
271        _ => None,
272    }
273}
274
275/// Get the external item path only based on PathMap.
276impl IDMap {
277    pub fn get_path(&self, id: &str) -> Option<&ItemSummary> {
278        self.use_id(id, |id| self.pathmap().get(id))
279    }
280
281    // fn use_path(&self, id: &Id, f: impl FnOnce(&ItemSummary) -> XString) -> Option<XString> {
282    //     self.get_path(&id.0).map(f)
283    // }
284
285    /// If the id doesn't refer to an ItemSummary, emit a warn and use the id as the result.
286    fn use_path_well(&self, id: &str, f: impl FnOnce(&ItemSummary) -> XString) -> XString {
287        match self.get_path(id).map(f) {
288            Some(s) => s,
289            None => {
290                warn!("Id({id}) doesn't refer to an Item in IndexMap");
291                XString::from(id)
292            }
293        }
294    }
295
296    /// Like `path`, but with strict item kind checking.
297    /// If the id doesn't refer to an ItemSummary with exact given kind, emit a warn.
298    pub fn path_with_kind_check<S, K>(&self, id: &S, kind: K) -> XString
299    where
300        S: ?Sized + IdAsStr,
301        K: Borrow<ItemKind>,
302    {
303        let id = id.id_str();
304        self.use_path_well(id, move |item| {
305            let kind = kind.borrow();
306            if &item.kind != kind {
307                warn!(
308                    "Id({id}) in PathMap is found as {:?}, but {kind:?} is required",
309                    item.kind
310                );
311            }
312            item.path.join_compact("::")
313        })
314    }
315
316    /// Returns the full path if it exists, or name if it exists or id if neither exists.
317    pub fn path(&self, id: &str) -> XString {
318        self.get_path(id)
319            .map(|item| item.path.join_compact("::"))
320            .unwrap_or_else(|| self.name(id))
321    }
322
323    /// Like `path`, but returns the choice for name/id fallback an Err variant.
324    pub fn path_or_name(&self, id: &str) -> Result<XString, XString> {
325        self.get_path(id)
326            .map(|item| item.path.join_compact("::"))
327            .ok_or_else(|| self.name(id))
328    }
329}