|
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