Skip to content

Commit f79ef02

Browse files
committed
Auto merge of #130587 - coolreader18:field-variant-doclink-disambig, r=notriddle,jyn514
Add `field@` and `variant@` doc-link disambiguators I'm not sure if this is big enough to need an fcp or not, but this is something I found missing when trying to refer to a field in macro-generated docs, not knowing if a method might be defined as well. Obviously, there are definitely other uses. In the case where it's not disambiguated, methods (and I suppose other associated items in the value namespace) still take priority, which `@jyn514` said was an oversight but I think is probably the desired behavior 99% of the time anyway - shadowing a field with an accessor method is a very common pattern. If fields and methods with the same name started conflicting, it would be a breaking change. Though, to quote them: > jyn: maybe you can break this only if both [the method and the field] are public > jyn: rustc has some future-incompat warning level > jyn: that gets through -A warnings and --cap-lints from cargo That'd be out of scope of this PR, though. Fixes #80283
2 parents c87004a + e91e015 commit f79ef02

File tree

8 files changed

+138
-66
lines changed

8 files changed

+138
-66
lines changed

Diff for: src/doc/rustdoc/src/write-documentation/linking-to-items-by-name.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ fn Foo() {}
8989

9090
These prefixes will be stripped when displayed in the documentation, so `[struct@Foo]` will be
9191
rendered as `Foo`. The following prefixes are available: `struct`, `enum`, `trait`, `union`,
92-
`mod`, `module`, `const`, `constant`, `fn`, `function`, `method`, `derive`, `type`, `value`,
93-
`macro`, `prim` or `primitive`.
92+
`mod`, `module`, `const`, `constant`, `fn`, `function`, `field`, `variant`, `method`, `derive`,
93+
`type`, `value`, `macro`, `prim` or `primitive`.
9494

9595
You can also disambiguate for functions by adding `()` after the function name,
9696
or for macros by adding `!` after the macro name. The macro `!` can be followed by `()`, `{}`,

Diff for: src/librustdoc/passes/collect_intra_doc_links.rs

+54-41
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ impl Res {
110110

111111
let prefix = match kind {
112112
DefKind::Fn | DefKind::AssocFn => return Suggestion::Function,
113-
DefKind::Field => return Suggestion::RemoveDisambiguator,
114113
DefKind::Macro(MacroKind::Bang) => return Suggestion::Macro,
115114

116115
DefKind::Macro(MacroKind::Derive) => "derive",
@@ -123,6 +122,8 @@ impl Res {
123122
"const"
124123
}
125124
DefKind::Static { .. } => "static",
125+
DefKind::Field => "field",
126+
DefKind::Variant | DefKind::Ctor(..) => "variant",
126127
// Now handle things that don't have a specific disambiguator
127128
_ => match kind
128129
.ns()
@@ -415,6 +416,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
415416
&mut self,
416417
path_str: &'path str,
417418
ns: Namespace,
419+
disambiguator: Option<Disambiguator>,
418420
item_id: DefId,
419421
module_id: DefId,
420422
) -> Result<Vec<(Res, Option<DefId>)>, UnresolvedPath<'path>> {
@@ -454,7 +456,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
454456
match resolve_primitive(path_root, TypeNS)
455457
.or_else(|| self.resolve_path(path_root, TypeNS, item_id, module_id))
456458
.map(|ty_res| {
457-
self.resolve_associated_item(ty_res, item_name, ns, module_id)
459+
self.resolve_associated_item(ty_res, item_name, ns, disambiguator, module_id)
458460
.into_iter()
459461
.map(|(res, def_id)| (res, Some(def_id)))
460462
.collect::<Vec<_>>()
@@ -557,6 +559,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
557559
root_res: Res,
558560
item_name: Symbol,
559561
ns: Namespace,
562+
disambiguator: Option<Disambiguator>,
560563
module_id: DefId,
561564
) -> Vec<(Res, DefId)> {
562565
let tcx = self.cx.tcx;
@@ -583,7 +586,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
583586
// FIXME: if the associated item is defined directly on the type alias,
584587
// it will show up on its documentation page, we should link there instead.
585588
let Some(res) = self.def_id_to_res(did) else { return Vec::new() };
586-
self.resolve_associated_item(res, item_name, ns, module_id)
589+
self.resolve_associated_item(res, item_name, ns, disambiguator, module_id)
587590
}
588591
Res::Def(
589592
def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy),
@@ -604,6 +607,39 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
604607
}
605608
}
606609

