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