]>
Commit | Line | Data |
---|---|---|
29967ef6 XL |
1 | //! This module implements [RFC 1946]: Intra-rustdoc-links |
2 | //! | |
3 | //! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md | |
4 | ||
3dfed10e XL |
5 | use rustc_ast as ast; |
6 | use rustc_data_structures::stable_set::FxHashSet; | |
7 | use rustc_errors::{Applicability, DiagnosticBuilder}; | |
dfeec247 | 8 | use rustc_expand::base::SyntaxExtensionKind; |
dfeec247 XL |
9 | use rustc_hir as hir; |
10 | use rustc_hir::def::{ | |
60c5eb7d XL |
11 | DefKind, |
12 | Namespace::{self, *}, | |
13 | PerNS, Res, | |
14 | }; | |
29967ef6 | 15 | use rustc_hir::def_id::{CrateNum, DefId}; |
ba9703b0 | 16 | use rustc_middle::ty; |
e1599b0c | 17 | use rustc_resolve::ParentScope; |
1b1a35ee XL |
18 | use rustc_session::lint::{ |
19 | builtin::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}, | |
20 | Lint, | |
21 | }; | |
f035d41b | 22 | use rustc_span::hygiene::MacroKind; |
29967ef6 | 23 | use rustc_span::symbol::sym; |
f9f354fc | 24 | use rustc_span::symbol::Ident; |
dfeec247 XL |
25 | use rustc_span::symbol::Symbol; |
26 | use rustc_span::DUMMY_SP; | |
1b1a35ee | 27 | use smallvec::{smallvec, SmallVec}; |
b7449926 | 28 | |
1b1a35ee | 29 | use std::borrow::Cow; |
3dfed10e | 30 | use std::cell::Cell; |
29967ef6 | 31 | use std::mem; |
b7449926 XL |
32 | use std::ops::Range; |
33 | ||
29967ef6 | 34 | use crate::clean::{self, Crate, GetDefId, Import, Item, ItemLink, PrimitiveType}; |
9fa01778 XL |
35 | use crate::core::DocContext; |
36 | use crate::fold::DocFolder; | |
37 | use crate::html::markdown::markdown_links; | |
3dfed10e | 38 | use crate::passes::Pass; |
0bf4aa26 | 39 | |
9fa01778 | 40 | use super::span_of_attrs; |
b7449926 | 41 | |
532ac7d7 XL |
42 | pub const COLLECT_INTRA_DOC_LINKS: Pass = Pass { |
43 | name: "collect-intra-doc-links", | |
60c5eb7d | 44 | run: collect_intra_doc_links, |
532ac7d7 XL |
45 | description: "reads a crate's documentation to resolve intra-doc-links", |
46 | }; | |
b7449926 | 47 | |
532ac7d7 | 48 | pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate { |
29967ef6 | 49 | LinkCollector::new(cx).fold_crate(krate) |
1b1a35ee | 50 | } |
b7449926 | 51 | |
29967ef6 | 52 | /// Top-level errors emitted by this pass. |
1b1a35ee XL |
53 | enum ErrorKind<'a> { |
54 | Resolve(Box<ResolutionFailure<'a>>), | |
55 | AnchorFailure(AnchorFailure), | |
56 | } | |
57 | ||
58 | impl<'a> From<ResolutionFailure<'a>> for ErrorKind<'a> { | |
59 | fn from(err: ResolutionFailure<'a>) -> Self { | |
60 | ErrorKind::Resolve(box err) | |
b7449926 XL |
61 | } |
62 | } | |
63 | ||
1b1a35ee | 64 | #[derive(Debug)] |
29967ef6 | 65 | /// A link failed to resolve. |
1b1a35ee XL |
66 | enum ResolutionFailure<'a> { |
67 | /// This resolved, but with the wrong namespace. | |
29967ef6 XL |
68 | /// |
69 | /// `Namespace` is the namespace specified with a disambiguator | |
70 | /// (as opposed to the actual namespace of the `Res`). | |
71 | WrongNamespace(Res, /* disambiguated */ Namespace), | |
1b1a35ee XL |
72 | /// The link failed to resolve. `resolution_failure` should look to see if there's |
73 | /// a more helpful error that can be given. | |
29967ef6 XL |
74 | NotResolved { |
75 | /// The scope the link was resolved in. | |
76 | module_id: DefId, | |
77 | /// If part of the link resolved, this has the `Res`. | |
78 | /// | |
79 | /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution. | |
80 | partial_res: Option<Res>, | |
81 | /// The remaining unresolved path segments. | |
82 | /// | |
83 | /// In `[std::io::Error::x]`, `x` would be unresolved. | |
84 | unresolved: Cow<'a, str>, | |
85 | }, | |
86 | /// This happens when rustdoc can't determine the parent scope for an item. | |
87 | /// | |
88 | /// It is always a bug in rustdoc. | |
1b1a35ee | 89 | NoParentItem, |
29967ef6 XL |
90 | /// This link has malformed generic parameters; e.g., the angle brackets are unbalanced. |
91 | MalformedGenerics(MalformedGenerics), | |
92 | /// Used to communicate that this should be ignored, but shouldn't be reported to the user | |
93 | /// | |
94 | /// This happens when there is no disambiguator and one of the namespaces | |
95 | /// failed to resolve. | |
1b1a35ee XL |
96 | Dummy, |
97 | } | |
98 | ||
29967ef6 XL |
99 | #[derive(Debug)] |
100 | enum MalformedGenerics { | |
101 | /// This link has unbalanced angle brackets. | |
102 | /// | |
103 | /// For example, `Vec<T` should trigger this, as should `Vec<T>>`. | |
104 | UnbalancedAngleBrackets, | |
105 | /// The generics are not attached to a type. | |
106 | /// | |
107 | /// For example, `<T>` should trigger this. | |
108 | /// | |
109 | /// This is detected by checking if the path is empty after the generics are stripped. | |
110 | MissingType, | |
111 | /// The link uses fully-qualified syntax, which is currently unsupported. | |
112 | /// | |
113 | /// For example, `<Vec as IntoIterator>::into_iter` should trigger this. | |
114 | /// | |
115 | /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside | |
116 | /// angle brackets. | |
117 | HasFullyQualifiedSyntax, | |
118 | /// The link has an invalid path separator. | |
119 | /// | |
120 | /// For example, `Vec:<T>:new()` should trigger this. Note that `Vec:new()` will **not** | |
121 | /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be | |
122 | /// called. | |
123 | /// | |
124 | /// Note that this will also **not** be triggered if the invalid path separator is inside angle | |
125 | /// brackets because rustdoc mostly ignores what's inside angle brackets (except for | |
126 | /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)). | |
127 | /// | |
128 | /// This is detected by checking if there is a colon followed by a non-colon in the link. | |
129 | InvalidPathSeparator, | |
130 | /// The link has too many angle brackets. | |
131 | /// | |
132 | /// For example, `Vec<<T>>` should trigger this. | |
133 | TooManyAngleBrackets, | |
134 | /// The link has empty angle brackets. | |
135 | /// | |
136 | /// For example, `Vec<>` should trigger this. | |
137 | EmptyAngleBrackets, | |
138 | } | |
139 | ||
1b1a35ee | 140 | impl ResolutionFailure<'a> { |
29967ef6 XL |
141 | /// This resolved fully (not just partially) but is erroneous for some other reason |
142 | /// | |
143 | /// Returns the full resolution of the link, if present. | |
1b1a35ee XL |
144 | fn full_res(&self) -> Option<Res> { |
145 | match self { | |
146 | Self::WrongNamespace(res, _) => Some(*res), | |
147 | _ => None, | |
148 | } | |
149 | } | |
3dfed10e XL |
150 | } |
151 | ||
152 | enum AnchorFailure { | |
29967ef6 | 153 | /// User error: `[std#x#y]` is not valid |
3dfed10e | 154 | MultipleAnchors, |
29967ef6 XL |
155 | /// The anchor provided by the user conflicts with Rustdoc's generated anchor. |
156 | /// | |
157 | /// This is an unfortunate state of affairs. Not every item that can be | |
158 | /// linked to has its own page; sometimes it is a subheading within a page, | |
159 | /// like for associated items. In those cases, rustdoc uses an anchor to | |
160 | /// link to the subheading. Since you can't have two anchors for the same | |
161 | /// link, Rustdoc disallows having a user-specified anchor. | |
162 | /// | |
163 | /// Most of the time this is fine, because you can just link to the page of | |
164 | /// the item if you want to provide your own anchor. For primitives, though, | |
165 | /// rustdoc uses the anchor as a side channel to know which page to link to; | |
166 | /// it doesn't show up in the generated link. Ideally, rustdoc would remove | |
167 | /// this limitation, allowing you to link to subheaders on primitives. | |
1b1a35ee | 168 | RustdocAnchorConflict(Res), |
60c5eb7d XL |
169 | } |
170 | ||
532ac7d7 XL |
171 | struct LinkCollector<'a, 'tcx> { |
172 | cx: &'a DocContext<'tcx>, | |
29967ef6 XL |
173 | /// A stack of modules used to decide what scope to resolve in. |
174 | /// | |
175 | /// The last module will be used if the parent scope of the current item is | |
176 | /// unknown. | |
3dfed10e XL |
177 | mod_ids: Vec<DefId>, |
178 | /// This is used to store the kind of associated items, | |
179 | /// because `clean` and the disambiguator code expect them to be different. | |
180 | /// See the code for associated items on inherent impls for details. | |
1b1a35ee | 181 | kind_side_channel: Cell<Option<(DefKind, DefId)>>, |
b7449926 XL |
182 | } |
183 | ||
532ac7d7 XL |
184 | impl<'a, 'tcx> LinkCollector<'a, 'tcx> { |
185 | fn new(cx: &'a DocContext<'tcx>) -> Self { | |
3dfed10e | 186 | LinkCollector { cx, mod_ids: Vec::new(), kind_side_channel: Cell::new(None) } |
60c5eb7d XL |
187 | } |
188 | ||
29967ef6 XL |
189 | /// Given a full link, parse it as an [enum struct variant]. |
190 | /// | |
191 | /// In particular, this will return an error whenever there aren't three | |
192 | /// full path segments left in the link. | |
193 | /// | |
194 | /// [enum struct variant]: hir::VariantData::Struct | |
60c5eb7d XL |
195 | fn variant_field( |
196 | &self, | |
1b1a35ee | 197 | path_str: &'path str, |
60c5eb7d | 198 | current_item: &Option<String>, |
3dfed10e | 199 | module_id: DefId, |
1b1a35ee | 200 | ) -> Result<(Res, Option<String>), ErrorKind<'path>> { |
60c5eb7d | 201 | let cx = self.cx; |
1b1a35ee XL |
202 | let no_res = || ResolutionFailure::NotResolved { |
203 | module_id, | |
204 | partial_res: None, | |
205 | unresolved: path_str.into(), | |
206 | }; | |
60c5eb7d | 207 | |
1b1a35ee | 208 | debug!("looking for enum variant {}", path_str); |
60c5eb7d | 209 | let mut split = path_str.rsplitn(3, "::"); |
1b1a35ee XL |
210 | let (variant_field_str, variant_field_name) = split |
211 | .next() | |
212 | .map(|f| (f, Symbol::intern(f))) | |
213 | .expect("fold_item should ensure link is non-empty"); | |
214 | let (variant_str, variant_name) = | |
215 | // we're not sure this is a variant at all, so use the full string | |
216 | // If there's no second component, the link looks like `[path]`. | |
217 | // So there's no partial res and we should say the whole link failed to resolve. | |
218 | split.next().map(|f| (f, Symbol::intern(f))).ok_or_else(no_res)?; | |
60c5eb7d XL |
219 | let path = split |
220 | .next() | |
221 | .map(|f| { | |
222 | if f == "self" || f == "Self" { | |
223 | if let Some(name) = current_item.as_ref() { | |
224 | return name.clone(); | |
225 | } | |
226 | } | |
227 | f.to_owned() | |
228 | }) | |
1b1a35ee XL |
229 | // If there's no third component, we saw `[a::b]` before and it failed to resolve. |
230 | // So there's no partial res. | |
231 | .ok_or_else(no_res)?; | |
232 | let ty_res = cx | |
60c5eb7d XL |
233 | .enter_resolver(|resolver| { |
234 | resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id) | |
235 | }) | |
1b1a35ee XL |
236 | .map(|(_, res)| res) |
237 | .unwrap_or(Res::Err); | |
60c5eb7d | 238 | if let Res::Err = ty_res { |
1b1a35ee | 239 | return Err(no_res().into()); |
60c5eb7d XL |
240 | } |
241 | let ty_res = ty_res.map_id(|_| panic!("unexpected node_id")); | |
242 | match ty_res { | |
243 | Res::Def(DefKind::Enum, did) => { | |
244 | if cx | |
245 | .tcx | |
246 | .inherent_impls(did) | |
247 | .iter() | |
74b04a01 | 248 | .flat_map(|imp| cx.tcx.associated_items(*imp).in_definition_order()) |
60c5eb7d XL |
249 | .any(|item| item.ident.name == variant_name) |
250 | { | |
1b1a35ee XL |
251 | // This is just to let `fold_item` know that this shouldn't be considered; |
252 | // it's a bug for the error to make it to the user | |
253 | return Err(ResolutionFailure::Dummy.into()); | |
60c5eb7d | 254 | } |
1b1a35ee | 255 | match cx.tcx.type_of(did).kind() { |
60c5eb7d XL |
256 | ty::Adt(def, _) if def.is_enum() => { |
257 | if def.all_fields().any(|item| item.ident.name == variant_field_name) { | |
258 | Ok(( | |
259 | ty_res, | |
260 | Some(format!( | |
261 | "variant.{}.field.{}", | |
1b1a35ee | 262 | variant_str, variant_field_name |
60c5eb7d XL |
263 | )), |
264 | )) | |
265 | } else { | |
1b1a35ee XL |
266 | Err(ResolutionFailure::NotResolved { |
267 | module_id, | |
268 | partial_res: Some(Res::Def(DefKind::Enum, def.did)), | |
269 | unresolved: variant_field_str.into(), | |
270 | } | |
271 | .into()) | |
60c5eb7d XL |
272 | } |
273 | } | |
1b1a35ee | 274 | _ => unreachable!(), |
60c5eb7d XL |
275 | } |
276 | } | |
1b1a35ee XL |
277 | _ => Err(ResolutionFailure::NotResolved { |
278 | module_id, | |
279 | partial_res: Some(ty_res), | |
280 | unresolved: variant_str.into(), | |
281 | } | |
282 | .into()), | |
b7449926 XL |
283 | } |
284 | } | |
285 | ||
29967ef6 XL |
286 | /// Given a primitive type, try to resolve an associated item. |
287 | /// | |
288 | /// HACK(jynelson): `item_str` is passed in instead of derived from `item_name` so the | |
289 | /// lifetimes on `&'path` will work. | |
290 | fn resolve_primitive_associated_item( | |
291 | &self, | |
292 | prim_ty: hir::PrimTy, | |
293 | ns: Namespace, | |
294 | module_id: DefId, | |
295 | item_name: Symbol, | |
296 | item_str: &'path str, | |
297 | ) -> Result<(Res, Option<String>), ErrorKind<'path>> { | |
298 | let cx = self.cx; | |
299 | ||
300 | PrimitiveType::from_hir(prim_ty) | |
301 | .impls(cx.tcx) | |
302 | .into_iter() | |
303 | .find_map(|&impl_| { | |
304 | cx.tcx | |
305 | .associated_items(impl_) | |
306 | .find_by_name_and_namespace( | |
307 | cx.tcx, | |
308 | Ident::with_dummy_span(item_name), | |
309 | ns, | |
310 | impl_, | |
311 | ) | |
312 | .map(|item| match item.kind { | |
313 | ty::AssocKind::Fn => "method", | |
314 | ty::AssocKind::Const => "associatedconstant", | |
315 | ty::AssocKind::Type => "associatedtype", | |
316 | }) | |
317 | .map(|out| { | |
318 | ( | |
319 | Res::PrimTy(prim_ty), | |
320 | Some(format!("{}#{}.{}", prim_ty.name(), out, item_str)), | |
321 | ) | |
322 | }) | |
323 | }) | |
324 | .ok_or_else(|| { | |
325 | debug!( | |
326 | "returning primitive error for {}::{} in {} namespace", | |
327 | prim_ty.name(), | |
328 | item_name, | |
329 | ns.descr() | |
330 | ); | |
331 | ResolutionFailure::NotResolved { | |
332 | module_id, | |
333 | partial_res: Some(Res::PrimTy(prim_ty)), | |
334 | unresolved: item_str.into(), | |
335 | } | |
336 | .into() | |
337 | }) | |
338 | } | |
339 | ||
f035d41b | 340 | /// Resolves a string as a macro. |
29967ef6 XL |
341 | /// |
342 | /// FIXME(jynelson): Can this be unified with `resolve()`? | |
343 | fn resolve_macro( | |
1b1a35ee XL |
344 | &self, |
345 | path_str: &'a str, | |
346 | module_id: DefId, | |
347 | ) -> Result<Res, ResolutionFailure<'a>> { | |
f035d41b XL |
348 | let cx = self.cx; |
349 | let path = ast::Path::from_ident(Ident::from_str(path_str)); | |
350 | cx.enter_resolver(|resolver| { | |
29967ef6 | 351 | // FIXME(jynelson): does this really need 3 separate lookups? |
f035d41b XL |
352 | if let Ok((Some(ext), res)) = resolver.resolve_macro_path( |
353 | &path, | |
354 | None, | |
29967ef6 | 355 | &ParentScope::module(resolver.graph_root(), resolver), |
f035d41b XL |
356 | false, |
357 | false, | |
358 | ) { | |
359 | if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind { | |
1b1a35ee | 360 | return Ok(res.map_id(|_| panic!("unexpected id"))); |
f035d41b XL |
361 | } |
362 | } | |
363 | if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) { | |
1b1a35ee | 364 | return Ok(res.map_id(|_| panic!("unexpected id"))); |
f035d41b | 365 | } |
1b1a35ee XL |
366 | debug!("resolving {} as a macro in the module {:?}", path_str, module_id); |
367 | if let Ok((_, res)) = | |
368 | resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id) | |
369 | { | |
370 | // don't resolve builtins like `#[derive]` | |
371 | if let Res::Def(..) = res { | |
372 | let res = res.map_id(|_| panic!("unexpected node_id")); | |
373 | return Ok(res); | |
f035d41b | 374 | } |
f035d41b | 375 | } |
1b1a35ee XL |
376 | Err(ResolutionFailure::NotResolved { |
377 | module_id, | |
378 | partial_res: None, | |
379 | unresolved: path_str.into(), | |
380 | }) | |
f035d41b XL |
381 | }) |
382 | } | |
1b1a35ee | 383 | |
29967ef6 XL |
384 | /// Convenience wrapper around `resolve_str_path_error`. |
385 | /// | |
386 | /// This also handles resolving `true` and `false` as booleans. | |
387 | /// NOTE: `resolve_str_path_error` knows only about paths, not about types. | |
388 | /// Associated items will never be resolved by this function. | |
389 | fn resolve_path(&self, path_str: &str, ns: Namespace, module_id: DefId) -> Option<Res> { | |
390 | let result = self.cx.enter_resolver(|resolver| { | |
391 | resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id) | |
392 | }); | |
393 | debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns); | |
394 | match result.map(|(_, res)| res) { | |
395 | // resolver doesn't know about true and false so we'll have to resolve them | |
396 | // manually as bool | |
397 | Ok(Res::Err) | Err(()) => is_bool_value(path_str, ns).map(|(_, res)| res), | |
398 | Ok(res) => Some(res.map_id(|_| panic!("unexpected node_id"))), | |
399 | } | |
400 | } | |
401 | ||
402 | /// Resolves a string as a path within a particular namespace. Returns an | |
403 | /// optional URL fragment in the case of variants and methods. | |
1b1a35ee | 404 | fn resolve<'path>( |
60c5eb7d | 405 | &self, |
1b1a35ee | 406 | path_str: &'path str, |
60c5eb7d | 407 | ns: Namespace, |
29967ef6 | 408 | // FIXME(#76467): This is for `Self`, and it's wrong. |
60c5eb7d | 409 | current_item: &Option<String>, |
1b1a35ee | 410 | module_id: DefId, |
60c5eb7d | 411 | extra_fragment: &Option<String>, |
1b1a35ee | 412 | ) -> Result<(Res, Option<String>), ErrorKind<'path>> { |
b7449926 XL |
413 | let cx = self.cx; |
414 | ||
29967ef6 XL |
415 | if let Some(res) = self.resolve_path(path_str, ns, module_id) { |
416 | match res { | |
417 | // FIXME(#76467): make this fallthrough to lookup the associated | |
418 | // item a separate function. | |
419 | Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => { | |
420 | assert_eq!(ns, ValueNS); | |
421 | } | |
422 | Res::Def(DefKind::AssocTy, _) => { | |
423 | assert_eq!(ns, TypeNS); | |
424 | } | |
1b1a35ee XL |
425 | Res::Def(DefKind::Variant, _) => { |
426 | return handle_variant(cx, res, extra_fragment); | |
427 | } | |
428 | // Not a trait item; just return what we found. | |
429 | Res::PrimTy(ty) => { | |
430 | if extra_fragment.is_some() { | |
431 | return Err(ErrorKind::AnchorFailure( | |
432 | AnchorFailure::RustdocAnchorConflict(res), | |
433 | )); | |
f035d41b | 434 | } |
1b1a35ee XL |
435 | return Ok((res, Some(ty.name_str().to_owned()))); |
436 | } | |
437 | Res::Def(DefKind::Mod, _) => { | |
438 | return Ok((res, extra_fragment.clone())); | |
b7449926 | 439 | } |
1b1a35ee XL |
440 | _ => { |
441 | return Ok((res, extra_fragment.clone())); | |
b7449926 | 442 | } |
1b1a35ee | 443 | } |
1b1a35ee | 444 | } |
b7449926 | 445 | |
1b1a35ee XL |
446 | // Try looking for methods and associated items. |
447 | let mut split = path_str.rsplitn(2, "::"); | |
448 | // this can be an `unwrap()` because we ensure the link is never empty | |
449 | let (item_str, item_name) = split.next().map(|i| (i, Symbol::intern(i))).unwrap(); | |
450 | let path_root = split | |
451 | .next() | |
452 | .map(|f| { | |
453 | if f == "self" || f == "Self" { | |
454 | if let Some(name) = current_item.as_ref() { | |
455 | return name.clone(); | |
3dfed10e XL |
456 | } |
457 | } | |
1b1a35ee XL |
458 | f.to_owned() |
459 | }) | |
460 | // If there's no `::`, it's not an associated item. | |
461 | // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved. | |
462 | .ok_or_else(|| { | |
463 | debug!("found no `::`, assumming {} was correctly not in scope", item_name); | |
464 | ResolutionFailure::NotResolved { | |
465 | module_id, | |
466 | partial_res: None, | |
467 | unresolved: item_str.into(), | |
468 | } | |
469 | })?; | |
470 | ||
29967ef6 XL |
471 | // FIXME: are these both necessary? |
472 | let ty_res = if let Some(ty_res) = resolve_primitive(&path_root, TypeNS) | |
473 | .map(|(_, res)| res) | |
474 | .or_else(|| self.resolve_path(&path_root, TypeNS, module_id)) | |
475 | { | |
476 | ty_res | |
477 | } else { | |
478 | // FIXME: this is duplicated on the end of this function. | |
479 | return if ns == Namespace::ValueNS { | |
480 | self.variant_field(path_str, current_item, module_id) | |
481 | } else { | |
482 | Err(ResolutionFailure::NotResolved { | |
1b1a35ee | 483 | module_id, |
29967ef6 XL |
484 | partial_res: None, |
485 | unresolved: path_root.into(), | |
1b1a35ee | 486 | } |
29967ef6 XL |
487 | .into()) |
488 | }; | |
1b1a35ee | 489 | }; |
29967ef6 | 490 | |
1b1a35ee | 491 | let res = match ty_res { |
29967ef6 XL |
492 | Res::PrimTy(prim) => Some( |
493 | self.resolve_primitive_associated_item(prim, ns, module_id, item_name, item_str), | |
494 | ), | |
1b1a35ee XL |
495 | Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias, did) => { |
496 | debug!("looking for associated item named {} for item {:?}", item_name, did); | |
497 | // Checks if item_name belongs to `impl SomeItem` | |
498 | let assoc_item = cx | |
499 | .tcx | |
500 | .inherent_impls(did) | |
501 | .iter() | |
502 | .flat_map(|&imp| { | |
503 | cx.tcx.associated_items(imp).find_by_name_and_namespace( | |
504 | cx.tcx, | |
505 | Ident::with_dummy_span(item_name), | |
506 | ns, | |
507 | imp, | |
508 | ) | |
509 | }) | |
510 | .map(|item| (item.kind, item.def_id)) | |
511 | // There should only ever be one associated item that matches from any inherent impl | |
512 | .next() | |
513 | // Check if item_name belongs to `impl SomeTrait for SomeItem` | |
29967ef6 XL |
514 | // FIXME(#74563): This gives precedence to `impl SomeItem`: |
515 | // Although having both would be ambiguous, use impl version for compatibility's sake. | |
1b1a35ee XL |
516 | // To handle that properly resolve() would have to support |
517 | // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn) | |
518 | .or_else(|| { | |
519 | let kind = | |
520 | resolve_associated_trait_item(did, module_id, item_name, ns, &self.cx); | |
521 | debug!("got associated item kind {:?}", kind); | |
522 | kind | |
523 | }); | |
524 | ||
525 | if let Some((kind, id)) = assoc_item { | |
526 | let out = match kind { | |
527 | ty::AssocKind::Fn => "method", | |
528 | ty::AssocKind::Const => "associatedconstant", | |
529 | ty::AssocKind::Type => "associatedtype", | |
530 | }; | |
531 | Some(if extra_fragment.is_some() { | |
532 | Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(ty_res))) | |
533 | } else { | |
29967ef6 | 534 | // HACK(jynelson): `clean` expects the type, not the associated item |
1b1a35ee XL |
535 | // but the disambiguator logic expects the associated item. |
536 | // Store the kind in a side channel so that only the disambiguator logic looks at it. | |
537 | self.kind_side_channel.set(Some((kind.as_def_kind(), id))); | |
538 | Ok((ty_res, Some(format!("{}.{}", out, item_str)))) | |
539 | }) | |
540 | } else if ns == Namespace::ValueNS { | |
541 | debug!("looking for variants or fields named {} for {:?}", item_name, did); | |
29967ef6 XL |
542 | // FIXME(jynelson): why is this different from |
543 | // `variant_field`? | |
1b1a35ee XL |
544 | match cx.tcx.type_of(did).kind() { |
545 | ty::Adt(def, _) => { | |
546 | let field = if def.is_enum() { | |
547 | def.all_fields().find(|item| item.ident.name == item_name) | |
60c5eb7d | 548 | } else { |
1b1a35ee XL |
549 | def.non_enum_variant() |
550 | .fields | |
551 | .iter() | |
552 | .find(|item| item.ident.name == item_name) | |
553 | }; | |
554 | field.map(|item| { | |
555 | if extra_fragment.is_some() { | |
556 | let res = Res::Def( | |
557 | if def.is_enum() { | |
558 | DefKind::Variant | |
60c5eb7d | 559 | } else { |
1b1a35ee XL |
560 | DefKind::Field |
561 | }, | |
562 | item.did, | |
563 | ); | |
564 | Err(ErrorKind::AnchorFailure( | |
565 | AnchorFailure::RustdocAnchorConflict(res), | |
566 | )) | |
567 | } else { | |
568 | Ok(( | |
569 | ty_res, | |
570 | Some(format!( | |
571 | "{}.{}", | |
572 | if def.is_enum() { "variant" } else { "structfield" }, | |
573 | item.ident | |
574 | )), | |
575 | )) | |
576 | } | |
577 | }) | |
578 | } | |
579 | _ => None, | |
580 | } | |
581 | } else { | |
29967ef6 | 582 | None |
1b1a35ee XL |
583 | } |
584 | } | |
585 | Res::Def(DefKind::Trait, did) => cx | |
586 | .tcx | |
587 | .associated_items(did) | |
588 | .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, did) | |
589 | .map(|item| { | |
590 | let kind = match item.kind { | |
591 | ty::AssocKind::Const => "associatedconstant", | |
592 | ty::AssocKind::Type => "associatedtype", | |
593 | ty::AssocKind::Fn => { | |
594 | if item.defaultness.has_value() { | |
595 | "method" | |
596 | } else { | |
597 | "tymethod" | |
b7449926 | 598 | } |
b7449926 | 599 | } |
1b1a35ee XL |
600 | }; |
601 | ||
602 | if extra_fragment.is_some() { | |
603 | Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(ty_res))) | |
3dfed10e | 604 | } else { |
1b1a35ee XL |
605 | let res = Res::Def(item.kind.as_def_kind(), item.def_id); |
606 | Ok((res, Some(format!("{}.{}", kind, item_str)))) | |
b7449926 | 607 | } |
1b1a35ee XL |
608 | }), |
609 | _ => None, | |
610 | }; | |
611 | res.unwrap_or_else(|| { | |
612 | if ns == Namespace::ValueNS { | |
613 | self.variant_field(path_str, current_item, module_id) | |
614 | } else { | |
615 | Err(ResolutionFailure::NotResolved { | |
616 | module_id, | |
617 | partial_res: Some(ty_res), | |
618 | unresolved: item_str.into(), | |
b7449926 | 619 | } |
1b1a35ee XL |
620 | .into()) |
621 | } | |
622 | }) | |
623 | } | |
b7449926 | 624 | |
1b1a35ee XL |
625 | /// Used for reporting better errors. |
626 | /// | |
627 | /// Returns whether the link resolved 'fully' in another namespace. | |
628 | /// 'fully' here means that all parts of the link resolved, not just some path segments. | |
629 | /// This returns the `Res` even if it was erroneous for some reason | |
630 | /// (such as having invalid URL fragments or being in the wrong namespace). | |
631 | fn check_full_res( | |
632 | &self, | |
633 | ns: Namespace, | |
634 | path_str: &str, | |
635 | module_id: DefId, | |
636 | current_item: &Option<String>, | |
637 | extra_fragment: &Option<String>, | |
638 | ) -> Option<Res> { | |
29967ef6 XL |
639 | // resolve() can't be used for macro namespace |
640 | let result = match ns { | |
641 | Namespace::MacroNS => self.resolve_macro(path_str, module_id).map_err(ErrorKind::from), | |
642 | Namespace::TypeNS | Namespace::ValueNS => self | |
643 | .resolve(path_str, ns, current_item, module_id, extra_fragment) | |
644 | .map(|(res, _)| res), | |
1b1a35ee | 645 | }; |
29967ef6 XL |
646 | |
647 | let res = match result { | |
648 | Ok(res) => Some(res), | |
649 | Err(ErrorKind::Resolve(box kind)) => kind.full_res(), | |
650 | Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res))) => Some(res), | |
651 | Err(ErrorKind::AnchorFailure(AnchorFailure::MultipleAnchors)) => None, | |
1b1a35ee | 652 | }; |
29967ef6 | 653 | self.kind_side_channel.take().map(|(kind, id)| Res::Def(kind, id)).or(res) |
b7449926 XL |
654 | } |
655 | } | |
656 | ||
29967ef6 XL |
657 | /// Look to see if a resolved item has an associated item named `item_name`. |
658 | /// | |
659 | /// Given `[std::io::Error::source]`, where `source` is unresolved, this would | |
660 | /// find `std::error::Error::source` and return | |
661 | /// `<io::Error as error::Error>::source`. | |
3dfed10e XL |
662 | fn resolve_associated_trait_item( |
663 | did: DefId, | |
664 | module: DefId, | |
665 | item_name: Symbol, | |
666 | ns: Namespace, | |
667 | cx: &DocContext<'_>, | |
1b1a35ee | 668 | ) -> Option<(ty::AssocKind, DefId)> { |
3dfed10e | 669 | let ty = cx.tcx.type_of(did); |
29967ef6 | 670 | // First consider blanket impls: `impl From<T> for T` |
3dfed10e XL |
671 | let implicit_impls = crate::clean::get_auto_trait_and_blanket_impls(cx, ty, did); |
672 | let mut candidates: Vec<_> = implicit_impls | |
673 | .flat_map(|impl_outer| { | |
674 | match impl_outer.inner { | |
29967ef6 | 675 | clean::ImplItem(impl_) => { |
3dfed10e XL |
676 | debug!("considering auto or blanket impl for trait {:?}", impl_.trait_); |
677 | // Give precedence to methods that were overridden | |
678 | if !impl_.provided_trait_methods.contains(&*item_name.as_str()) { | |
679 | let mut items = impl_.items.into_iter().filter_map(|assoc| { | |
680 | if assoc.name.as_deref() != Some(&*item_name.as_str()) { | |
681 | return None; | |
682 | } | |
683 | let kind = assoc | |
684 | .inner | |
685 | .as_assoc_kind() | |
686 | .expect("inner items for a trait should be associated items"); | |
687 | if kind.namespace() != ns { | |
688 | return None; | |
689 | } | |
690 | ||
691 | trace!("considering associated item {:?}", assoc.inner); | |
692 | // We have a slight issue: normal methods come from `clean` types, | |
693 | // but provided methods come directly from `tcx`. | |
694 | // Fortunately, we don't need the whole method, we just need to know | |
695 | // what kind of associated item it is. | |
1b1a35ee | 696 | Some((kind, assoc.def_id)) |
3dfed10e XL |
697 | }); |
698 | let assoc = items.next(); | |
699 | debug_assert_eq!(items.count(), 0); | |
700 | assoc | |
701 | } else { | |
702 | // These are provided methods or default types: | |
703 | // ``` | |
704 | // trait T { | |
705 | // type A = usize; | |
706 | // fn has_default() -> A { 0 } | |
707 | // } | |
708 | // ``` | |
709 | let trait_ = impl_.trait_.unwrap().def_id().unwrap(); | |
710 | cx.tcx | |
711 | .associated_items(trait_) | |
712 | .find_by_name_and_namespace( | |
713 | cx.tcx, | |
714 | Ident::with_dummy_span(item_name), | |
715 | ns, | |
716 | trait_, | |
717 | ) | |
1b1a35ee | 718 | .map(|assoc| (assoc.kind, assoc.def_id)) |
3dfed10e XL |
719 | } |
720 | } | |
721 | _ => panic!("get_impls returned something that wasn't an impl"), | |
722 | } | |
723 | }) | |
724 | .collect(); | |
725 | ||
726 | // Next consider explicit impls: `impl MyTrait for MyType` | |
727 | // Give precedence to inherent impls. | |
728 | if candidates.is_empty() { | |
729 | let traits = traits_implemented_by(cx, did, module); | |
730 | debug!("considering traits {:?}", traits); | |
731 | candidates.extend(traits.iter().filter_map(|&trait_| { | |
732 | cx.tcx | |
733 | .associated_items(trait_) | |
734 | .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_) | |
1b1a35ee | 735 | .map(|assoc| (assoc.kind, assoc.def_id)) |
3dfed10e XL |
736 | })); |
737 | } | |
29967ef6 | 738 | // FIXME(#74563): warn about ambiguity |
3dfed10e | 739 | debug!("the candidates were {:?}", candidates); |
1b1a35ee | 740 | candidates.pop() |
3dfed10e XL |
741 | } |
742 | ||
743 | /// Given a type, return all traits in scope in `module` implemented by that type. | |
744 | /// | |
745 | /// NOTE: this cannot be a query because more traits could be available when more crates are compiled! | |
746 | /// So it is not stable to serialize cross-crate. | |
747 | fn traits_implemented_by(cx: &DocContext<'_>, type_: DefId, module: DefId) -> FxHashSet<DefId> { | |
748 | let mut cache = cx.module_trait_cache.borrow_mut(); | |
749 | let in_scope_traits = cache.entry(module).or_insert_with(|| { | |
750 | cx.enter_resolver(|resolver| { | |
751 | resolver.traits_in_scope(module).into_iter().map(|candidate| candidate.def_id).collect() | |
752 | }) | |
753 | }); | |
754 | ||
755 | let ty = cx.tcx.type_of(type_); | |
756 | let iter = in_scope_traits.iter().flat_map(|&trait_| { | |
757 | trace!("considering explicit impl for trait {:?}", trait_); | |
3dfed10e | 758 | |
29967ef6 XL |
759 | // Look at each trait implementation to see if it's an impl for `did` |
760 | cx.tcx.find_map_relevant_impl(trait_, ty, |impl_| { | |
3dfed10e XL |
761 | let trait_ref = cx.tcx.impl_trait_ref(impl_).expect("this is not an inherent impl"); |
762 | // Check if these are the same type. | |
763 | let impl_type = trait_ref.self_ty(); | |
1b1a35ee | 764 | trace!( |
3dfed10e | 765 | "comparing type {} with kind {:?} against type {:?}", |
1b1a35ee XL |
766 | impl_type, |
767 | impl_type.kind(), | |
768 | type_ | |
3dfed10e XL |
769 | ); |
770 | // Fast path: if this is a primitive simple `==` will work | |
29967ef6 | 771 | let saw_impl = impl_type == ty |
1b1a35ee | 772 | || match impl_type.kind() { |
3dfed10e XL |
773 | // Check if these are the same def_id |
774 | ty::Adt(def, _) => { | |
775 | debug!("adt def_id: {:?}", def.did); | |
776 | def.did == type_ | |
777 | } | |
1b1a35ee | 778 | ty::Foreign(def_id) => *def_id == type_, |
3dfed10e XL |
779 | _ => false, |
780 | }; | |
29967ef6 XL |
781 | |
782 | if saw_impl { Some(trait_) } else { None } | |
783 | }) | |
3dfed10e XL |
784 | }); |
785 | iter.collect() | |
786 | } | |
787 | ||
29967ef6 | 788 | /// Check for resolve collisions between a trait and its derive. |
f035d41b | 789 | /// |
29967ef6 | 790 | /// These are common and we should just resolve to the trait in that case. |
1b1a35ee | 791 | fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_>>>) -> bool { |
29967ef6 | 792 | matches!(*ns, PerNS { |
1b1a35ee XL |
793 | type_ns: Ok((Res::Def(DefKind::Trait, _), _)), |
794 | macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)), | |
f035d41b | 795 | .. |
29967ef6 | 796 | }) |
f035d41b XL |
797 | } |
798 | ||
532ac7d7 | 799 | impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> { |
b7449926 | 800 | fn fold_item(&mut self, mut item: Item) -> Option<Item> { |
3dfed10e | 801 | use rustc_middle::ty::DefIdTree; |
b7449926 | 802 | |
3dfed10e XL |
803 | let parent_node = if item.is_fake() { |
804 | // FIXME: is this correct? | |
805 | None | |
1b1a35ee XL |
806 | // If we're documenting the crate root itself, it has no parent. Use the root instead. |
807 | } else if item.def_id.is_top_level_module() { | |
808 | Some(item.def_id) | |
3dfed10e XL |
809 | } else { |
810 | let mut current = item.def_id; | |
811 | // The immediate parent might not always be a module. | |
812 | // Find the first parent which is. | |
813 | loop { | |
814 | if let Some(parent) = self.cx.tcx.parent(current) { | |
815 | if self.cx.tcx.def_kind(parent) == DefKind::Mod { | |
816 | break Some(parent); | |
817 | } | |
818 | current = parent; | |
819 | } else { | |
1b1a35ee XL |
820 | debug!( |
821 | "{:?} has no parent (kind={:?}, original was {:?})", | |
822 | current, | |
823 | self.cx.tcx.def_kind(current), | |
824 | item.def_id | |
825 | ); | |
3dfed10e XL |
826 | break None; |
827 | } | |
0bf4aa26 | 828 | } |
3dfed10e | 829 | }; |
0bf4aa26 XL |
830 | |
831 | if parent_node.is_some() { | |
3dfed10e | 832 | trace!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.def_id); |
0bf4aa26 XL |
833 | } |
834 | ||
b7449926 | 835 | let current_item = match item.inner { |
29967ef6 | 836 | clean::ModuleItem(..) => { |
b7449926 | 837 | if item.attrs.inner_docs { |
3dfed10e | 838 | if item.def_id.is_top_level_module() { item.name.clone() } else { None } |
b7449926 | 839 | } else { |
3dfed10e XL |
840 | match parent_node.or(self.mod_ids.last().copied()) { |
841 | Some(parent) if !parent.is_top_level_module() => { | |
3dfed10e | 842 | Some(self.cx.tcx.item_name(parent).to_string()) |
b7449926 XL |
843 | } |
844 | _ => None, | |
845 | } | |
846 | } | |
847 | } | |
29967ef6 | 848 | clean::ImplItem(clean::Impl { ref for_, .. }) => { |
b7449926 XL |
849 | for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string()) |
850 | } | |
0731742a | 851 | // we don't display docs on `extern crate` items anyway, so don't process them. |
29967ef6 | 852 | clean::ExternCrateItem(..) => { |
3dfed10e XL |
853 | debug!("ignoring extern crate item {:?}", item.def_id); |
854 | return self.fold_item_recur(item); | |
855 | } | |
29967ef6 XL |
856 | clean::ImportItem(Import { kind: clean::ImportKind::Simple(ref name, ..), .. }) => { |
857 | Some(name.clone()) | |
858 | } | |
859 | clean::MacroItem(..) => None, | |
b7449926 XL |
860 | _ => item.name.clone(), |
861 | }; | |
862 | ||
863 | if item.is_mod() && item.attrs.inner_docs { | |
3dfed10e | 864 | self.mod_ids.push(item.def_id); |
b7449926 XL |
865 | } |
866 | ||
f9f354fc | 867 | // find item's parent to resolve `Self` in item's docs below |
29967ef6 XL |
868 | // FIXME(#76467, #75809): this is a mess and doesn't handle cross-crate |
869 | // re-exports | |
f9f354fc XL |
870 | let parent_name = self.cx.as_local_hir_id(item.def_id).and_then(|item_hir| { |
871 | let parent_hir = self.cx.tcx.hir().get_parent_item(item_hir); | |
872 | let item_parent = self.cx.tcx.hir().find(parent_hir); | |
873 | match item_parent { | |
874 | Some(hir::Node::Item(hir::Item { | |
875 | kind: | |
876 | hir::ItemKind::Impl { | |
877 | self_ty: | |
878 | hir::Ty { | |
879 | kind: | |
880 | hir::TyKind::Path(hir::QPath::Resolved( | |
881 | _, | |
882 | hir::Path { segments, .. }, | |
883 | )), | |
884 | .. | |
885 | }, | |
886 | .. | |
887 | }, | |
888 | .. | |
f035d41b | 889 | })) => segments.first().map(|seg| seg.ident.to_string()), |
f9f354fc XL |
890 | Some(hir::Node::Item(hir::Item { |
891 | ident, kind: hir::ItemKind::Enum(..), .. | |
892 | })) | |
893 | | Some(hir::Node::Item(hir::Item { | |
894 | ident, kind: hir::ItemKind::Struct(..), .. | |
895 | })) | |
896 | | Some(hir::Node::Item(hir::Item { | |
897 | ident, kind: hir::ItemKind::Union(..), .. | |
898 | })) | |
899 | | Some(hir::Node::Item(hir::Item { | |
900 | ident, kind: hir::ItemKind::Trait(..), .. | |
901 | })) => Some(ident.to_string()), | |
902 | _ => None, | |
903 | } | |
904 | }); | |
905 | ||
29967ef6 XL |
906 | // We want to resolve in the lexical scope of the documentation. |
907 | // In the presence of re-exports, this is not the same as the module of the item. | |
908 | // Rather than merging all documentation into one, resolve it one attribute at a time | |
909 | // so we know which module it came from. | |
910 | let mut attrs = item.attrs.doc_strings.iter().peekable(); | |
911 | while let Some(attr) = attrs.next() { | |
912 | // `collapse_docs` does not have the behavior we want: | |
913 | // we want `///` and `#[doc]` to count as the same attribute, | |
914 | // but currently it will treat them as separate. | |
915 | // As a workaround, combine all attributes with the same parent module into the same attribute. | |
916 | let mut combined_docs = attr.doc.clone(); | |
917 | loop { | |
918 | match attrs.peek() { | |
919 | Some(next) if next.parent_module == attr.parent_module => { | |
920 | combined_docs.push('\n'); | |
921 | combined_docs.push_str(&attrs.next().unwrap().doc); | |
922 | } | |
923 | _ => break, | |
924 | } | |
925 | } | |
926 | debug!("combined_docs={}", combined_docs); | |
3dfed10e | 927 | |
29967ef6 XL |
928 | let (krate, parent_node) = if let Some(id) = attr.parent_module { |
929 | trace!("docs {:?} came from {:?}", attr.doc, id); | |
930 | (id.krate, Some(id)) | |
931 | } else { | |
932 | trace!("no parent found for {:?}", attr.doc); | |
933 | (item.def_id.krate, parent_node) | |
934 | }; | |
935 | // NOTE: if there are links that start in one crate and end in another, this will not resolve them. | |
936 | // This is a degenerate case and it's not supported by rustdoc. | |
937 | for (ori_link, link_range) in markdown_links(&combined_docs) { | |
938 | let link = self.resolve_link( | |
939 | &item, | |
940 | &combined_docs, | |
941 | ¤t_item, | |
942 | parent_node, | |
943 | &parent_name, | |
944 | krate, | |
945 | ori_link, | |
946 | link_range, | |
947 | ); | |
948 | if let Some(link) = link { | |
949 | item.attrs.links.push(link); | |
950 | } | |
951 | } | |
1b1a35ee XL |
952 | } |
953 | ||
954 | if item.is_mod() { | |
29967ef6 XL |
955 | if !item.attrs.inner_docs { |
956 | self.mod_ids.push(item.def_id); | |
957 | } | |
1b1a35ee | 958 | |
29967ef6 | 959 | let ret = self.fold_item_recur(item); |
1b1a35ee | 960 | self.mod_ids.pop(); |
1b1a35ee XL |
961 | ret |
962 | } else { | |
963 | self.fold_item_recur(item) | |
964 | } | |
965 | } | |
966 | } | |
967 | ||
968 | impl LinkCollector<'_, '_> { | |
29967ef6 XL |
969 | /// This is the entry point for resolving an intra-doc link. |
970 | /// | |
971 | /// FIXME(jynelson): this is way too many arguments | |
1b1a35ee XL |
972 | fn resolve_link( |
973 | &self, | |
29967ef6 | 974 | item: &Item, |
1b1a35ee XL |
975 | dox: &str, |
976 | current_item: &Option<String>, | |
977 | parent_node: Option<DefId>, | |
978 | parent_name: &Option<String>, | |
29967ef6 | 979 | krate: CrateNum, |
1b1a35ee XL |
980 | ori_link: String, |
981 | link_range: Option<Range<usize>>, | |
29967ef6 | 982 | ) -> Option<ItemLink> { |
1b1a35ee XL |
983 | trace!("considering link '{}'", ori_link); |
984 | ||
985 | // Bail early for real links. | |
986 | if ori_link.contains('/') { | |
29967ef6 | 987 | return None; |
1b1a35ee XL |
988 | } |
989 | ||
990 | // [] is mostly likely not supposed to be a link | |
991 | if ori_link.is_empty() { | |
29967ef6 | 992 | return None; |
1b1a35ee XL |
993 | } |
994 | ||
995 | let cx = self.cx; | |
996 | let link = ori_link.replace("`", ""); | |
997 | let parts = link.split('#').collect::<Vec<_>>(); | |
998 | let (link, extra_fragment) = if parts.len() > 2 { | |
999 | anchor_failure(cx, &item, &link, dox, link_range, AnchorFailure::MultipleAnchors); | |
29967ef6 | 1000 | return None; |
1b1a35ee XL |
1001 | } else if parts.len() == 2 { |
1002 | if parts[0].trim().is_empty() { | |
1003 | // This is an anchor to an element of the current page, nothing to do in here! | |
29967ef6 | 1004 | return None; |
b7449926 | 1005 | } |
1b1a35ee XL |
1006 | (parts[0], Some(parts[1].to_owned())) |
1007 | } else { | |
1008 | (parts[0], None) | |
1009 | }; | |
532ac7d7 | 1010 | |
29967ef6 XL |
1011 | // Parse and strip the disambiguator from the link, if present. |
1012 | let (mut path_str, disambiguator) = if let Ok((d, path)) = Disambiguator::from_str(&link) { | |
1013 | (path.trim(), Some(d)) | |
1014 | } else { | |
1015 | (link.trim(), None) | |
1016 | }; | |
b7449926 | 1017 | |
29967ef6 XL |
1018 | if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, ".contains(ch))) { |
1019 | return None; | |
1020 | } | |
1b1a35ee | 1021 | |
29967ef6 XL |
1022 | // We stripped `()` and `!` when parsing the disambiguator. |
1023 | // Add them back to be displayed, but not prefix disambiguators. | |
1024 | let link_text = | |
1025 | disambiguator.map(|d| d.display_for(path_str)).unwrap_or_else(|| path_str.to_owned()); | |
1026 | ||
1027 | // In order to correctly resolve intra-doc-links we need to | |
1028 | // pick a base AST node to work from. If the documentation for | |
1029 | // this module came from an inner comment (//!) then we anchor | |
1030 | // our name resolution *inside* the module. If, on the other | |
1031 | // hand it was an outer comment (///) then we anchor the name | |
1032 | // resolution in the parent module on the basis that the names | |
1033 | // used are more likely to be intended to be parent names. For | |
1034 | // this, we set base_node to None for inner comments since | |
1035 | // we've already pushed this node onto the resolution stack but | |
1036 | // for outer comments we explicitly try and resolve against the | |
1037 | // parent_node first. | |
1038 | let base_node = if item.is_mod() && item.attrs.inner_docs { | |
1039 | self.mod_ids.last().copied() | |
1040 | } else { | |
1041 | parent_node | |
1042 | }; | |
b7449926 | 1043 | |
29967ef6 XL |
1044 | let mut module_id = if let Some(id) = base_node { |
1045 | id | |
1046 | } else { | |
1047 | // This is a bug. | |
1048 | debug!("attempting to resolve item without parent module: {}", path_str); | |
1049 | let err_kind = ResolutionFailure::NoParentItem.into(); | |
1050 | resolution_failure( | |
1051 | self, | |
1052 | &item, | |
1053 | path_str, | |
1b1a35ee | 1054 | disambiguator, |
1b1a35ee | 1055 | dox, |
29967ef6 XL |
1056 | link_range, |
1057 | smallvec![err_kind], | |
1058 | ); | |
1059 | return None; | |
1b1a35ee | 1060 | }; |
e74abb32 | 1061 | |
29967ef6 XL |
1062 | let resolved_self; |
1063 | // replace `Self` with suitable item's parent name | |
1064 | if path_str.starts_with("Self::") { | |
1065 | if let Some(ref name) = parent_name { | |
1066 | resolved_self = format!("{}::{}", name, &path_str[6..]); | |
1067 | path_str = &resolved_self; | |
1068 | } | |
1069 | } else if path_str.starts_with("crate::") { | |
1070 | use rustc_span::def_id::CRATE_DEF_INDEX; | |
1071 | ||
1072 | // HACK(jynelson): rustc_resolve thinks that `crate` is the crate currently being documented. | |
1073 | // But rustdoc wants it to mean the crate this item was originally present in. | |
1074 | // To work around this, remove it and resolve relative to the crate root instead. | |
1075 | // HACK(jynelson)(2): If we just strip `crate::` then suddenly primitives become ambiguous | |
1076 | // (consider `crate::char`). Instead, change it to `self::`. This works because 'self' is now the crate root. | |
1077 | // FIXME(#78696): This doesn't always work. | |
1078 | resolved_self = format!("self::{}", &path_str["crate::".len()..]); | |
1079 | path_str = &resolved_self; | |
1080 | module_id = DefId { krate, index: CRATE_DEF_INDEX }; | |
1081 | } | |
1082 | ||
1083 | // Strip generics from the path. | |
1084 | let stripped_path_string; | |
1085 | if path_str.contains(['<', '>'].as_slice()) { | |
1086 | stripped_path_string = match strip_generics_from_path(path_str) { | |
1087 | Ok(path) => path, | |
1088 | Err(err_kind) => { | |
1089 | debug!("link has malformed generics: {}", path_str); | |
1090 | resolution_failure( | |
1091 | self, | |
1092 | &item, | |
1093 | path_str, | |
1094 | disambiguator, | |
1095 | dox, | |
1096 | link_range, | |
1097 | smallvec![err_kind], | |
1098 | ); | |
1099 | return None; | |
1100 | } | |
1101 | }; | |
1102 | path_str = &stripped_path_string; | |
1103 | } | |
1104 | // Sanity check to make sure we don't have any angle brackets after stripping generics. | |
1105 | assert!(!path_str.contains(['<', '>'].as_slice())); | |
1106 | ||
1107 | // The link is not an intra-doc link if it still contains commas or spaces after | |
1108 | // stripping generics. | |
1109 | if path_str.contains([',', ' '].as_slice()) { | |
1110 | return None; | |
1111 | } | |
1112 | ||
1113 | let (mut res, mut fragment) = self.resolve_with_disambiguator( | |
1114 | disambiguator, | |
1115 | item, | |
1116 | dox, | |
1117 | path_str, | |
1118 | current_item, | |
1119 | module_id, | |
1120 | extra_fragment, | |
1121 | &ori_link, | |
1122 | link_range.clone(), | |
1123 | )?; | |
1124 | ||
1b1a35ee XL |
1125 | // Check for a primitive which might conflict with a module |
1126 | // Report the ambiguity and require that the user specify which one they meant. | |
1127 | // FIXME: could there ever be a primitive not in the type namespace? | |
1128 | if matches!( | |
1129 | disambiguator, | |
1130 | None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive) | |
1131 | ) && !matches!(res, Res::PrimTy(_)) | |
1132 | { | |
29967ef6 | 1133 | if let Some((path, prim)) = resolve_primitive(path_str, TypeNS) { |
1b1a35ee XL |
1134 | // `prim@char` |
1135 | if matches!(disambiguator, Some(Disambiguator::Primitive)) { | |
1136 | if fragment.is_some() { | |
1137 | anchor_failure( | |
1138 | cx, | |
1139 | &item, | |
1140 | path_str, | |
1141 | dox, | |
1142 | link_range, | |
1143 | AnchorFailure::RustdocAnchorConflict(prim), | |
1144 | ); | |
29967ef6 | 1145 | return None; |
f9f354fc | 1146 | } |
1b1a35ee | 1147 | res = prim; |
29967ef6 | 1148 | fragment = Some(path.as_str().to_string()); |
1b1a35ee XL |
1149 | } else { |
1150 | // `[char]` when a `char` module is in scope | |
1151 | let candidates = vec![res, prim]; | |
1152 | ambiguity_error(cx, &item, path_str, dox, link_range, candidates); | |
29967ef6 | 1153 | return None; |
f9f354fc | 1154 | } |
1b1a35ee XL |
1155 | } |
1156 | } | |
f9f354fc | 1157 | |
1b1a35ee XL |
1158 | let report_mismatch = |specified: Disambiguator, resolved: Disambiguator| { |
1159 | // The resolved item did not match the disambiguator; give a better error than 'not found' | |
1160 | let msg = format!("incompatible link kind for `{}`", path_str); | |
1161 | let callback = |diag: &mut DiagnosticBuilder<'_>, sp| { | |
1162 | let note = format!( | |
1163 | "this link resolved to {} {}, which is not {} {}", | |
1164 | resolved.article(), | |
1165 | resolved.descr(), | |
1166 | specified.article(), | |
1167 | specified.descr() | |
1168 | ); | |
1169 | diag.note(¬e); | |
1170 | suggest_disambiguator(resolved, diag, path_str, dox, sp, &link_range); | |
1171 | }; | |
1172 | report_diagnostic(cx, BROKEN_INTRA_DOC_LINKS, &msg, &item, dox, &link_range, callback); | |
1173 | }; | |
1174 | if let Res::PrimTy(..) = res { | |
1175 | match disambiguator { | |
1176 | Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => { | |
29967ef6 | 1177 | Some(ItemLink { link: ori_link, link_text, did: None, fragment }) |
1b1a35ee XL |
1178 | } |
1179 | Some(other) => { | |
1180 | report_mismatch(other, Disambiguator::Primitive); | |
29967ef6 | 1181 | None |
1b1a35ee XL |
1182 | } |
1183 | } | |
1184 | } else { | |
1185 | debug!("intra-doc link to {} resolved to {:?}", path_str, res); | |
1186 | ||
1187 | // Disallow e.g. linking to enums with `struct@` | |
1188 | if let Res::Def(kind, _) = res { | |
1189 | debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator); | |
1190 | match (self.kind_side_channel.take().map(|(kind, _)| kind).unwrap_or(kind), disambiguator) { | |
1191 | | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const))) | |
1192 | // NOTE: this allows 'method' to mean both normal functions and associated functions | |
1193 | // This can't cause ambiguity because both are in the same namespace. | |
1194 | | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn))) | |
1195 | // These are namespaces; allow anything in the namespace to match | |
1196 | | (_, Some(Disambiguator::Namespace(_))) | |
1197 | // If no disambiguator given, allow anything | |
1198 | | (_, None) | |
1199 | // All of these are valid, so do nothing | |
1200 | => {} | |
1201 | (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {} | |
1202 | (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => { | |
1203 | report_mismatch(specified, Disambiguator::Kind(kind)); | |
29967ef6 | 1204 | return None; |
b7449926 | 1205 | } |
1b1a35ee XL |
1206 | } |
1207 | } | |
532ac7d7 | 1208 | |
1b1a35ee XL |
1209 | // item can be non-local e.g. when using #[doc(primitive = "pointer")] |
1210 | if let Some((src_id, dst_id)) = res | |
1211 | .opt_def_id() | |
1212 | .and_then(|def_id| def_id.as_local()) | |
1213 | .and_then(|dst_id| item.def_id.as_local().map(|src_id| (src_id, dst_id))) | |
1214 | { | |
1215 | use rustc_hir::def_id::LOCAL_CRATE; | |
532ac7d7 | 1216 | |
1b1a35ee XL |
1217 | let hir_src = self.cx.tcx.hir().local_def_id_to_hir_id(src_id); |
1218 | let hir_dst = self.cx.tcx.hir().local_def_id_to_hir_id(dst_id); | |
f035d41b | 1219 | |
1b1a35ee XL |
1220 | if self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_src) |
1221 | && !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_dst) | |
1222 | { | |
1223 | privacy_error(cx, &item, &path_str, dox, link_range); | |
1224 | } | |
1225 | } | |
29967ef6 XL |
1226 | let id = clean::register_res(cx, res); |
1227 | Some(ItemLink { link: ori_link, link_text, did: Some(id), fragment }) | |
1b1a35ee XL |
1228 | } |
1229 | } | |
1230 | ||
29967ef6 XL |
1231 | /// After parsing the disambiguator, resolve the main part of the link. |
1232 | // FIXME(jynelson): wow this is just so much | |
1b1a35ee XL |
1233 | fn resolve_with_disambiguator( |
1234 | &self, | |
1235 | disambiguator: Option<Disambiguator>, | |
29967ef6 | 1236 | item: &Item, |
1b1a35ee XL |
1237 | dox: &str, |
1238 | path_str: &str, | |
1239 | current_item: &Option<String>, | |
1240 | base_node: DefId, | |
1241 | extra_fragment: Option<String>, | |
1242 | ori_link: &str, | |
1243 | link_range: Option<Range<usize>>, | |
1244 | ) -> Option<(Res, Option<String>)> { | |
1245 | match disambiguator.map(Disambiguator::ns) { | |
1246 | Some(ns @ (ValueNS | TypeNS)) => { | |
1247 | match self.resolve(path_str, ns, ¤t_item, base_node, &extra_fragment) { | |
1248 | Ok(res) => Some(res), | |
1249 | Err(ErrorKind::Resolve(box mut kind)) => { | |
1250 | // We only looked in one namespace. Try to give a better error if possible. | |
1251 | if kind.full_res().is_none() { | |
1252 | let other_ns = if ns == ValueNS { TypeNS } else { ValueNS }; | |
1253 | // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator` | |
1254 | // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach | |
1255 | for &new_ns in &[other_ns, MacroNS] { | |
1256 | if let Some(res) = self.check_full_res( | |
1257 | new_ns, | |
1258 | path_str, | |
1259 | base_node, | |
1260 | ¤t_item, | |
1261 | &extra_fragment, | |
1262 | ) { | |
1263 | kind = ResolutionFailure::WrongNamespace(res, ns); | |
1264 | break; | |
1265 | } | |
f035d41b | 1266 | } |
532ac7d7 | 1267 | } |
1b1a35ee XL |
1268 | resolution_failure( |
1269 | self, | |
1270 | &item, | |
1271 | path_str, | |
1272 | disambiguator, | |
1273 | dox, | |
1274 | link_range, | |
1275 | smallvec![kind], | |
1276 | ); | |
1277 | // This could just be a normal link or a broken link | |
1278 | // we could potentially check if something is | |
1279 | // "intra-doc-link-like" and warn in that case. | |
1280 | return None; | |
b7449926 | 1281 | } |
1b1a35ee XL |
1282 | Err(ErrorKind::AnchorFailure(msg)) => { |
1283 | anchor_failure(self.cx, &item, &ori_link, dox, link_range, msg); | |
1284 | return None; | |
b7449926 XL |
1285 | } |
1286 | } | |
1b1a35ee XL |
1287 | } |
1288 | None => { | |
1289 | // Try everything! | |
1290 | let mut candidates = PerNS { | |
1291 | macro_ns: self | |
29967ef6 | 1292 | .resolve_macro(path_str, base_node) |
1b1a35ee XL |
1293 | .map(|res| (res, extra_fragment.clone())), |
1294 | type_ns: match self.resolve( | |
1295 | path_str, | |
1296 | TypeNS, | |
1297 | ¤t_item, | |
1298 | base_node, | |
1299 | &extra_fragment, | |
1300 | ) { | |
1301 | Ok(res) => { | |
1302 | debug!("got res in TypeNS: {:?}", res); | |
1303 | Ok(res) | |
3dfed10e | 1304 | } |
1b1a35ee XL |
1305 | Err(ErrorKind::AnchorFailure(msg)) => { |
1306 | anchor_failure(self.cx, &item, ori_link, dox, link_range, msg); | |
1307 | return None; | |
1308 | } | |
1309 | Err(ErrorKind::Resolve(box kind)) => Err(kind), | |
1310 | }, | |
1311 | value_ns: match self.resolve( | |
1312 | path_str, | |
1313 | ValueNS, | |
1314 | ¤t_item, | |
1315 | base_node, | |
1316 | &extra_fragment, | |
1317 | ) { | |
1318 | Ok(res) => Ok(res), | |
1319 | Err(ErrorKind::AnchorFailure(msg)) => { | |
1320 | anchor_failure(self.cx, &item, ori_link, dox, link_range, msg); | |
1321 | return None; | |
1322 | } | |
1323 | Err(ErrorKind::Resolve(box kind)) => Err(kind), | |
3dfed10e | 1324 | } |
1b1a35ee XL |
1325 | .and_then(|(res, fragment)| { |
1326 | // Constructors are picked up in the type namespace. | |
1327 | match res { | |
1328 | Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => { | |
1329 | Err(ResolutionFailure::WrongNamespace(res, TypeNS)) | |
1330 | } | |
1331 | _ => match (fragment, extra_fragment) { | |
1332 | (Some(fragment), Some(_)) => { | |
1333 | // Shouldn't happen but who knows? | |
1334 | Ok((res, Some(fragment))) | |
1335 | } | |
1336 | (fragment, None) | (None, fragment) => Ok((res, fragment)), | |
1337 | }, | |
1338 | } | |
1339 | }), | |
1340 | }; | |
3dfed10e | 1341 | |
1b1a35ee XL |
1342 | let len = candidates.iter().filter(|res| res.is_ok()).count(); |
1343 | ||
1344 | if len == 0 { | |
1345 | resolution_failure( | |
1346 | self, | |
1347 | &item, | |
1348 | path_str, | |
1349 | disambiguator, | |
1350 | dox, | |
1351 | link_range, | |
1352 | candidates.into_iter().filter_map(|res| res.err()).collect(), | |
3dfed10e | 1353 | ); |
1b1a35ee XL |
1354 | // this could just be a normal link |
1355 | return None; | |
3dfed10e | 1356 | } |
1b1a35ee XL |
1357 | |
1358 | if len == 1 { | |
1359 | Some(candidates.into_iter().filter_map(|res| res.ok()).next().unwrap()) | |
1360 | } else if len == 2 && is_derive_trait_collision(&candidates) { | |
1361 | Some(candidates.type_ns.unwrap()) | |
1362 | } else { | |
1363 | if is_derive_trait_collision(&candidates) { | |
1364 | candidates.macro_ns = Err(ResolutionFailure::Dummy); | |
3dfed10e | 1365 | } |
1b1a35ee XL |
1366 | // If we're reporting an ambiguity, don't mention the namespaces that failed |
1367 | let candidates = candidates.map(|candidate| candidate.ok().map(|(res, _)| res)); | |
1368 | ambiguity_error( | |
1369 | self.cx, | |
1370 | &item, | |
1371 | path_str, | |
1372 | dox, | |
1373 | link_range, | |
1374 | candidates.present_items().collect(), | |
1375 | ); | |
1376 | return None; | |
3dfed10e | 1377 | } |
1b1a35ee XL |
1378 | } |
1379 | Some(MacroNS) => { | |
29967ef6 | 1380 | match self.resolve_macro(path_str, base_node) { |
1b1a35ee XL |
1381 | Ok(res) => Some((res, extra_fragment)), |
1382 | Err(mut kind) => { | |
29967ef6 | 1383 | // `resolve_macro` only looks in the macro namespace. Try to give a better error if possible. |
1b1a35ee XL |
1384 | for &ns in &[TypeNS, ValueNS] { |
1385 | if let Some(res) = self.check_full_res( | |
1386 | ns, | |
1387 | path_str, | |
1388 | base_node, | |
1389 | ¤t_item, | |
1390 | &extra_fragment, | |
1391 | ) { | |
1392 | kind = ResolutionFailure::WrongNamespace(res, MacroNS); | |
1393 | break; | |
1394 | } | |
1395 | } | |
1396 | resolution_failure( | |
1397 | self, | |
1398 | &item, | |
1399 | path_str, | |
1400 | disambiguator, | |
1401 | dox, | |
1402 | link_range, | |
1403 | smallvec![kind], | |
1404 | ); | |
1405 | return None; | |
f035d41b XL |
1406 | } |
1407 | } | |
b7449926 XL |
1408 | } |
1409 | } | |
48663c56 | 1410 | } |
b7449926 XL |
1411 | } |
1412 | ||
3dfed10e | 1413 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
29967ef6 | 1414 | /// Disambiguators for a link. |
3dfed10e | 1415 | enum Disambiguator { |
29967ef6 XL |
1416 | /// `prim@` |
1417 | /// | |
1418 | /// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103> | |
3dfed10e | 1419 | Primitive, |
29967ef6 | 1420 | /// `struct@` or `f()` |
3dfed10e | 1421 | Kind(DefKind), |
29967ef6 | 1422 | /// `type@` |
3dfed10e XL |
1423 | Namespace(Namespace), |
1424 | } | |
1425 | ||
1426 | impl Disambiguator { | |
1b1a35ee XL |
1427 | /// The text that should be displayed when the path is rendered as HTML. |
1428 | /// | |
1429 | /// NOTE: `path` is not the original link given by the user, but a name suitable for passing to `resolve`. | |
1430 | fn display_for(&self, path: &str) -> String { | |
1431 | match self { | |
1432 | // FIXME: this will have different output if the user had `m!()` originally. | |
1433 | Self::Kind(DefKind::Macro(MacroKind::Bang)) => format!("{}!", path), | |
1434 | Self::Kind(DefKind::Fn) => format!("{}()", path), | |
1435 | _ => path.to_owned(), | |
1436 | } | |
1437 | } | |
1438 | ||
29967ef6 | 1439 | /// Given a link, parse and return `(disambiguator, path_str)` |
3dfed10e XL |
1440 | fn from_str(link: &str) -> Result<(Self, &str), ()> { |
1441 | use Disambiguator::{Kind, Namespace as NS, Primitive}; | |
1442 | ||
1443 | let find_suffix = || { | |
1444 | let suffixes = [ | |
1445 | ("!()", DefKind::Macro(MacroKind::Bang)), | |
1446 | ("()", DefKind::Fn), | |
1447 | ("!", DefKind::Macro(MacroKind::Bang)), | |
1448 | ]; | |
1449 | for &(suffix, kind) in &suffixes { | |
1450 | if link.ends_with(suffix) { | |
1451 | return Ok((Kind(kind), link.trim_end_matches(suffix))); | |
1452 | } | |
1453 | } | |
1454 | Err(()) | |
1455 | }; | |
1456 | ||
1457 | if let Some(idx) = link.find('@') { | |
1458 | let (prefix, rest) = link.split_at(idx); | |
1459 | let d = match prefix { | |
1460 | "struct" => Kind(DefKind::Struct), | |
1461 | "enum" => Kind(DefKind::Enum), | |
1462 | "trait" => Kind(DefKind::Trait), | |
1463 | "union" => Kind(DefKind::Union), | |
1464 | "module" | "mod" => Kind(DefKind::Mod), | |
1465 | "const" | "constant" => Kind(DefKind::Const), | |
1466 | "static" => Kind(DefKind::Static), | |
1467 | "function" | "fn" | "method" => Kind(DefKind::Fn), | |
1468 | "derive" => Kind(DefKind::Macro(MacroKind::Derive)), | |
1469 | "type" => NS(Namespace::TypeNS), | |
1470 | "value" => NS(Namespace::ValueNS), | |
1471 | "macro" => NS(Namespace::MacroNS), | |
1472 | "prim" | "primitive" => Primitive, | |
1473 | _ => return find_suffix(), | |
1474 | }; | |
1475 | Ok((d, &rest[1..])) | |
1476 | } else { | |
1477 | find_suffix() | |
1478 | } | |
1479 | } | |
1480 | ||
1b1a35ee XL |
1481 | /// WARNING: panics on `Res::Err` |
1482 | fn from_res(res: Res) -> Self { | |
1483 | match res { | |
1484 | Res::Def(kind, _) => Disambiguator::Kind(kind), | |
1485 | Res::PrimTy(_) => Disambiguator::Primitive, | |
1486 | _ => Disambiguator::Namespace(res.ns().expect("can't call `from_res` on Res::err")), | |
1487 | } | |
1488 | } | |
1489 | ||
29967ef6 | 1490 | /// Used for error reporting. |
1b1a35ee | 1491 | fn suggestion(self) -> Suggestion { |
3dfed10e | 1492 | let kind = match self { |
1b1a35ee | 1493 | Disambiguator::Primitive => return Suggestion::Prefix("prim"), |
3dfed10e XL |
1494 | Disambiguator::Kind(kind) => kind, |
1495 | Disambiguator::Namespace(_) => panic!("display_for cannot be used on namespaces"), | |
1496 | }; | |
1497 | if kind == DefKind::Macro(MacroKind::Bang) { | |
1b1a35ee | 1498 | return Suggestion::Macro; |
3dfed10e | 1499 | } else if kind == DefKind::Fn || kind == DefKind::AssocFn { |
1b1a35ee | 1500 | return Suggestion::Function; |
3dfed10e | 1501 | } |
1b1a35ee | 1502 | |
3dfed10e XL |
1503 | let prefix = match kind { |
1504 | DefKind::Struct => "struct", | |
1505 | DefKind::Enum => "enum", | |
1506 | DefKind::Trait => "trait", | |
1507 | DefKind::Union => "union", | |
1508 | DefKind::Mod => "mod", | |
1509 | DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst => { | |
1510 | "const" | |
1511 | } | |
1512 | DefKind::Static => "static", | |
1513 | DefKind::Macro(MacroKind::Derive) => "derive", | |
1514 | // Now handle things that don't have a specific disambiguator | |
1515 | _ => match kind | |
1516 | .ns() | |
1517 | .expect("tried to calculate a disambiguator for a def without a namespace?") | |
1518 | { | |
1519 | Namespace::TypeNS => "type", | |
1520 | Namespace::ValueNS => "value", | |
1521 | Namespace::MacroNS => "macro", | |
1522 | }, | |
1523 | }; | |
1b1a35ee XL |
1524 | |
1525 | Suggestion::Prefix(prefix) | |
3dfed10e XL |
1526 | } |
1527 | ||
1528 | fn ns(self) -> Namespace { | |
1529 | match self { | |
1530 | Self::Namespace(n) => n, | |
1531 | Self::Kind(k) => { | |
1532 | k.ns().expect("only DefKinds with a valid namespace can be disambiguators") | |
1533 | } | |
1534 | Self::Primitive => TypeNS, | |
1535 | } | |
1536 | } | |
1537 | ||
1538 | fn article(self) -> &'static str { | |
1539 | match self { | |
1540 | Self::Namespace(_) => panic!("article() doesn't make sense for namespaces"), | |
1541 | Self::Kind(k) => k.article(), | |
1542 | Self::Primitive => "a", | |
1543 | } | |
1544 | } | |
1545 | ||
1546 | fn descr(self) -> &'static str { | |
1547 | match self { | |
1548 | Self::Namespace(n) => n.descr(), | |
1549 | // HACK(jynelson): by looking at the source I saw the DefId we pass | |
1550 | // for `expected.descr()` doesn't matter, since it's not a crate | |
1551 | Self::Kind(k) => k.descr(DefId::local(hir::def_id::DefIndex::from_usize(0))), | |
1552 | Self::Primitive => "builtin type", | |
1553 | } | |
1554 | } | |
1555 | } | |
1556 | ||
29967ef6 | 1557 | /// A suggestion to show in a diagnostic. |
1b1a35ee | 1558 | enum Suggestion { |
29967ef6 | 1559 | /// `struct@` |
1b1a35ee | 1560 | Prefix(&'static str), |
29967ef6 | 1561 | /// `f()` |
1b1a35ee | 1562 | Function, |
29967ef6 | 1563 | /// `m!` |
1b1a35ee XL |
1564 | Macro, |
1565 | } | |
1566 | ||
1567 | impl Suggestion { | |
1568 | fn descr(&self) -> Cow<'static, str> { | |
1569 | match self { | |
1570 | Self::Prefix(x) => format!("prefix with `{}@`", x).into(), | |
1571 | Self::Function => "add parentheses".into(), | |
1572 | Self::Macro => "add an exclamation mark".into(), | |
1573 | } | |
1574 | } | |
1575 | ||
1576 | fn as_help(&self, path_str: &str) -> String { | |
1577 | // FIXME: if this is an implied shortcut link, it's bad style to suggest `@` | |
1578 | match self { | |
1579 | Self::Prefix(prefix) => format!("{}@{}", prefix, path_str), | |
1580 | Self::Function => format!("{}()", path_str), | |
1581 | Self::Macro => format!("{}!", path_str), | |
1582 | } | |
1583 | } | |
1584 | } | |
1585 | ||
3dfed10e XL |
1586 | /// Reports a diagnostic for an intra-doc link. |
1587 | /// | |
1588 | /// If no link range is provided, or the source span of the link cannot be determined, the span of | |
1589 | /// the entire documentation block is used for the lint. If a range is provided but the span | |
1590 | /// calculation fails, a note is added to the diagnostic pointing to the link in the markdown. | |
1591 | /// | |
1592 | /// The `decorate` callback is invoked in all cases to allow further customization of the | |
1593 | /// diagnostic before emission. If the span of the link was able to be determined, the second | |
1594 | /// parameter of the callback will contain it, and the primary span of the diagnostic will be set | |
1595 | /// to it. | |
1596 | fn report_diagnostic( | |
532ac7d7 | 1597 | cx: &DocContext<'_>, |
1b1a35ee | 1598 | lint: &'static Lint, |
3dfed10e | 1599 | msg: &str, |
48663c56 | 1600 | item: &Item, |
b7449926 | 1601 | dox: &str, |
1b1a35ee | 1602 | link_range: &Option<Range<usize>>, |
3dfed10e | 1603 | decorate: impl FnOnce(&mut DiagnosticBuilder<'_>, Option<rustc_span::Span>), |
b7449926 | 1604 | ) { |
48663c56 XL |
1605 | let hir_id = match cx.as_local_hir_id(item.def_id) { |
1606 | Some(hir_id) => hir_id, | |
1607 | None => { | |
1608 | // If non-local, no need to check anything. | |
3dfed10e | 1609 | info!("ignoring warning from parent crate: {}", msg); |
48663c56 XL |
1610 | return; |
1611 | } | |
1612 | }; | |
3dfed10e | 1613 | |
48663c56 | 1614 | let attrs = &item.attrs; |
416331ca | 1615 | let sp = span_of_attrs(attrs).unwrap_or(item.source.span()); |
b7449926 | 1616 | |
1b1a35ee | 1617 | cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |lint| { |
3dfed10e XL |
1618 | let mut diag = lint.build(msg); |
1619 | ||
1620 | let span = link_range | |
1621 | .as_ref() | |
1622 | .and_then(|range| super::source_span_for_markdown_range(cx, dox, range, attrs)); | |
1623 | ||
1624 | if let Some(link_range) = link_range { | |
1625 | if let Some(sp) = span { | |
1626 | diag.set_span(sp); | |
1627 | } else { | |
1628 | // blah blah blah\nblah\nblah [blah] blah blah\nblah blah | |
1629 | // ^ ~~~~ | |
1630 | // | link_range | |
1631 | // last_new_line_offset | |
1632 | let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); | |
1633 | let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); | |
1634 | ||
1635 | // Print the line containing the `link_range` and manually mark it with '^'s. | |
1636 | diag.note(&format!( | |
1637 | "the link appears in this line:\n\n{line}\n\ | |
1b1a35ee | 1638 | {indicator: <before$}{indicator:^<found$}", |
3dfed10e XL |
1639 | line = line, |
1640 | indicator = "", | |
1641 | before = link_range.start - last_new_line_offset, | |
1642 | found = link_range.len(), | |
1643 | )); | |
74b04a01 | 1644 | } |
3dfed10e XL |
1645 | } |
1646 | ||
1647 | decorate(&mut diag, span); | |
1648 | ||
1649 | diag.emit(); | |
1650 | }); | |
b7449926 XL |
1651 | } |
1652 | ||
29967ef6 XL |
1653 | /// Reports a link that failed to resolve. |
1654 | /// | |
1655 | /// This also tries to resolve any intermediate path segments that weren't | |
1656 | /// handled earlier. For example, if passed `Item::Crate(std)` and `path_str` | |
1657 | /// `std::io::Error::x`, this will resolve `std::io::Error`. | |
60c5eb7d | 1658 | fn resolution_failure( |
1b1a35ee | 1659 | collector: &LinkCollector<'_, '_>, |
60c5eb7d XL |
1660 | item: &Item, |
1661 | path_str: &str, | |
1b1a35ee | 1662 | disambiguator: Option<Disambiguator>, |
60c5eb7d XL |
1663 | dox: &str, |
1664 | link_range: Option<Range<usize>>, | |
1b1a35ee | 1665 | kinds: SmallVec<[ResolutionFailure<'_>; 3]>, |
60c5eb7d | 1666 | ) { |
3dfed10e | 1667 | report_diagnostic( |
1b1a35ee XL |
1668 | collector.cx, |
1669 | BROKEN_INTRA_DOC_LINKS, | |
3dfed10e | 1670 | &format!("unresolved link to `{}`", path_str), |
60c5eb7d | 1671 | item, |
60c5eb7d | 1672 | dox, |
1b1a35ee | 1673 | &link_range, |
3dfed10e | 1674 | |diag, sp| { |
1b1a35ee XL |
1675 | let item = |res: Res| { |
1676 | format!( | |
1677 | "the {} `{}`", | |
1678 | res.descr(), | |
1679 | collector.cx.tcx.item_name(res.def_id()).to_string() | |
1680 | ) | |
1681 | }; | |
1682 | let assoc_item_not_allowed = |res: Res| { | |
1683 | let def_id = res.def_id(); | |
1684 | let name = collector.cx.tcx.item_name(def_id); | |
1685 | format!( | |
1686 | "`{}` is {} {}, not a module or type, and cannot have associated items", | |
1687 | name, | |
1688 | res.article(), | |
1689 | res.descr() | |
1690 | ) | |
1691 | }; | |
1692 | // ignore duplicates | |
1693 | let mut variants_seen = SmallVec::<[_; 3]>::new(); | |
1694 | for mut failure in kinds { | |
1695 | let variant = std::mem::discriminant(&failure); | |
1696 | if variants_seen.contains(&variant) { | |
1697 | continue; | |
1698 | } | |
1699 | variants_seen.push(variant); | |
1700 | ||
1701 | if let ResolutionFailure::NotResolved { module_id, partial_res, unresolved } = | |
1702 | &mut failure | |
1703 | { | |
1704 | use DefKind::*; | |
1705 | ||
1706 | let module_id = *module_id; | |
1707 | // FIXME(jynelson): this might conflict with my `Self` fix in #76467 | |
1708 | // FIXME: maybe use itertools `collect_tuple` instead? | |
1709 | fn split(path: &str) -> Option<(&str, &str)> { | |
1710 | let mut splitter = path.rsplitn(2, "::"); | |
1711 | splitter.next().and_then(|right| splitter.next().map(|left| (left, right))) | |
1712 | } | |
3dfed10e | 1713 | |
1b1a35ee XL |
1714 | // Check if _any_ parent of the path gets resolved. |
1715 | // If so, report it and say the first which failed; if not, say the first path segment didn't resolve. | |
1716 | let mut name = path_str; | |
1717 | 'outer: loop { | |
1718 | let (start, end) = if let Some(x) = split(name) { | |
1719 | x | |
1720 | } else { | |
1721 | // avoid bug that marked [Quux::Z] as missing Z, not Quux | |
1722 | if partial_res.is_none() { | |
1723 | *unresolved = name.into(); | |
1724 | } | |
1725 | break; | |
1726 | }; | |
1727 | name = start; | |
1728 | for &ns in &[TypeNS, ValueNS, MacroNS] { | |
1729 | if let Some(res) = | |
1730 | collector.check_full_res(ns, &start, module_id, &None, &None) | |
1731 | { | |
1732 | debug!("found partial_res={:?}", res); | |
1733 | *partial_res = Some(res); | |
1734 | *unresolved = end.into(); | |
1735 | break 'outer; | |
1736 | } | |
1737 | } | |
1738 | *unresolved = end.into(); | |
1739 | } | |
1740 | ||
1741 | let last_found_module = match *partial_res { | |
1742 | Some(Res::Def(DefKind::Mod, id)) => Some(id), | |
1743 | None => Some(module_id), | |
1744 | _ => None, | |
1745 | }; | |
1746 | // See if this was a module: `[path]` or `[std::io::nope]` | |
1747 | if let Some(module) = last_found_module { | |
29967ef6 XL |
1748 | let note = if partial_res.is_some() { |
1749 | // Part of the link resolved; e.g. `std::io::nonexistent` | |
1750 | let module_name = collector.cx.tcx.item_name(module); | |
1751 | format!("no item named `{}` in module `{}`", unresolved, module_name) | |
1752 | } else { | |
1753 | // None of the link resolved; e.g. `Notimported` | |
1754 | format!("no item named `{}` in scope", unresolved) | |
1755 | }; | |
1b1a35ee XL |
1756 | if let Some(span) = sp { |
1757 | diag.span_label(span, ¬e); | |
1758 | } else { | |
1759 | diag.note(¬e); | |
1760 | } | |
29967ef6 | 1761 | |
1b1a35ee XL |
1762 | // If the link has `::` in it, assume it was meant to be an intra-doc link. |
1763 | // Otherwise, the `[]` might be unrelated. | |
1764 | // FIXME: don't show this for autolinks (`<>`), `()` style links, or reference links | |
1765 | if !path_str.contains("::") { | |
1766 | diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#); | |
1767 | } | |
29967ef6 | 1768 | |
1b1a35ee XL |
1769 | continue; |
1770 | } | |
1771 | ||
1772 | // Otherwise, it must be an associated item or variant | |
1773 | let res = partial_res.expect("None case was handled by `last_found_module`"); | |
1774 | let diagnostic_name; | |
1775 | let (kind, name) = match res { | |
1776 | Res::Def(kind, def_id) => { | |
1777 | diagnostic_name = collector.cx.tcx.item_name(def_id).as_str(); | |
1778 | (Some(kind), &*diagnostic_name) | |
1779 | } | |
1780 | Res::PrimTy(ty) => (None, ty.name_str()), | |
1781 | _ => unreachable!("only ADTs and primitives are in scope at module level"), | |
1782 | }; | |
1783 | let path_description = if let Some(kind) = kind { | |
1784 | match kind { | |
1785 | Mod | ForeignMod => "inner item", | |
1786 | Struct => "field or associated item", | |
1787 | Enum | Union => "variant or associated item", | |
1788 | Variant | |
1789 | | Field | |
1790 | | Closure | |
1791 | | Generator | |
1792 | | AssocTy | |
1793 | | AssocConst | |
1794 | | AssocFn | |
1795 | | Fn | |
1796 | | Macro(_) | |
1797 | | Const | |
1798 | | ConstParam | |
1799 | | ExternCrate | |
1800 | | Use | |
1801 | | LifetimeParam | |
1802 | | Ctor(_, _) | |
1803 | | AnonConst => { | |
1804 | let note = assoc_item_not_allowed(res); | |
1805 | if let Some(span) = sp { | |
1806 | diag.span_label(span, ¬e); | |
1807 | } else { | |
1808 | diag.note(¬e); | |
1809 | } | |
1810 | return; | |
1811 | } | |
1812 | Trait | TyAlias | ForeignTy | OpaqueTy | TraitAlias | TyParam | |
1813 | | Static => "associated item", | |
1814 | Impl | GlobalAsm => unreachable!("not a path"), | |
1815 | } | |
1816 | } else { | |
1817 | "associated item" | |
1818 | }; | |
1819 | let note = format!( | |
1820 | "the {} `{}` has no {} named `{}`", | |
1821 | res.descr(), | |
1822 | name, | |
1823 | disambiguator.map_or(path_description, |d| d.descr()), | |
1824 | unresolved, | |
1825 | ); | |
1826 | if let Some(span) = sp { | |
1827 | diag.span_label(span, ¬e); | |
1828 | } else { | |
1829 | diag.note(¬e); | |
1830 | } | |
1831 | ||
1832 | continue; | |
1833 | } | |
1834 | let note = match failure { | |
1835 | ResolutionFailure::NotResolved { .. } => unreachable!("handled above"), | |
1836 | ResolutionFailure::Dummy => continue, | |
1837 | ResolutionFailure::WrongNamespace(res, expected_ns) => { | |
1838 | if let Res::Def(kind, _) = res { | |
1839 | let disambiguator = Disambiguator::Kind(kind); | |
1840 | suggest_disambiguator( | |
1841 | disambiguator, | |
1842 | diag, | |
1843 | path_str, | |
1844 | dox, | |
1845 | sp, | |
1846 | &link_range, | |
1847 | ) | |
1848 | } | |
1849 | ||
1850 | format!( | |
1851 | "this link resolves to {}, which is not in the {} namespace", | |
1852 | item(res), | |
1853 | expected_ns.descr() | |
1854 | ) | |
1855 | } | |
1856 | ResolutionFailure::NoParentItem => { | |
1857 | diag.level = rustc_errors::Level::Bug; | |
1858 | "all intra doc links should have a parent item".to_owned() | |
1859 | } | |
29967ef6 XL |
1860 | ResolutionFailure::MalformedGenerics(variant) => match variant { |
1861 | MalformedGenerics::UnbalancedAngleBrackets => { | |
1862 | String::from("unbalanced angle brackets") | |
1863 | } | |
1864 | MalformedGenerics::MissingType => { | |
1865 | String::from("missing type for generic parameters") | |
1866 | } | |
1867 | MalformedGenerics::HasFullyQualifiedSyntax => { | |
1868 | diag.note("see https://github.com/rust-lang/rust/issues/74563 for more information"); | |
1869 | String::from("fully-qualified syntax is unsupported") | |
1870 | } | |
1871 | MalformedGenerics::InvalidPathSeparator => { | |
1872 | String::from("has invalid path separator") | |
1873 | } | |
1874 | MalformedGenerics::TooManyAngleBrackets => { | |
1875 | String::from("too many angle brackets") | |
1876 | } | |
1877 | MalformedGenerics::EmptyAngleBrackets => { | |
1878 | String::from("empty angle brackets") | |
1879 | } | |
1880 | }, | |
1b1a35ee XL |
1881 | }; |
1882 | if let Some(span) = sp { | |
1883 | diag.span_label(span, ¬e); | |
1884 | } else { | |
1885 | diag.note(¬e); | |
1886 | } | |
1887 | } | |
3dfed10e | 1888 | }, |
60c5eb7d XL |
1889 | ); |
1890 | } | |
1891 | ||
29967ef6 | 1892 | /// Report an anchor failure. |
60c5eb7d XL |
1893 | fn anchor_failure( |
1894 | cx: &DocContext<'_>, | |
1895 | item: &Item, | |
1896 | path_str: &str, | |
1897 | dox: &str, | |
1898 | link_range: Option<Range<usize>>, | |
3dfed10e | 1899 | failure: AnchorFailure, |
60c5eb7d | 1900 | ) { |
3dfed10e XL |
1901 | let msg = match failure { |
1902 | AnchorFailure::MultipleAnchors => format!("`{}` contains multiple anchors", path_str), | |
1b1a35ee XL |
1903 | AnchorFailure::RustdocAnchorConflict(res) => format!( |
1904 | "`{}` contains an anchor, but links to {kind}s are already anchored", | |
1905 | path_str, | |
1906 | kind = res.descr(), | |
1907 | ), | |
3dfed10e XL |
1908 | }; |
1909 | ||
1b1a35ee | 1910 | report_diagnostic(cx, BROKEN_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| { |
3dfed10e XL |
1911 | if let Some(sp) = sp { |
1912 | diag.span_label(sp, "contains invalid anchor"); | |
1913 | } | |
1914 | }); | |
60c5eb7d XL |
1915 | } |
1916 | ||
29967ef6 | 1917 | /// Report an ambiguity error, where there were multiple possible resolutions. |
532ac7d7 XL |
1918 | fn ambiguity_error( |
1919 | cx: &DocContext<'_>, | |
48663c56 | 1920 | item: &Item, |
532ac7d7 XL |
1921 | path_str: &str, |
1922 | dox: &str, | |
1923 | link_range: Option<Range<usize>>, | |
1b1a35ee | 1924 | candidates: Vec<Res>, |
532ac7d7 | 1925 | ) { |
3dfed10e XL |
1926 | let mut msg = format!("`{}` is ", path_str); |
1927 | ||
1928 | match candidates.as_slice() { | |
1b1a35ee | 1929 | [first_def, second_def] => { |
3dfed10e XL |
1930 | msg += &format!( |
1931 | "both {} {} and {} {}", | |
1932 | first_def.article(), | |
1933 | first_def.descr(), | |
1934 | second_def.article(), | |
1935 | second_def.descr(), | |
1936 | ); | |
48663c56 | 1937 | } |
3dfed10e XL |
1938 | _ => { |
1939 | let mut candidates = candidates.iter().peekable(); | |
1b1a35ee | 1940 | while let Some(res) = candidates.next() { |
3dfed10e XL |
1941 | if candidates.peek().is_some() { |
1942 | msg += &format!("{} {}, ", res.article(), res.descr()); | |
1943 | } else { | |
1944 | msg += &format!("and {} {}", res.article(), res.descr()); | |
74b04a01 XL |
1945 | } |
1946 | } | |
3dfed10e XL |
1947 | } |
1948 | } | |
74b04a01 | 1949 | |
1b1a35ee | 1950 | report_diagnostic(cx, BROKEN_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| { |
3dfed10e XL |
1951 | if let Some(sp) = sp { |
1952 | diag.span_label(sp, "ambiguous link"); | |
1b1a35ee XL |
1953 | } else { |
1954 | diag.note("ambiguous link"); | |
1955 | } | |
74b04a01 | 1956 | |
1b1a35ee XL |
1957 | for res in candidates { |
1958 | let disambiguator = Disambiguator::from_res(res); | |
1959 | suggest_disambiguator(disambiguator, diag, path_str, dox, sp, &link_range); | |
1960 | } | |
1961 | }); | |
1962 | } | |
532ac7d7 | 1963 | |
29967ef6 XL |
1964 | /// In case of an ambiguity or mismatched disambiguator, suggest the correct |
1965 | /// disambiguator. | |
1b1a35ee XL |
1966 | fn suggest_disambiguator( |
1967 | disambiguator: Disambiguator, | |
1968 | diag: &mut DiagnosticBuilder<'_>, | |
1969 | path_str: &str, | |
1970 | dox: &str, | |
1971 | sp: Option<rustc_span::Span>, | |
1972 | link_range: &Option<Range<usize>>, | |
1973 | ) { | |
1974 | let suggestion = disambiguator.suggestion(); | |
1975 | let help = format!("to link to the {}, {}", disambiguator.descr(), suggestion.descr()); | |
3dfed10e | 1976 | |
1b1a35ee XL |
1977 | if let Some(sp) = sp { |
1978 | let link_range = link_range.as_ref().expect("must have a link range if we have a span"); | |
1979 | let msg = if dox.bytes().nth(link_range.start) == Some(b'`') { | |
1980 | format!("`{}`", suggestion.as_help(path_str)) | |
1981 | } else { | |
1982 | suggestion.as_help(path_str) | |
1983 | }; | |
3dfed10e | 1984 | |
1b1a35ee XL |
1985 | diag.span_suggestion(sp, &help, msg, Applicability::MaybeIncorrect); |
1986 | } else { | |
1987 | diag.help(&format!("{}: {}", help, suggestion.as_help(path_str))); | |
1988 | } | |
3dfed10e XL |
1989 | } |
1990 | ||
29967ef6 | 1991 | /// Report a link from a public item to a private one. |
3dfed10e XL |
1992 | fn privacy_error( |
1993 | cx: &DocContext<'_>, | |
1994 | item: &Item, | |
1995 | path_str: &str, | |
1996 | dox: &str, | |
1997 | link_range: Option<Range<usize>>, | |
1998 | ) { | |
1999 | let item_name = item.name.as_deref().unwrap_or("<unknown>"); | |
2000 | let msg = | |
2001 | format!("public documentation for `{}` links to private item `{}`", item_name, path_str); | |
2002 | ||
1b1a35ee | 2003 | report_diagnostic(cx, PRIVATE_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| { |
3dfed10e XL |
2004 | if let Some(sp) = sp { |
2005 | diag.span_label(sp, "this item is private"); | |
2006 | } | |
2007 | ||
2008 | let note_msg = if cx.render_options.document_private { | |
2009 | "this link resolves only because you passed `--document-private-items`, but will break without" | |
2010 | } else { | |
2011 | "this link will resolve properly if you pass `--document-private-items`" | |
2012 | }; | |
2013 | diag.note(note_msg); | |
2014 | }); | |
b7449926 XL |
2015 | } |
2016 | ||
48663c56 | 2017 | /// Given an enum variant's res, return the res of its enum and the associated fragment. |
60c5eb7d XL |
2018 | fn handle_variant( |
2019 | cx: &DocContext<'_>, | |
2020 | res: Res, | |
2021 | extra_fragment: &Option<String>, | |
1b1a35ee | 2022 | ) -> Result<(Res, Option<String>), ErrorKind<'static>> { |
ba9703b0 | 2023 | use rustc_middle::ty::DefIdTree; |
b7449926 | 2024 | |
60c5eb7d | 2025 | if extra_fragment.is_some() { |
1b1a35ee | 2026 | return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res))); |
60c5eb7d | 2027 | } |
29967ef6 XL |
2028 | cx.tcx |
2029 | .parent(res.def_id()) | |
2030 | .map(|parent| { | |
2031 | let parent_def = Res::Def(DefKind::Enum, parent); | |
2032 | let variant = cx.tcx.expect_variant_res(res); | |
2033 | (parent_def, Some(format!("variant.{}", variant.ident.name))) | |
2034 | }) | |
2035 | .ok_or_else(|| ResolutionFailure::NoParentItem.into()) | |
b7449926 XL |
2036 | } |
2037 | ||
29967ef6 XL |
2038 | // FIXME: At this point, this is basically a copy of the PrimitiveTypeTable |
2039 | const PRIMITIVES: &[(Symbol, Res)] = &[ | |
2040 | (sym::u8, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U8))), | |
2041 | (sym::u16, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U16))), | |
2042 | (sym::u32, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U32))), | |
2043 | (sym::u64, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U64))), | |
2044 | (sym::u128, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U128))), | |
2045 | (sym::usize, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::Usize))), | |
2046 | (sym::i8, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I8))), | |
2047 | (sym::i16, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I16))), | |
2048 | (sym::i32, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I32))), | |
2049 | (sym::i64, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I64))), | |
2050 | (sym::i128, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I128))), | |
2051 | (sym::isize, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::Isize))), | |
2052 | (sym::f32, Res::PrimTy(hir::PrimTy::Float(rustc_ast::FloatTy::F32))), | |
2053 | (sym::f64, Res::PrimTy(hir::PrimTy::Float(rustc_ast::FloatTy::F64))), | |
2054 | (sym::str, Res::PrimTy(hir::PrimTy::Str)), | |
2055 | (sym::bool, Res::PrimTy(hir::PrimTy::Bool)), | |
2056 | (sym::char, Res::PrimTy(hir::PrimTy::Char)), | |
b7449926 XL |
2057 | ]; |
2058 | ||
29967ef6 XL |
2059 | /// Resolve a primitive type or value. |
2060 | fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> { | |
2061 | is_bool_value(path_str, ns).or_else(|| { | |
2062 | if ns == TypeNS { | |
2063 | // FIXME: this should be replaced by a lookup in PrimitiveTypeTable | |
2064 | let maybe_primitive = Symbol::intern(path_str); | |
2065 | PRIMITIVES.iter().find(|x| x.0 == maybe_primitive).copied() | |
2066 | } else { | |
2067 | None | |
2068 | } | |
2069 | }) | |
2070 | } | |
2071 | ||
2072 | /// Resolve a primitive value. | |
2073 | fn is_bool_value(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> { | |
2074 | if ns == TypeNS && (path_str == "true" || path_str == "false") { | |
2075 | Some((sym::bool, Res::PrimTy(hir::PrimTy::Bool))) | |
3dfed10e XL |
2076 | } else { |
2077 | None | |
2078 | } | |
b7449926 | 2079 | } |
9fa01778 | 2080 | |
29967ef6 XL |
2081 | fn strip_generics_from_path(path_str: &str) -> Result<String, ResolutionFailure<'static>> { |
2082 | let mut stripped_segments = vec![]; | |
2083 | let mut path = path_str.chars().peekable(); | |
2084 | let mut segment = Vec::new(); | |
2085 | ||
2086 | while let Some(chr) = path.next() { | |
2087 | match chr { | |
2088 | ':' => { | |
2089 | if path.next_if_eq(&':').is_some() { | |
2090 | let stripped_segment = | |
2091 | strip_generics_from_path_segment(mem::take(&mut segment))?; | |
2092 | if !stripped_segment.is_empty() { | |
2093 | stripped_segments.push(stripped_segment); | |
2094 | } | |
2095 | } else { | |
2096 | return Err(ResolutionFailure::MalformedGenerics( | |
2097 | MalformedGenerics::InvalidPathSeparator, | |
2098 | )); | |
2099 | } | |
2100 | } | |
2101 | '<' => { | |
2102 | segment.push(chr); | |
2103 | ||
2104 | match path.next() { | |
2105 | Some('<') => { | |
2106 | return Err(ResolutionFailure::MalformedGenerics( | |
2107 | MalformedGenerics::TooManyAngleBrackets, | |
2108 | )); | |
2109 | } | |
2110 | Some('>') => { | |
2111 | return Err(ResolutionFailure::MalformedGenerics( | |
2112 | MalformedGenerics::EmptyAngleBrackets, | |
2113 | )); | |
2114 | } | |
2115 | Some(chr) => { | |
2116 | segment.push(chr); | |
2117 | ||
2118 | while let Some(chr) = path.next_if(|c| *c != '>') { | |
2119 | segment.push(chr); | |
2120 | } | |
2121 | } | |
2122 | None => break, | |
2123 | } | |
2124 | } | |
2125 | _ => segment.push(chr), | |
2126 | } | |
2127 | debug!("raw segment: {:?}", segment); | |
2128 | } | |
2129 | ||
2130 | if !segment.is_empty() { | |
2131 | let stripped_segment = strip_generics_from_path_segment(segment)?; | |
2132 | if !stripped_segment.is_empty() { | |
2133 | stripped_segments.push(stripped_segment); | |
2134 | } | |
2135 | } | |
2136 | ||
2137 | debug!("path_str: {:?}\nstripped segments: {:?}", path_str, &stripped_segments); | |
2138 | ||
2139 | let stripped_path = stripped_segments.join("::"); | |
2140 | ||
2141 | if !stripped_path.is_empty() { | |
2142 | Ok(stripped_path) | |
2143 | } else { | |
2144 | Err(ResolutionFailure::MalformedGenerics(MalformedGenerics::MissingType)) | |
2145 | } | |
2146 | } | |
2147 | ||
2148 | fn strip_generics_from_path_segment( | |
2149 | segment: Vec<char>, | |
2150 | ) -> Result<String, ResolutionFailure<'static>> { | |
2151 | let mut stripped_segment = String::new(); | |
2152 | let mut param_depth = 0; | |
2153 | ||
2154 | let mut latest_generics_chunk = String::new(); | |
2155 | ||
2156 | for c in segment { | |
2157 | if c == '<' { | |
2158 | param_depth += 1; | |
2159 | latest_generics_chunk.clear(); | |
2160 | } else if c == '>' { | |
2161 | param_depth -= 1; | |
2162 | if latest_generics_chunk.contains(" as ") { | |
2163 | // The segment tries to use fully-qualified syntax, which is currently unsupported. | |
2164 | // Give a helpful error message instead of completely ignoring the angle brackets. | |
2165 | return Err(ResolutionFailure::MalformedGenerics( | |
2166 | MalformedGenerics::HasFullyQualifiedSyntax, | |
2167 | )); | |
2168 | } | |
2169 | } else { | |
2170 | if param_depth == 0 { | |
2171 | stripped_segment.push(c); | |
2172 | } else { | |
2173 | latest_generics_chunk.push(c); | |
2174 | } | |
2175 | } | |
2176 | } | |
2177 | ||
2178 | if param_depth == 0 { | |
2179 | Ok(stripped_segment) | |
2180 | } else { | |
2181 | // The segment has unbalanced angle brackets, e.g. `Vec<T` or `Vec<T>>` | |
2182 | Err(ResolutionFailure::MalformedGenerics(MalformedGenerics::UnbalancedAngleBrackets)) | |
2183 | } | |
9fa01778 | 2184 | } |