|
1 | | -use rustc::lint as lint; |
2 | | -use rustc::hir; |
| 1 | +use errors::Applicability; |
3 | 2 | use rustc::hir::def::Def; |
4 | 3 | use rustc::hir::def_id::DefId; |
| 4 | +use rustc::hir; |
| 5 | +use rustc::lint as lint; |
5 | 6 | use rustc::ty; |
6 | 7 | use syntax; |
7 | 8 | use syntax::ast::{self, Ident}; |
@@ -53,6 +54,13 @@ struct LinkCollector<'a, 'tcx> { |
53 | 54 | is_nightly_build: bool, |
54 | 55 | } |
55 | 56 |
|
| 57 | +#[derive(Debug, Copy, Clone)] |
| 58 | +enum Namespace { |
| 59 | + Type, |
| 60 | + Value, |
| 61 | + Macro, |
| 62 | +} |
| 63 | + |
56 | 64 | impl<'a, 'tcx> LinkCollector<'a, 'tcx> { |
57 | 65 | fn new(cx: &'a DocContext<'tcx>) -> Self { |
58 | 66 | LinkCollector { |
@@ -345,57 +353,52 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> { |
345 | 353 | } |
346 | 354 | PathKind::Unknown => { |
347 | 355 | // Try everything! |
| 356 | + let mut candidates = vec![]; |
| 357 | + |
348 | 358 | if let Some(macro_def) = macro_resolve(cx, path_str) { |
349 | | - if let Ok(type_def) = |
350 | | - self.resolve(path_str, false, ¤t_item, parent_node) |
351 | | - { |
352 | | - let (type_kind, article, type_disambig) |
353 | | - = type_ns_kind(type_def.0, path_str); |
354 | | - ambiguity_error(cx, &item.attrs, path_str, |
355 | | - article, type_kind, &type_disambig, |
356 | | - "a", "macro", &format!("macro@{}", path_str)); |
357 | | - continue; |
358 | | - } else if let Ok(value_def) = |
359 | | - self.resolve(path_str, true, ¤t_item, parent_node) |
360 | | - { |
361 | | - let (value_kind, value_disambig) |
362 | | - = value_ns_kind(value_def.0, path_str) |
363 | | - .expect("struct and mod cases should have been \ |
364 | | - caught in previous branch"); |
365 | | - ambiguity_error(cx, &item.attrs, path_str, |
366 | | - "a", value_kind, &value_disambig, |
367 | | - "a", "macro", &format!("macro@{}", path_str)); |
368 | | - } |
369 | | - (macro_def, None) |
370 | | - } else if let Ok(type_def) = |
| 359 | + candidates.push(((macro_def, None), Namespace::Macro)); |
| 360 | + } |
| 361 | + |
| 362 | + if let Ok(type_def) = |
371 | 363 | self.resolve(path_str, false, ¤t_item, parent_node) |
372 | 364 | { |
373 | | - // It is imperative we search for not-a-value first |
374 | | - // Otherwise we will find struct ctors for when we are looking |
375 | | - // for structs, and the link won't work if there is something in |
376 | | - // both namespaces. |
377 | | - if let Ok(value_def) = |
378 | | - self.resolve(path_str, true, ¤t_item, parent_node) |
379 | | - { |
380 | | - let kind = value_ns_kind(value_def.0, path_str); |
381 | | - if let Some((value_kind, value_disambig)) = kind { |
382 | | - let (type_kind, article, type_disambig) |
383 | | - = type_ns_kind(type_def.0, path_str); |
384 | | - ambiguity_error(cx, &item.attrs, path_str, |
385 | | - article, type_kind, &type_disambig, |
386 | | - "a", value_kind, &value_disambig); |
387 | | - continue; |
388 | | - } |
389 | | - } |
390 | | - type_def |
391 | | - } else if let Ok(value_def) = |
| 365 | + candidates.push((type_def, Namespace::Type)); |
| 366 | + } |
| 367 | + |
| 368 | + if let Ok(value_def) = |
392 | 369 | self.resolve(path_str, true, ¤t_item, parent_node) |
393 | 370 | { |
394 | | - value_def |
395 | | - } else { |
| 371 | + // Structs, variants, and mods exist in both namespaces, skip them. |
| 372 | + match value_def.0 { |
| 373 | + Def::StructCtor(..) |
| 374 | + | Def::Mod(..) |
| 375 | + | Def::Variant(..) |
| 376 | + | Def::VariantCtor(..) |
| 377 | + | Def::SelfCtor(..) => (), |
| 378 | + _ => candidates.push((value_def, Namespace::Value)), |
| 379 | + } |
| 380 | + } |
| 381 | + |
| 382 | + if candidates.len() == 1 { |
| 383 | + candidates.remove(0).0 |
| 384 | + } else if candidates.is_empty() { |
396 | 385 | resolution_failure(cx, &item.attrs, path_str, &dox, link_range); |
397 | 386 | // this could just be a normal link |
398 | 387 | continue; |
| 388 | + } else { |
| 389 | + let candidates = candidates.into_iter().map(|((def, _), ns)| { |
| 390 | + (def, ns) |
| 391 | + }).collect::<Vec<_>>(); |
| 392 | + |
| 393 | + ambiguity_error( |
| 394 | + cx, |
| 395 | + &item.attrs, |
| 396 | + path_str, |
| 397 | + &dox, |
| 398 | + link_range, |
| 399 | + &candidates, |
| 400 | + ); |
| 401 | + continue; |
399 | 402 | } |
400 | 403 | } |
401 | 404 | PathKind::Macro => { |
@@ -505,59 +508,108 @@ fn resolution_failure( |
505 | 508 | diag.emit(); |
506 | 509 | } |
507 | 510 |
|
508 | | -fn ambiguity_error(cx: &DocContext<'_>, attrs: &Attributes, |
509 | | - path_str: &str, |
510 | | - article1: &str, kind1: &str, disambig1: &str, |
511 | | - article2: &str, kind2: &str, disambig2: &str) { |
| 511 | +fn ambiguity_error( |
| 512 | + cx: &DocContext<'_>, |
| 513 | + attrs: &Attributes, |
| 514 | + path_str: &str, |
| 515 | + dox: &str, |
| 516 | + link_range: Option<Range<usize>>, |
| 517 | + candidates: &[(Def, Namespace)], |
| 518 | +) { |
512 | 519 | let sp = span_of_attrs(attrs); |
513 | | - cx.sess() |
514 | | - .struct_span_warn(sp, |
515 | | - &format!("`{}` is both {} {} and {} {}", |
516 | | - path_str, article1, kind1, |
517 | | - article2, kind2)) |
518 | | - .help(&format!("try `{}` if you want to select the {}, \ |
519 | | - or `{}` if you want to \ |
520 | | - select the {}", |
521 | | - disambig1, kind1, disambig2, |
522 | | - kind2)) |
523 | | - .emit(); |
524 | | -} |
525 | 520 |
|
526 | | -/// Given a def, returns its name and disambiguator |
527 | | -/// for a value namespace. |
528 | | -/// |
529 | | -/// Returns `None` for things which cannot be ambiguous since |
530 | | -/// they exist in both namespaces (structs and modules). |
531 | | -fn value_ns_kind(def: Def, path_str: &str) -> Option<(&'static str, String)> { |
532 | | - match def { |
533 | | - // Structs, variants, and mods exist in both namespaces; skip them. |
534 | | - Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) | |
535 | | - Def::VariantCtor(..) | Def::SelfCtor(..) |
536 | | - => None, |
537 | | - Def::Fn(..) |
538 | | - => Some(("function", format!("{}()", path_str))), |
539 | | - Def::Method(..) |
540 | | - => Some(("method", format!("{}()", path_str))), |
541 | | - Def::Const(..) |
542 | | - => Some(("const", format!("const@{}", path_str))), |
543 | | - Def::Static(..) |
544 | | - => Some(("static", format!("static@{}", path_str))), |
545 | | - _ => Some(("value", format!("value@{}", path_str))), |
| 521 | + let mut msg = format!("`{}` is ", path_str); |
| 522 | + |
| 523 | + match candidates { |
| 524 | + [(first_def, _), (second_def, _)] => { |
| 525 | + msg += &format!( |
| 526 | + "both {} {} and {} {}", |
| 527 | + first_def.article(), |
| 528 | + first_def.kind_name(), |
| 529 | + second_def.article(), |
| 530 | + second_def.kind_name(), |
| 531 | + ); |
| 532 | + } |
| 533 | + _ => { |
| 534 | + let mut candidates = candidates.iter().peekable(); |
| 535 | + while let Some((def, _)) = candidates.next() { |
| 536 | + if candidates.peek().is_some() { |
| 537 | + msg += &format!("{} {}, ", def.article(), def.kind_name()); |
| 538 | + } else { |
| 539 | + msg += &format!("and {} {}", def.article(), def.kind_name()); |
| 540 | + } |
| 541 | + } |
| 542 | + } |
546 | 543 | } |
547 | | -} |
548 | 544 |
|
549 | | -/// Given a def, returns its name, the article to be used, and a disambiguator |
550 | | -/// for the type namespace. |
551 | | -fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) { |
552 | | - let (kind, article) = match def { |
553 | | - // We can still have non-tuple structs. |
554 | | - Def::Struct(..) => ("struct", "a"), |
555 | | - Def::Enum(..) => ("enum", "an"), |
556 | | - Def::Trait(..) => ("trait", "a"), |
557 | | - Def::Union(..) => ("union", "a"), |
558 | | - _ => ("type", "a"), |
559 | | - }; |
560 | | - (kind, article, format!("{}@{}", kind, path_str)) |
| 545 | + let mut diag = cx.tcx.struct_span_lint_hir( |
| 546 | + lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, |
| 547 | + hir::CRATE_HIR_ID, |
| 548 | + sp, |
| 549 | + &msg, |
| 550 | + ); |
| 551 | + |
| 552 | + if let Some(link_range) = link_range { |
| 553 | + if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) { |
| 554 | + diag.set_span(sp); |
| 555 | + diag.span_label(sp, "ambiguous link"); |
| 556 | + |
| 557 | + for (def, ns) in candidates { |
| 558 | + let (action, mut suggestion) = match def { |
| 559 | + Def::Method(..) | Def::Fn(..) => { |
| 560 | + ("add parentheses", format!("{}()", path_str)) |
| 561 | + } |
| 562 | + _ => { |
| 563 | + let type_ = match (def, ns) { |
| 564 | + (Def::Const(..), _) => "const", |
| 565 | + (Def::Static(..), _) => "static", |
| 566 | + (Def::Struct(..), _) => "struct", |
| 567 | + (Def::Enum(..), _) => "enum", |
| 568 | + (Def::Union(..), _) => "union", |
| 569 | + (Def::Trait(..), _) => "trait", |
| 570 | + (Def::Mod(..), _) => "module", |
| 571 | + (_, Namespace::Type) => "type", |
| 572 | + (_, Namespace::Value) => "value", |
| 573 | + (_, Namespace::Macro) => "macro", |
| 574 | + }; |
| 575 | + |
| 576 | + // FIXME: if this is an implied shortcut link, it's bad style to suggest `@` |
| 577 | + ("prefix with the item type", format!("{}@{}", type_, path_str)) |
| 578 | + } |
| 579 | + }; |
| 580 | + |
| 581 | + if dox.bytes().nth(link_range.start) == Some(b'`') { |
| 582 | + suggestion = format!("`{}`", suggestion); |
| 583 | + } |
| 584 | + |
| 585 | + diag.span_suggestion( |
| 586 | + sp, |
| 587 | + &format!("to link to the {}, {}", def.kind_name(), action), |
| 588 | + suggestion, |
| 589 | + Applicability::MaybeIncorrect, |
| 590 | + ); |
| 591 | + } |
| 592 | + } else { |
| 593 | + // blah blah blah\nblah\nblah [blah] blah blah\nblah blah |
| 594 | + // ^ ~~~~ |
| 595 | + // | link_range |
| 596 | + // last_new_line_offset |
| 597 | + let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); |
| 598 | + let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); |
| 599 | + |
| 600 | + // Print the line containing the `link_range` and manually mark it with '^'s. |
| 601 | + diag.note(&format!( |
| 602 | + "the link appears in this line:\n\n{line}\n\ |
| 603 | + {indicator: <before$}{indicator:^<found$}", |
| 604 | + line=line, |
| 605 | + indicator="", |
| 606 | + before=link_range.start - last_new_line_offset, |
| 607 | + found=link_range.len(), |
| 608 | + )); |
| 609 | + } |
| 610 | + } |
| 611 | + |
| 612 | + diag.emit(); |
561 | 613 | } |
562 | 614 |
|
563 | 615 | /// Given an enum variant's def, return the def of its enum and the associated fragment. |
|
0 commit comments