610+
let search_for_field = || {
611+
let (DefKind::Struct | DefKind::Union) = def_kind else { return vec![] };
612+
debug!("looking for fields named {item_name} for {did:?}");
613+
// FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
614+
// NOTE: it's different from variant_field because it only resolves struct fields,
615+
// not variant fields (2 path segments, not 3).
616+
//
617+
// We need to handle struct (and union) fields in this code because
618+
// syntactically their paths are identical to associated item paths:
619+
// `module::Type::field` and `module::Type::Assoc`.
620+
//
621+
// On the other hand, variant fields can't be mistaken for associated
622+
// items because they look like this: `module::Type::Variant::field`.
623+
//
624+
// Variants themselves don't need to be handled here, even though
625+
// they also look like associated items (`module::Type::Variant`),
626+
// because they are real Rust syntax (unlike the intra-doc links
627+
// field syntax) and are handled by the compiler's resolver.
628+
let ty::Adt(def, _) = tcx.type_of(did).instantiate_identity().kind() else {
629+
unreachable!()
630+
};
631+
def.non_enum_variant()
632+
.fields
633+
.iter()
634+
.filter(|field| field.name == item_name)
635+
.map(|field| (root_res, field.did))
636+
.collect::<Vec<_>>()
637+
};
638+
639+
if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator {
640+
return search_for_field();
641+
}
642+
607643
// Checks if item_name belongs to `impl SomeItem`
608644
let mut assoc_items: Vec<_> = tcx
609645
.inherent_impls(did)
@@ -646,32 +682,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
646682
if ns != Namespace::ValueNS {
647683
return Vec::new();
648684
}
649-
debug!("looking for fields named {item_name} for {did:?}");
650-
// FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
651-
// NOTE: it's different from variant_field because it only resolves struct fields,
652-
// not variant fields (2 path segments, not 3).
653-
//
654-
// We need to handle struct (and union) fields in this code because
655-
// syntactically their paths are identical to associated item paths:
656-
// `module::Type::field` and `module::Type::Assoc`.
657-
//
658-
// On the other hand, variant fields can't be mistaken for associated
659-
// items because they look like this: `module::Type::Variant::field`.
660-
//
661-
// Variants themselves don't need to be handled here, even though
662-
// they also look like associated items (`module::Type::Variant`),
663-
// because they are real Rust syntax (unlike the intra-doc links
664-
// field syntax) and are handled by the compiler's resolver.
665-
let def = match tcx.type_of(did).instantiate_identity().kind() {
666-
ty::Adt(def, _) if !def.is_enum() => def,
667-
_ => return Vec::new(),
668-
};
669-
def.non_enum_variant()
670-
.fields
671-
.iter()
672-
.filter(|field| field.name == item_name)
673-
.map(|field| (root_res, field.did))
674-
.collect::<Vec<_>>()
685+
686+
search_for_field()
675687
}
676688
Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace(
677689
tcx,
@@ -1297,7 +1309,7 @@ impl LinkCollector<'_, '_> {
12971309

12981310
match disambiguator.map(Disambiguator::ns) {
12991311
Some(expected_ns) => {
1300-
match self.resolve(path_str, expected_ns, item_id, module_id) {
1312+
match self.resolve(path_str, expected_ns, disambiguator, item_id, module_id) {
13011313
Ok(candidates) => candidates,
13021314
Err(err) => {
13031315
// We only looked in one namespace. Try to give a better error if possible.
@@ -1306,8 +1318,9 @@ impl LinkCollector<'_, '_> {
13061318
let mut err = ResolutionFailure::NotResolved(err);
13071319
for other_ns in [TypeNS, ValueNS, MacroNS] {
13081320
if other_ns != expected_ns {
1309-
if let Ok(&[res, ..]) =
1310-
self.resolve(path_str, other_ns, item_id, module_id).as_deref()
1321+
if let Ok(&[res, ..]) = self
1322+
.resolve(path_str, other_ns, None, item_id, module_id)
1323+
.as_deref()
13111324
{
13121325
err = ResolutionFailure::WrongNamespace {
13131326
res: full_res(self.cx.tcx, res),
@@ -1327,7 +1340,7 @@ impl LinkCollector<'_, '_> {
13271340
None => {
13281341
// Try everything!
13291342
let mut candidate = |ns| {
1330-
self.resolve(path_str, ns, item_id, module_id)
1343+
self.resolve(path_str, ns, None, item_id, module_id)
13311344
.map_err(ResolutionFailure::NotResolved)
13321345
};
13331346

@@ -1531,6 +1544,8 @@ impl Disambiguator {
15311544
}),
15321545
"function" | "fn" | "method" => Kind(DefKind::Fn),
15331546
"derive" => Kind(DefKind::Macro(MacroKind::Derive)),
1547+
"field" => Kind(DefKind::Field),
1548+
"variant" => Kind(DefKind::Variant),
15341549
"type" => NS(Namespace::TypeNS),
15351550
"value" => NS(Namespace::ValueNS),
15361551
"macro" => NS(Namespace::MacroNS),
@@ -1569,6 +1584,8 @@ impl Disambiguator {
15691584
fn ns(self) -> Namespace {
15701585
match self {
15711586
Self::Namespace(n) => n,
1587+
// for purposes of link resolution, fields are in the value namespace.
1588+
Self::Kind(DefKind::Field) => ValueNS,
15721589
Self::Kind(k) => {
15731590
k.ns().expect("only DefKinds with a valid namespace can be disambiguators")
15741591
}
@@ -1603,8 +1620,6 @@ enum Suggestion {
16031620
Function,
16041621
/// `m!`
16051622
Macro,
1606-
/// `foo` without any disambiguator
1607-
RemoveDisambiguator,
16081623
}
16091624

16101625
impl Suggestion {
@@ -1613,7 +1628,6 @@ impl Suggestion {
16131628
Self::Prefix(x) => format!("prefix with `{x}@`").into(),
16141629
Self::Function => "add parentheses".into(),
16151630
Self::Macro => "add an exclamation mark".into(),
1616-
Self::RemoveDisambiguator => "remove the disambiguator".into(),
16171631
}
16181632
}
16191633

@@ -1623,13 +1637,11 @@ impl Suggestion {
16231637
Self::Prefix(prefix) => format!("{prefix}@{path_str}"),
16241638
Self::Function => format!("{path_str}()"),
16251639
Self::Macro => format!("{path_str}!"),
1626-
Self::RemoveDisambiguator => path_str.into(),
16271640
}
16281641
}
16291642

16301643
fn as_help_span(
16311644
&self,
1632-
path_str: &str,
16331645
ori_link: &str,
16341646
sp: rustc_span::Span,
16351647
) -> Vec<(rustc_span::Span, String)> {
@@ -1677,7 +1689,6 @@ impl Suggestion {
16771689
}
16781690
sugg
16791691
}
1680-
Self::RemoveDisambiguator => vec![(sp, path_str.into())],
16811692
}
16821693
}
16831694
}
@@ -1826,7 +1837,9 @@ fn resolution_failure(
18261837
};
18271838
name = start;
18281839
for ns in [TypeNS, ValueNS, MacroNS] {
1829-
if let Ok(v_res) = collector.resolve(start, ns, item_id, module_id) {
1840+
if let Ok(v_res) =
1841+
collector.resolve(start, ns, None, item_id, module_id)
1842+
{
18301843
debug!("found partial_res={v_res:?}");
18311844
if let Some(&res) = v_res.first() {
18321845
*partial_res = Some(full_res(tcx, res));
@@ -2164,7 +2177,7 @@ fn suggest_disambiguator(
21642177
};
21652178

21662179
if let (Some(sp), Some(ori_link)) = (sp, ori_link) {
2167-
let mut spans = suggestion.as_help_span(path_str, ori_link, sp);
2180+
let mut spans = suggestion.as_help_span(ori_link, sp);
21682181
if spans.len() > 1 {
21692182
diag.multipart_suggestion(help, spans, Applicability::MaybeIncorrect);
21702183
} else {

Diff for: tests/rustdoc-ui/intra-doc/disambiguator-mismatch.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![deny(rustdoc::broken_intra_doc_links)]
22
//~^ NOTE lint level is defined
3-
pub enum S {}
3+
pub enum S {
4+
A,
5+
}
46
fn S() {}
57

68
#[macro_export]
@@ -13,6 +15,10 @@ const c: usize = 0;
1315

1416
trait T {}
1517

18+
struct X {
19+
y: usize,
20+
}
21+
1622
/// Link to [struct@S]
1723
//~^ ERROR incompatible link kind for `S`
1824
//~| NOTE this link resolved
@@ -78,4 +84,14 @@ trait T {}
7884
//~^ ERROR unresolved link to `std`
7985
//~| NOTE this link resolves to the crate `std`
8086
//~| HELP to link to the crate, prefix with `mod@`
87+
88+
/// Link to [method@X::y]
89+
//~^ ERROR incompatible link kind for `X::y`
90+
//~| NOTE this link resolved
91+
//~| HELP prefix with `field@`
92+
93+
/// Link to [field@S::A]
94+
//~^ ERROR incompatible link kind for `S::A`
95+
//~| NOTE this link resolved
96+
//~| HELP prefix with `variant@`
8197
pub fn f() {}

0 commit comments

Comments
 (0)