Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2d2a3f2

Browse files
authoredMar 14, 2019
Rollup merge of rust-lang#58824 - euclio:intra-link-ambiguity, r=petrochenkov
overhaul intra-doc-link ambiguity warning Fixes rust-lang#52784. - Makes the warning part of the `intra_doc_link_resolution_failure` lint. - Tightens the span to just the ambiguous link. - Reports ambiguities across all three namespaces. - Uses structured suggestions for disambiguation. - Adds a test for the warnings. r? @QuietMisdreavus
2 parents b656841 + 3bb2275 commit 2d2a3f2

File tree

4 files changed

+296
-153
lines changed

4 files changed

+296
-153
lines changed
 

‎src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 176 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use rustc::lint as lint;
2-
use rustc::hir;
3-
use rustc::hir::def::Def;
1+
use errors::Applicability;
2+
use rustc::hir::def::{Def, Namespace::{self, *}, PerNS};
43
use rustc::hir::def_id::DefId;
4+
use rustc::hir;
5+
use rustc::lint as lint;
56
use rustc::ty;
67
use syntax;
78
use syntax::ast::{self, Ident};
@@ -35,39 +36,24 @@ pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
3536
}
3637
}
3738

38-
#[derive(Debug)]
39-
enum PathKind {
40-
/// Either a value or type, but not a macro
41-
Unknown,
42-
/// Macro
43-
Macro,
44-
/// Values, functions, consts, statics (everything in the value namespace)
45-
Value,
46-
/// Types, traits (everything in the type namespace)
47-
Type,
48-
}
49-
5039
struct LinkCollector<'a, 'tcx> {
5140
cx: &'a DocContext<'tcx>,
5241
mod_ids: Vec<ast::NodeId>,
53-
is_nightly_build: bool,
5442
}
5543

5644
impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
5745
fn new(cx: &'a DocContext<'tcx>) -> Self {
5846
LinkCollector {
5947
cx,
6048
mod_ids: Vec::new(),
61-
is_nightly_build: UnstableFeatures::from_environment().is_nightly_build(),
6249
}
6350
}
6451

65-
/// Resolves a given string as a path, along with whether or not it is
66-
/// in the value namespace. Also returns an optional URL fragment in the case
67-
/// of variants and methods.
52+
/// Resolves a string as a path within a particular namespace. Also returns an optional
53+
/// URL fragment in the case of variants and methods.
6854
fn resolve(&self,
6955
path_str: &str,
70-
is_val: bool,
56+
ns: Namespace,
7157
current_item: &Option<String>,
7258
parent_id: Option<ast::NodeId>)
7359
-> Result<(Def, Option<String>), ()>
@@ -78,11 +64,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
7864
// path.
7965
if let Some(id) = parent_id.or(self.mod_ids.last().cloned()) {
8066
// FIXME: `with_scope` requires the `NodeId` of a module.
81-
let result = cx.enter_resolver(|resolver| resolver.with_scope(id,
82-
|resolver| {
83-
resolver.resolve_str_path_error(DUMMY_SP,
84-
&path_str, is_val)
85-
}));
67+
let result = cx.enter_resolver(|resolver| {
68+
resolver.with_scope(id, |resolver| {
69+
resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns == ValueNS)
70+
})
71+
});
8672

8773
if let Ok(result) = result {
8874
// In case this is a trait item, skip the
@@ -95,16 +81,16 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
9581
_ => return Ok((result.def, None))
9682
};
9783

