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