1use crate::{
2 Navigator, RustdocData, navigator::parse_docsrs_url, rustdoc_data::kind_discriminator,
3};
4use fieldwork::Fieldwork;
5use rustdoc_types::{
6 ExternalCrate, Id, Item, ItemEnum, ItemKind, ItemSummary, MacroKind, ProcMacro, Use,
7};
8
9#[derive(Copy, Clone, Debug)]
16pub(crate) struct ParentRef<'a> {
17 pub(crate) crate_docs: &'a RustdocData,
18 pub(crate) item: &'a Item,
19 pub(crate) name: Option<&'a str>,
21}
22
23impl<'a> From<DocRef<'a, Item>> for ParentRef<'a> {
24 fn from(d: DocRef<'a, Item>) -> Self {
25 ParentRef {
26 crate_docs: d.crate_docs,
27 item: d.item,
28 name: d.name,
29 }
30 }
31}
32use semver::VersionReq;
33use std::{
34 fmt::{self, Debug, Display, Formatter},
35 hash::{Hash, Hasher},
36 ops::Deref,
37};
38
39#[derive(Fieldwork)]
40#[fieldwork(get, option_set_some)]
41pub struct DocRef<'a, T> {
42 crate_docs: &'a RustdocData,
43 item: &'a T,
44 navigator: &'a Navigator,
45
46 #[field(get = false, with, set)]
47 name: Option<&'a str>,
48
49 #[field(get = false, with(vis = "pub(crate)", option_set_some, into))]
52 parent: Option<ParentRef<'a>>,
53}
54
55impl<'a, T> PartialEq for DocRef<'a, T> {
57 fn eq(&self, other: &Self) -> bool {
58 std::ptr::eq(self.item, other.item) && std::ptr::eq(self.crate_docs, other.crate_docs)
59 }
60}
61
62impl<'a, T> Eq for DocRef<'a, T> {}
63
64impl Hash for DocRef<'_, Item> {
65 fn hash<H: Hasher>(&self, state: &mut H) {
66 self.crate_docs.name().hash(state);
67 self.id.hash(state);
68 }
69}
70
71impl<'a, T> From<&DocRef<'a, T>> for &'a RustdocData {
72 fn from(value: &DocRef<'a, T>) -> Self {
73 value.crate_docs
74 }
75}
76impl<'a, T> From<DocRef<'a, T>> for &'a RustdocData {
77 fn from(value: DocRef<'a, T>) -> Self {
78 value.crate_docs
79 }
80}
81
82impl<'a, T> Deref for DocRef<'a, T> {
83 type Target = T;
84
85 fn deref(&self) -> &Self::Target {
86 self.item
87 }
88}
89
90impl<'a, T> DocRef<'a, T> {
91 pub fn build_ref<U>(&self, inner: &'a U) -> DocRef<'a, U> {
92 DocRef::new(self.navigator, self.crate_docs, inner)
93 }
94
95 pub fn get_path(&self, id: Id) -> Option<DocRef<'a, Item>> {
96 self.crate_docs.get_path(self.navigator, id)
97 }
98}
99
100impl<'a> DocRef<'a, Item> {
101 pub fn name(&self) -> Option<&'a str> {
102 self.name
103 .or(self.item.name.as_deref())
104 .or(self.summary().and_then(|x| x.path.last().map(|y| &**y)))
105 }
106
107 pub fn inner(&self) -> &'a ItemEnum {
108 &self.item.inner
109 }
110
111 pub fn path(&self) -> Option<Path<'a>> {
112 self.crate_docs().path(&self.id)
113 }
114
115 pub fn summary(&self) -> Option<&'a ItemSummary> {
116 self.crate_docs().paths.get(&self.id)
117 }
118
119 pub fn find_child(&self, child_name: &str) -> Option<DocRef<'a, Item>> {
120 self.child_items()
121 .find(|c| c.name().is_some_and(|n| n == child_name))
122 }
123
124 pub fn find_by_path<'b>(
125 &self,
126 mut iter: impl Iterator<Item = &'b String>,
127 ) -> Option<DocRef<'a, Item>> {
128 let Some(next) = iter.next() else {
129 return Some(*self);
130 };
131
132 for child in self.child_items() {
133 if let Some(name) = child.name()
134 && name == next
135 {
136 return child.find_by_path(iter);
137 }
138 }
139
140 None
141 }
142
143 pub fn discriminated_path(&self) -> Option<String> {
156 if let Some(summary) = self.summary() {
157 let path = &summary.path;
161 let tail = path.get(1..)?;
162 let disc = kind_discriminator(self.kind());
163 let crate_name = self.crate_docs().name();
164 return match tail {
165 [] => Some(format!("{crate_name}::{disc}@{}", path[0])),
166 [.., last] => {
167 let prefix = tail[..tail.len() - 1].join("::");
168 if prefix.is_empty() {
169 Some(format!("{crate_name}::{disc}@{last}"))
170 } else {
171 Some(format!("{crate_name}::{prefix}::{disc}@{last}"))
172 }
173 }
174 };
175 }
176
177 let parent_ref = self.parent?;
180 let disc = kind_discriminator(self.kind());
181 let name = self.item.name.as_deref()?;
182
183 if let Some(parent_summary) = parent_ref.crate_docs.paths.get(&parent_ref.item.id) {
187 if let Some(tail) = parent_summary.path.get(1..) {
188 let parent_key = tail.join("::");
189 if parent_ref.crate_docs.path_to_id.contains_key(&parent_key) {
190 let crate_name = parent_ref.crate_docs.name();
191 let parent_path = if parent_key.is_empty() {
192 crate_name.to_string()
193 } else {
194 format!("{crate_name}::{parent_key}")
195 };
196 return Some(format!("{parent_path}::{disc}@{name}"));
197 }
198 }
199 }
200
201 let parent = DocRef::new(self.navigator, parent_ref.crate_docs, parent_ref.item);
203 let parent = match parent_ref.name {
204 Some(n) => parent.with_name(n),
205 None => parent,
206 };
207 let parent_path = parent.discriminated_path()?;
208 Some(format!("{parent_path}::{disc}@{name}"))
209 }
210
211 pub fn kind(&self) -> ItemKind {
212 match self.item.inner {
213 ItemEnum::Module(_) => ItemKind::Module,
214 ItemEnum::ExternCrate { .. } => ItemKind::ExternCrate,
215 ItemEnum::Use(_) => ItemKind::Use,
216 ItemEnum::Union(_) => ItemKind::Union,
217 ItemEnum::Struct(_) => ItemKind::Struct,
218 ItemEnum::StructField(_) => ItemKind::StructField,
219 ItemEnum::Enum(_) => ItemKind::Enum,
220 ItemEnum::Variant(_) => ItemKind::Variant,
221 ItemEnum::Function(_) => ItemKind::Function,
222 ItemEnum::Trait(_) => ItemKind::Trait,
223 ItemEnum::TraitAlias(_) => ItemKind::TraitAlias,
224 ItemEnum::Impl(_) => ItemKind::Impl,
225 ItemEnum::TypeAlias(_) => ItemKind::TypeAlias,
226 ItemEnum::Constant { .. } => ItemKind::Constant,
227 ItemEnum::Static(_) => ItemKind::Static,
228 ItemEnum::ExternType => ItemKind::ExternType,
229 ItemEnum::ProcMacro(ProcMacro {
230 kind: MacroKind::Attr,
231 ..
232 }) => ItemKind::ProcAttribute,
233 ItemEnum::ProcMacro(ProcMacro {
234 kind: MacroKind::Derive,
235 ..
236 }) => ItemKind::ProcDerive,
237 ItemEnum::Macro(_)
238 | ItemEnum::ProcMacro(ProcMacro {
239 kind: MacroKind::Bang,
240 ..
241 }) => ItemKind::Macro,
242 ItemEnum::Primitive(_) => ItemKind::Primitive,
243 ItemEnum::AssocConst { .. } => ItemKind::AssocConst,
244 ItemEnum::AssocType { .. } => ItemKind::AssocType,
245 }
246 }
247}
248
249impl<'a, T> Clone for DocRef<'a, T> {
250 fn clone(&self) -> Self {
251 *self
252 }
253}
254
255impl<'a, T> Copy for DocRef<'a, T> {}
256
257impl<'a, T: Debug> Debug for DocRef<'a, T> {
258 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
259 f.debug_struct("DocRef")
260 .field("crate_docs", &self.crate_docs)
261 .field("item", &self.item)
262 .finish_non_exhaustive()
263 }
264}
265
266impl<'a, T> DocRef<'a, T> {
267 pub(crate) fn new(
268 navigator: &'a Navigator,
269 crate_docs: impl Into<&'a RustdocData>,
270 item: &'a T,
271 ) -> Self {
272 let crate_docs = crate_docs.into();
273 Self {
274 navigator,
275 crate_docs,
276 item,
277 name: None,
278 parent: None,
279 }
280 }
281
282 pub fn get(&self, id: &Id) -> Option<DocRef<'a, Item>> {
283 self.crate_docs.get(self.navigator, id)
284 }
285}
286
287impl<'a> DocRef<'a, Use> {
288 pub fn use_name(self) -> &'a str {
289 self.name.unwrap_or(&self.item.name)
290 }
291}
292
293impl<'a> DocRef<'a, ItemSummary> {
294 pub fn external_crate(&self) -> Option<DocRef<'a, ExternalCrate>> {
297 if self.crate_id == 0 {
298 return None;
299 }
300
301 let external = self.crate_docs().external_crates.get(&self.crate_id)?;
302 Some(self.build_ref(external))
303 }
304}
305
306impl<'a> DocRef<'a, ExternalCrate> {
307 pub fn crate_name(&self) -> &'a str {
310 if let Some(url) = &self.item.html_root_url {
311 if let Some((name, _)) = parse_docsrs_url(url) {
312 return name;
313 }
314 }
315 &self.item.name
316 }
317
318 pub fn load(&self) -> Option<&'a RustdocData> {
320 let name = self.crate_name();
321 let version_req = if let Some(url) = &self.item.html_root_url {
322 parse_docsrs_url(url)
323 .and_then(|(_, version)| VersionReq::parse(&format!("={version}")).ok())
324 .unwrap_or(VersionReq::STAR)
325 } else {
326 VersionReq::STAR
327 };
328
329 self.navigator().load_crate(name, &version_req)
330 }
331}
332
333#[derive(Debug)]
334pub struct Path<'a>(&'a [String]);
335
336impl<'a> From<&'a ItemSummary> for Path<'a> {
337 fn from(value: &'a ItemSummary) -> Self {
338 Self(&value.path)
339 }
340}
341
342impl<'a> IntoIterator for Path<'a> {
343 type Item = &'a str;
344
345 type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
346
347 fn into_iter(self) -> Self::IntoIter {
348 Box::new(self.0.iter().map(|x| &**x))
349 }
350}
351
352impl Display for Path<'_> {
353 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
354 for (i, segment) in self.0.iter().enumerate() {
355 if i > 0 {
356 f.write_str("::")?;
357 }
358 f.write_str(segment)?;
359 }
360 Ok(())
361 }
362}
363
364#[allow(dead_code)]
370const _: () = {
371 const fn assert_send<T: Send>() {}
372 const fn assert_sync<T: Sync>() {}
373
374 const fn check_doc_ref_send() {
376 assert_send::<DocRef<'_, rustdoc_types::Item>>();
377 }
378
379 const fn check_doc_ref_sync() {
381 assert_sync::<DocRef<'_, rustdoc_types::Item>>();
382 }
383};