98-
if value != is_val {
84+
if value != (ns == ValueNS) {
9985
return Err(())
10086
}
101-
} else if let Some(prim) = is_primitive(path_str, is_val) {
87+
} else if let Some(prim) = is_primitive(path_str, ns) {
10288
return Ok((prim, Some(path_str.to_owned())))
10389
} else {
10490
// If resolution failed, it may still be a method
10591
// because methods are not handled by the resolver
10692
// If so, bail when we're not looking for a value.
107-
if !is_val {
93+
if ns != ValueNS {
10894
return Err(())
10995
}
11096
}
@@ -128,7 +114,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
128114
path = name.clone();
129115
}
130116
}
131-
if let Some(prim) = is_primitive(&path, false) {
117+
if let Some(prim) = is_primitive(&path, TypeNS) {
132118
let did = primitive_impl(cx, &path).ok_or(())?;
133119
return cx.tcx.associated_items(did)
134120
.find(|item| item.ident.name == item_name)
@@ -152,8 +138,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
152138
.find(|item| item.ident.name == item_name);
153139
if let Some(item) = item {
154140
let out = match item.kind {
155-
ty::AssociatedKind::Method if is_val => "method",
156-
ty::AssociatedKind::Const if is_val => "associatedconstant",
141+
ty::AssociatedKind::Method if ns == ValueNS => "method",
142+
ty::AssociatedKind::Const if ns == ValueNS => "associatedconstant",
157143
_ => return Err(())
158144
};
159145
Ok((ty.def, Some(format!("{}.{}", out, item_name))))
@@ -190,9 +176,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
190176
.find(|item| item.ident.name == item_name);
191177
if let Some(item) = item {
192178
let kind = match item.kind {
193-
ty::AssociatedKind::Const if is_val => "associatedconstant",
194-
ty::AssociatedKind::Type if !is_val => "associatedtype",
195-
ty::AssociatedKind::Method if is_val => {
179+
ty::AssociatedKind::Const if ns == ValueNS => "associatedconstant",
180+
ty::AssociatedKind::Type if ns == TypeNS => "associatedtype",
181+
ty::AssociatedKind::Method if ns == ValueNS => {
196182
if item.defaultness.has_value() {
197183
"method"
198184
} else {
@@ -279,39 +265,35 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
279265

280266
look_for_tests(&cx, &dox, &item, true);
281267

282-
if !self.is_nightly_build {
283-
return None;
284-
}
285-
286268
for (ori_link, link_range) in markdown_links(&dox) {
287269
// Bail early for real links.
288270
if ori_link.contains('/') {
289271
continue;
290272
}
291273
let link = ori_link.replace("`", "");
292274
let (def, fragment) = {
293-
let mut kind = PathKind::Unknown;
275+
let mut kind = None;
294276
let path_str = if let Some(prefix) =
295277
["struct@", "enum@", "type@",
296278
"trait@", "union@"].iter()
297279
.find(|p| link.starts_with(**p)) {
298-
kind = PathKind::Type;
280+
kind = Some(TypeNS);
299281
link.trim_start_matches(prefix)
300282
} else if let Some(prefix) =
301283
["const@", "static@",
302284
"value@", "function@", "mod@",
303285
"fn@", "module@", "method@"]
304286
.iter().find(|p| link.starts_with(**p)) {
305-
kind = PathKind::Value;
287+
kind = Some(ValueNS);
306288
link.trim_start_matches(prefix)
307289
} else if link.ends_with("()") {
308-
kind = PathKind::Value;
290+
kind = Some(ValueNS);
309291
link.trim_end_matches("()")
310292
} else if link.starts_with("macro@") {
311-
kind = PathKind::Macro;
293+
kind = Some(MacroNS);
312294
link.trim_start_matches("macro@")
313295
} else if link.ends_with('!') {
314-
kind = PathKind::Macro;
296+
kind = Some(MacroNS);
315297
link.trim_end_matches('!')
316298
} else {
317299
&link[..]
@@ -323,8 +305,8 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
323305
}
324306

325307
match kind {
326-
PathKind::Value => {
327-
if let Ok(def) = self.resolve(path_str, true, &current_item, parent_node) {
308+
Some(ns @ ValueNS) => {
309+
if let Ok(def) = self.resolve(path_str, ns, &current_item, parent_node) {
328310
def
329311
} else {
330312
resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
@@ -334,71 +316,58 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
334316
continue;
335317
}
336318
}
337-
PathKind::Type => {
338-
if let Ok(def) = self.resolve(path_str, false, &current_item, parent_node) {
319+
Some(ns @ TypeNS) => {
320+
if let Ok(def) = self.resolve(path_str, ns, &current_item, parent_node) {
339321
def
340322
} else {
341323
resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
342324
// This could just be a normal link.
343325
continue;
344326
}
345327
}
346-
PathKind::Unknown => {
328+
None => {
347329
// Try everything!
348-
if let Some(macro_def) = macro_resolve(cx, path_str) {
349-
if let Ok(type_def) =
350-
self.resolve(path_str, false, &current_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, &current_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) =
371-
self.resolve(path_str, false, &current_item, parent_node)
372-
{
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, &current_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) =
392-
self.resolve(path_str, true, &current_item, parent_node)
393-
{
394-
value_def
395-
} else {
330+
let candidates = PerNS {
331+
macro_ns: macro_resolve(cx, path_str).map(|def| (def, None)),
332+
type_ns: self
333+
.resolve(path_str, TypeNS, &current_item, parent_node)
334+
.ok(),
335+
value_ns: self
336+
.resolve(path_str, ValueNS, &current_item, parent_node)
337+
.ok()
338+
.and_then(|(def, fragment)| {
339+
// Constructors are picked up in the type namespace.
340+
match def {
341+
Def::StructCtor(..)
342+
| Def::VariantCtor(..)
343+
| Def::SelfCtor(..) => None,
344+
_ => Some((def, fragment))
345+
}
346+
}),
347+
};
348+
349+
if candidates.is_empty() {
396350
resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
397351
// this could just be a normal link
398352
continue;
399353
}
354+
355+
let is_unambiguous = candidates.clone().present_items().count() == 1;
356+
if is_unambiguous {
357+
candidates.present_items().next().unwrap()
358+
} else {
359+
ambiguity_error(
360+
cx,
361+
&item.attrs,
362+
path_str,
363+
&dox,
364+
link_range,
365+
candidates.map(|candidate| candidate.map(|(def, _)| def)),
366+
);
367+
continue;
368+
}
400369
}
401-
PathKind::Macro => {
370+
Some(MacroNS) => {
402371
if let Some(def) = macro_resolve(cx, path_str) {
403372
(def, None)
404373
} else {
@@ -505,59 +474,114 @@ fn resolution_failure(
505474
diag.emit();
506475
}
507476

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) {
477+
fn ambiguity_error(
478+
cx: &DocContext<'_>,
479+
attrs: &Attributes,
480+
path_str: &str,
481+
dox: &str,
482+
link_range: Option<Range<usize>>,
483+
candidates: PerNS<Option<Def>>,
484+
) {
512485
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-
}
525486

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))),
487+
let mut msg = format!("`{}` is ", path_str);
488+
489+
let candidates = [TypeNS, ValueNS, MacroNS].iter().filter_map(|&ns| {
490+
candidates[ns].map(|def| (def, ns))
491+
}).collect::<Vec<_>>();
492+
match candidates.as_slice() {
493+
[(first_def, _), (second_def, _)] => {
494+
msg += &format!(
495+
"both {} {} and {} {}",
496+
first_def.article(),
497+
first_def.kind_name(),
498+
second_def.article(),
499+
second_def.kind_name(),
500+
);
501+
}
502+
_ => {
503+
let mut candidates = candidates.iter().peekable();
504+
while let Some((def, _)) = candidates.next() {
505+
if candidates.peek().is_some() {
506+
msg += &format!("{} {}, ", def.article(), def.kind_name());
507+
} else {
508+
msg += &format!("and {} {}", def.article(), def.kind_name());
509+
}
510+
}
511+
}
546512
}
547-
}
548513

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))
514+
let mut diag = cx.tcx.struct_span_lint_hir(
515+
lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
516+
hir::CRATE_HIR_ID,
517+
sp,
518+
&msg,
519+
);
520+
521+
if let Some(link_range) = link_range {
522+
if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) {
523+
diag.set_span(sp);
524+
diag.span_label(sp, "ambiguous link");
525+
526+
for (def, ns) in candidates {
527+
let (action, mut suggestion) = match def {
528+
Def::Method(..) | Def::Fn(..) => {
529+
("add parentheses", format!("{}()", path_str))
530+
}
531+
Def::Macro(..) => {
532+
("add an exclamation mark", format!("{}!", path_str))
533+
}
534+
_ => {
535+
let type_ = match (def, ns) {
536+
(Def::Const(..), _) => "const",
537+
(Def::Static(..), _) => "static",
538+
(Def::Struct(..), _) => "struct",
539+
(Def::Enum(..), _) => "enum",
540+
(Def::Union(..), _) => "union",
541+
(Def::Trait(..), _) => "trait",
542+
(Def::Mod(..), _) => "module",
543+
(_, TypeNS) => "type",
544+
(_, ValueNS) => "value",
545+
(_, MacroNS) => "macro",
546+
};
547+
548+
// FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
549+
("prefix with the item type", format!("{}@{}", type_, path_str))
550+
}
551+
};
552+
553+
if dox.bytes().nth(link_range.start) == Some(b'`') {
554+
suggestion = format!("`{}`", suggestion);
555+
}
556+
557+
diag.span_suggestion(
558+
sp,
559+
&format!("to link to the {}, {}", def.kind_name(), action),
560+
suggestion,
561+
Applicability::MaybeIncorrect,
562+
);
563+
}
564+
} else {
565+
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah
566+
// ^ ~~~~
567+
// | link_range
568+
// last_new_line_offset
569+
let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
570+
let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
571+
572+
// Print the line containing the `link_range` and manually mark it with '^'s.
573+
diag.note(&format!(
574+
"the link appears in this line:\n\n{line}\n\
575+
{indicator: <before$}{indicator:^<found$}",
576+
line=line,
577+
indicator="",
578+
before=link_range.start - last_new_line_offset,
579+
found=link_range.len(),
580+
));
581+
}
582+
}
583+
584+
diag.emit();
561585
}
562586

563587
/// Given an enum variant's def, return the def of its enum and the associated fragment.
@@ -594,11 +618,11 @@ const PRIMITIVES: &[(&str, Def)] = &[
594618
("char", Def::PrimTy(hir::PrimTy::Char)),
595619
];
596620

597-
fn is_primitive(path_str: &str, is_val: bool) -> Option<Def> {
598-
if is_val {
599-
None
600-
} else {
621+
fn is_primitive(path_str: &str, ns: Namespace) -> Option<Def> {
622+
if ns == TypeNS {
601623
PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)
624+
} else {
625+
None
602626
}
603627
}
604628

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#![deny(intra_doc_link_resolution_failure)]
2+
#![allow(non_camel_case_types)]
3+
#![allow(non_upper_case_globals)]
4+
5+
pub fn ambiguous() {}
6+
7+
pub struct ambiguous {}
8+
9+
#[macro_export]
10+
macro_rules! multi_conflict { () => {} }
11+
12+
#[allow(non_camel_case_types)]
13+
pub struct multi_conflict {}
14+
15+
pub fn multi_conflict() {}
16+
17+
pub mod type_and_value {}
18+
19+
pub const type_and_value: i32 = 0;
20+
21+
pub mod foo {
22+
pub enum bar {}
23+
24+
pub fn bar() {}
25+
}
26+
27+
/// [`ambiguous`] is ambiguous. //~ERROR `ambiguous`
28+
///
29+
/// [ambiguous] is ambiguous. //~ERROR ambiguous
30+
///
31+
/// [`multi_conflict`] is a three-way conflict. //~ERROR `multi_conflict`
32+
///
33+
/// Ambiguous [type_and_value]. //~ERROR type_and_value
34+
///
35+
/// Ambiguous non-implied shortcut link [`foo::bar`]. //~ERROR `foo::bar`
36+
pub struct Docs {}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
error: `ambiguous` is both a struct and a function
2+
--> $DIR/intra-links-ambiguity.rs:27:6
3+
|
4+
LL | /// [`ambiguous`] is ambiguous. //~ERROR `ambiguous`
5+
| ^^^^^^^^^^^ ambiguous link
6+
|
7+
note: lint level defined here
8+
--> $DIR/intra-links-ambiguity.rs:1:9
9+
|
10+
LL | #![deny(intra_doc_link_resolution_failure)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
help: to link to the struct, prefix with the item type
13+
|
14+
LL | /// [`struct@ambiguous`] is ambiguous. //~ERROR `ambiguous`
15+
| ^^^^^^^^^^^^^^^^^^
16+
help: to link to the function, add parentheses
17+
|
18+
LL | /// [`ambiguous()`] is ambiguous. //~ERROR `ambiguous`
19+
| ^^^^^^^^^^^^^
20+
21+
error: `ambiguous` is both a struct and a function
22+
--> $DIR/intra-links-ambiguity.rs:29:6
23+
|
24+
LL | /// [ambiguous] is ambiguous. //~ERROR ambiguous
25+
| ^^^^^^^^^ ambiguous link
26+
help: to link to the struct, prefix with the item type
27+
|
28+
LL | /// [struct@ambiguous] is ambiguous. //~ERROR ambiguous
29+
| ^^^^^^^^^^^^^^^^
30+
help: to link to the function, add parentheses
31+
|
32+
LL | /// [ambiguous()] is ambiguous. //~ERROR ambiguous
33+
| ^^^^^^^^^^^
34+
35+
error: `multi_conflict` is a struct, a function, and a macro
36+
--> $DIR/intra-links-ambiguity.rs:31:6
37+
|
38+
LL | /// [`multi_conflict`] is a three-way conflict. //~ERROR `multi_conflict`
39+
| ^^^^^^^^^^^^^^^^ ambiguous link
40+
help: to link to the struct, prefix with the item type
41+
|
42+
LL | /// [`struct@multi_conflict`] is a three-way conflict. //~ERROR `multi_conflict`
43+
| ^^^^^^^^^^^^^^^^^^^^^^^
44+
help: to link to the function, add parentheses
45+
|
46+
LL | /// [`multi_conflict()`] is a three-way conflict. //~ERROR `multi_conflict`
47+
| ^^^^^^^^^^^^^^^^^^
48+
help: to link to the macro, add an exclamation mark
49+
|
50+
LL | /// [`multi_conflict!`] is a three-way conflict. //~ERROR `multi_conflict`
51+
| ^^^^^^^^^^^^^^^^^
52+
53+
error: `type_and_value` is both a module and a constant
54+
--> $DIR/intra-links-ambiguity.rs:33:16
55+
|
56+
LL | /// Ambiguous [type_and_value]. //~ERROR type_and_value
57+
| ^^^^^^^^^^^^^^ ambiguous link
58+
help: to link to the module, prefix with the item type
59+
|
60+
LL | /// Ambiguous [module@type_and_value]. //~ERROR type_and_value
61+
| ^^^^^^^^^^^^^^^^^^^^^
62+
help: to link to the constant, prefix with the item type
63+
|
64+
LL | /// Ambiguous [const@type_and_value]. //~ERROR type_and_value
65+
| ^^^^^^^^^^^^^^^^^^^^
66+
67+
error: `foo::bar` is both an enum and a function
68+
--> $DIR/intra-links-ambiguity.rs:35:42
69+
|
70+
LL | /// Ambiguous non-implied shortcut link [`foo::bar`]. //~ERROR `foo::bar`
71+
| ^^^^^^^^^^ ambiguous link
72+
help: to link to the enum, prefix with the item type
73+
|
74+
LL | /// Ambiguous non-implied shortcut link [`enum@foo::bar`]. //~ERROR `foo::bar`
75+
| ^^^^^^^^^^^^^^^
76+
help: to link to the function, add parentheses
77+
|
78+
LL | /// Ambiguous non-implied shortcut link [`foo::bar()`]. //~ERROR `foo::bar`
79+
| ^^^^^^^^^^^^
80+
81+
error: aborting due to 5 previous errors
82+

‎src/test/rustdoc/intra-links.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
//! * [`ThisType::this_method`](ThisType::this_method)
2323
//! * [`ThisEnum`](ThisEnum)
2424
//! * [`ThisEnum::ThisVariant`](ThisEnum::ThisVariant)
25+
//! * [`ThisEnum::ThisVariantCtor`](ThisEnum::ThisVariantCtor)
2526
//! * [`ThisTrait`](ThisTrait)
2627
//! * [`ThisTrait::this_associated_method`](ThisTrait::this_associated_method)
2728
//! * [`ThisTrait::ThisAssociatedType`](ThisTrait::ThisAssociatedType)
@@ -50,7 +51,7 @@ pub struct ThisType;
5051
impl ThisType {
5152
pub fn this_method() {}
5253
}
53-
pub enum ThisEnum { ThisVariant, }
54+
pub enum ThisEnum { ThisVariant, ThisVariantCtor(u32), }
5455
pub trait ThisTrait {
5556
type ThisAssociatedType;
5657
const THIS_ASSOCIATED_CONST: u8;

0 commit comments

Comments
 (0)
Please sign in to comment.