Skip to content

Commit 8fdce9b

Browse files
committed
Auto merge of #74489 - jyn514:assoc-items, r=manishearth,petrochenkov
Fix intra-doc links for associated items @Manishearth and I found that links of the following sort are broken: ```rust $ cat str_from.rs /// [`String::from`] pub fn foo() {} $ rustdoc str_from.rs warning: `[String::from]` cannot be resolved, ignoring it. --> str_from.rs:4:6 | 4 | /// [`String::from`] | ^^^^^^^^^^^^^^ cannot be resolved, ignoring ``` It turns out this is because the current implementation only looks at inherent impls (`impl Bar {}`) and traits _for the item being documented_. Note that this is not the same as the item being _linked_ to. So this code would work: ```rust pub trait T1 { fn method(); } pub struct S; impl T1 for S { /// [S::method] on method fn method() {} } ``` but putting the documentation on `trait T1` would not. ~~I realized that writing it up that my fix is only partially correct: It removes the inherent impls code when it should instead remove the `trait_item` code.~~ Fixed. Additionally, I discovered while writing this there is some ambiguity: you could have multiple methods with the same name, but for different traits: ```rust pub trait T1 { fn method(); } pub trait T2 { fn method(); } /// See [S::method] pub struct S; ``` Rustdoc should give an ambiguity error here, but since there is currently no way to disambiguate the traits (#74563) it does not (#74489 (comment)). There is a _third_ ambiguity that pops up: What if the trait is generic and is implemented multiple times with different generic parameters? In this case, my fix does not do very well: it thinks there is only one trait instantiated and links to that trait: ``` /// [`String::from`] -- this resolves to https://doc.rust-lang.org/nightly/alloc/string/struct.String.html#method.from pub fn foo() {} ``` However, every `From` implementation has a method called `from`! So the browser picks a random one. This is not the desired behavior, but it's not clear how to avoid it. To be consistent with the rest of intra-doc links, this only resolves associated items from traits that are in scope. This required changes to rustc_resolve to work cross-crate; the relevant commits are prefixed with `resolve: `. As a bonus, considering only traits in scope is slightly faster. To avoid re-calculating the traits over and over, rustdoc uses a cache to store the traits in scope for a given module.
2 parents 5180f3d + d77eff2 commit 8fdce9b

File tree

10 files changed

+480
-174
lines changed

10 files changed

+480
-174
lines changed

Diff for: src/librustc_resolve/late.rs

+15-80
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use RibKind::*;
99

1010
use crate::{path_names_to_string, BindingError, CrateLint, LexicalScopeBinding};
11-
use crate::{Module, ModuleOrUniformRoot, NameBindingKind, ParentScope, PathResult};
11+
use crate::{Module, ModuleOrUniformRoot, ParentScope, PathResult};
1212
use crate::{ResolutionError, Resolver, Segment, UseError};
1313

1414
use rustc_ast::ptr::P;
@@ -24,7 +24,6 @@ use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
2424
use rustc_hir::TraitCandidate;
2525
use rustc_middle::{bug, span_bug};
2626
use rustc_session::lint;
27-
use rustc_span::def_id::LocalDefId;
2827
use rustc_span::symbol::{kw, sym, Ident, Symbol};
2928
use rustc_span::Span;
3029
use smallvec::{smallvec, SmallVec};
@@ -2342,95 +2341,31 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
23422341
ident.span = ident.span.normalize_to_macros_2_0();
23432342
let mut search_module = self.parent_scope.module;
23442343
loop {
2345-
self.get_traits_in_module_containing_item(ident, ns, search_module, &mut found_traits);
2344+
self.r.get_traits_in_module_containing_item(
2345+
ident,
2346+
ns,
2347+
search_module,
2348+
&mut found_traits,
2349+
&self.parent_scope,
2350+
);
23462351
search_module =
23472352
unwrap_or!(self.r.hygienic_lexical_parent(search_module, &mut ident.span), break);
23482353
}
23492354

23502355
if let Some(prelude) = self.r.prelude {
23512356
if !search_module.no_implicit_prelude {
2352-
self.get_traits_in_module_containing_item(ident, ns, prelude, &mut found_traits);
2357+
self.r.get_traits_in_module_containing_item(
2358+
ident,
2359+
ns,
2360+
prelude,
2361+
&mut found_traits,
2362+
&self.parent_scope,
2363+
);
23532364
}
23542365
}
23552366

23562367
found_traits
23572368
}
2358-
2359-
fn get_traits_in_module_containing_item(
2360-
&mut self,
2361-
ident: Ident,
2362-
ns: Namespace,
2363-
module: Module<'a>,
2364-
found_traits: &mut Vec<TraitCandidate>,
2365-
) {
2366-
assert!(ns == TypeNS || ns == ValueNS);
2367-
let mut traits = module.traits.borrow_mut();
2368-
if traits.is_none() {
2369-
let mut collected_traits = Vec::new();
2370-
module.for_each_child(self.r, |_, name, ns, binding| {
2371-
if ns != TypeNS {
2372-
return;
2373-
}
2374-
match binding.res() {
2375-
Res::Def(DefKind::Trait | DefKind::TraitAlias, _) => {
2376-
collected_traits.push((name, binding))
2377-
}
2378-
_ => (),
2379-
}
2380-
});
2381-
*traits = Some(collected_traits.into_boxed_slice());
2382-
}
2383-
2384-
for &(trait_name, binding) in traits.as_ref().unwrap().iter() {
2385-
// Traits have pseudo-modules that can be used to search for the given ident.
2386-
if let Some(module) = binding.module() {
2387-
let mut ident = ident;
2388-
if ident.span.glob_adjust(module.expansion, binding.span).is_none() {
2389-
continue;
2390-
}
2391-
if self
2392-
.r
2393-
.resolve_ident_in_module_unadjusted(
2394-
ModuleOrUniformRoot::Module(module),
2395-
ident,
2396-
ns,
2397-
&self.parent_scope,
2398-
false,
2399-
module.span,
2400-
)
2401-
.is_ok()
2402-
{
2403-
let import_ids = self.find_transitive_imports(&binding.kind, trait_name);
2404-
let trait_def_id = module.def_id().unwrap();
2405-
found_traits.push(TraitCandidate { def_id: trait_def_id, import_ids });
2406-
}
2407-
} else if let Res::Def(DefKind::TraitAlias, _) = binding.res() {
2408-
// For now, just treat all trait aliases as possible candidates, since we don't
2409-
// know if the ident is somewhere in the transitive bounds.
2410-
let import_ids = self.find_transitive_imports(&binding.kind, trait_name);
2411-
let trait_def_id = binding.res().def_id();
2412-
found_traits.push(TraitCandidate { def_id: trait_def_id, import_ids });
2413-
} else {
2414-
bug!("candidate is not trait or trait alias?")
2415-
}
2416-
}
2417-
}
2418-
2419-
fn find_transitive_imports(
2420-
&mut self,
2421-
mut kind: &NameBindingKind<'_>,
2422-
trait_name: Ident,
2423-
) -> SmallVec<[LocalDefId; 1]> {
2424-
let mut import_ids = smallvec![];
2425-
while let NameBindingKind::Import { import, binding, .. } = kind {
2426-
let id = self.r.local_def_id(import.id);
2427-
self.r.maybe_unused_trait_imports.insert(id);
2428-
self.r.add_to_glob_map(&import, trait_name);
2429-
import_ids.push(id);
2430-
kind = &binding.kind;
2431-
}
2432-
import_ids
2433-
}
24342369
}
24352370

24362371
impl<'a> Resolver<'a> {

Diff for: src/librustc_resolve/lib.rs

+115-1
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ use rustc_index::vec::IndexVec;
4343
use rustc_metadata::creader::{CStore, CrateLoader};
4444
use rustc_middle::hir::exports::ExportMap;
4545
use rustc_middle::middle::cstore::{CrateStore, MetadataLoaderDyn};
46-
use rustc_middle::span_bug;
4746
use rustc_middle::ty::query::Providers;
4847
use rustc_middle::ty::{self, DefIdTree, ResolverOutputs};
48+
use rustc_middle::{bug, span_bug};
4949
use rustc_session::lint;
5050
use rustc_session::lint::{BuiltinLintDiagnostics, LintBuffer};
5151
use rustc_session::Session;
@@ -54,6 +54,7 @@ use rustc_span::source_map::Spanned;
5454
use rustc_span::symbol::{kw, sym, Ident, Symbol};
5555
use rustc_span::{Span, DUMMY_SP};
5656

57+
use smallvec::{smallvec, SmallVec};
5758
use std::cell::{Cell, RefCell};
5859
use std::collections::BTreeSet;
5960
use std::{cmp, fmt, iter, ptr};
@@ -521,6 +522,29 @@ impl<'a> ModuleData<'a> {
521522
}
522523
}
523524

525+
/// This modifies `self` in place. The traits will be stored in `self.traits`.
526+
fn ensure_traits<R>(&'a self, resolver: &mut R)
527+
where
528+
R: AsMut<Resolver<'a>>,
529+
{
530+
let mut traits = self.traits.borrow_mut();
531+
if traits.is_none() {
532+
let mut collected_traits = Vec::new();
533+
self.for_each_child(resolver, |_, name, ns, binding| {
534+
if ns != TypeNS {
535+
return;
536+
}
537+
match binding.res() {
538+
Res::Def(DefKind::Trait | DefKind::TraitAlias, _) => {
539+
collected_traits.push((name, binding))
540+
}
541+
_ => (),
542+
}
543+
});
544+
*traits = Some(collected_traits.into_boxed_slice());
545+
}
546+
}
547+
524548
fn res(&self) -> Option<Res> {
525549
match self.kind {
526550
ModuleKind::Def(kind, def_id, _) => Some(Res::Def(kind, def_id)),
@@ -1430,6 +1454,68 @@ impl<'a> Resolver<'a> {
14301454
self.crate_loader.postprocess(krate);
14311455
}
14321456

1457+
fn get_traits_in_module_containing_item(
1458+
&mut self,
1459+
ident: Ident,
1460+
ns: Namespace,
1461+
module: Module<'a>,
1462+
found_traits: &mut Vec<TraitCandidate>,
1463+
parent_scope: &ParentScope<'a>,
1464+
) {
1465+
assert!(ns == TypeNS || ns == ValueNS);
1466+
module.ensure_traits(self);
1467+
let traits = module.traits.borrow();
1468+
1469+
for &(trait_name, binding) in traits.as_ref().unwrap().iter() {
1470+
// Traits have pseudo-modules that can be used to search for the given ident.
1471+
if let Some(module) = binding.module() {
1472+
let mut ident = ident;
1473+
if ident.span.glob_adjust(module.expansion, binding.span).is_none() {
1474+
continue;
1475+
}
1476+
if self
1477+
.resolve_ident_in_module_unadjusted(
1478+
ModuleOrUniformRoot::Module(module),
1479+
ident,
1480+
ns,
1481+
parent_scope,
1482+
false,
1483+
module.span,
1484+
)
1485+
.is_ok()
1486+
{
1487+
let import_ids = self.find_transitive_imports(&binding.kind, trait_name);
1488+
let trait_def_id = module.def_id().unwrap();
1489+
found_traits.push(TraitCandidate { def_id: trait_def_id, import_ids });
1490+
}
1491+
} else if let Res::Def(DefKind::TraitAlias, _) = binding.res() {
1492+
// For now, just treat all trait aliases as possible candidates, since we don't
1493+
// know if the ident is somewhere in the transitive bounds.
1494+
let import_ids = self.find_transitive_imports(&binding.kind, trait_name);
1495+
let trait_def_id = binding.res().def_id();
1496+
found_traits.push(TraitCandidate { def_id: trait_def_id, import_ids });
1497+
} else {
1498+
bug!("candidate is not trait or trait alias?")
1499+
}
1500+
}
1501+
}
1502+
1503+
fn find_transitive_imports(
1504+
&mut self,
1505+
mut kind: &NameBindingKind<'_>,
1506+
trait_name: Ident,
1507+
) -> SmallVec<[LocalDefId; 1]> {
1508+
let mut import_ids = smallvec![];
1509+
while let NameBindingKind::Import { import, binding, .. } = kind {
1510+
let id = self.local_def_id(import.id);
1511+
self.maybe_unused_trait_imports.insert(id);
1512+
self.add_to_glob_map(&import, trait_name);
1513+
import_ids.push(id);
1514+
kind = &binding.kind;
1515+
}
1516+
import_ids
1517+
}
1518+
14331519
fn new_module(
14341520
&self,
14351521
parent: Module<'a>,
@@ -3039,6 +3125,34 @@ impl<'a> Resolver<'a> {
30393125
})
30403126
}
30413127

3128+
/// This is equivalent to `get_traits_in_module_containing_item`, but without filtering by the associated item.
3129+
///
3130+
/// This is used by rustdoc for intra-doc links.
3131+
pub fn traits_in_scope(&mut self, module_id: DefId) -> Vec<TraitCandidate> {
3132+
let module = self.get_module(module_id);
3133+
module.ensure_traits(self);
3134+
let traits = module.traits.borrow();
3135+
let to_candidate =
3136+
|this: &mut Self, &(trait_name, binding): &(Ident, &NameBinding<'_>)| TraitCandidate {
3137+
def_id: binding.res().def_id(),
3138+
import_ids: this.find_transitive_imports(&binding.kind, trait_name),
3139+
};
3140+
3141+
let mut candidates: Vec<_> =
3142+
traits.as_ref().unwrap().iter().map(|x| to_candidate(self, x)).collect();
3143+
3144+
if let Some(prelude) = self.prelude {
3145+
if !module.no_implicit_prelude {
3146+
prelude.ensure_traits(self);
3147+
candidates.extend(
3148+
prelude.traits.borrow().as_ref().unwrap().iter().map(|x| to_candidate(self, x)),
3149+
);
3150+
}
3151+
}
3152+
3153+
candidates
3154+
}
3155+
30423156
/// Rustdoc uses this to resolve things in a recoverable way. `ResolutionError<'a>`
30433157
/// isn't something that can be returned because it can't be made to live that long,
30443158
/// and also it's a private type. Fortunately rustdoc doesn't need to know the error,

Diff for: src/librustdoc/clean/types.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_hir::lang_items;
2020
use rustc_hir::Mutability;
2121
use rustc_index::vec::IndexVec;
2222
use rustc_middle::middle::stability;
23-
use rustc_middle::ty::TyCtxt;
23+
use rustc_middle::ty::{AssocKind, TyCtxt};
2424
use rustc_span::hygiene::MacroKind;
2525
use rustc_span::source_map::DUMMY_SP;
2626
use rustc_span::symbol::{kw, sym, Ident, Symbol};
@@ -282,12 +282,21 @@ pub enum ItemEnum {
282282
}
283283

284284
impl ItemEnum {
285-
pub fn is_associated(&self) -> bool {
285+
pub fn is_type_alias(&self) -> bool {
286286
match *self {
287287
ItemEnum::TypedefItem(_, _) | ItemEnum::AssocTypeItem(_, _) => true,
288288
_ => false,
289289
}
290290
}
291+
292+
pub fn as_assoc_kind(&self) -> Option<AssocKind> {
293+
match *self {
294+
ItemEnum::AssocConstItem(..) => Some(AssocKind::Const),
295+
ItemEnum::AssocTypeItem(..) => Some(AssocKind::Type),
296+
ItemEnum::TyMethodItem(..) | ItemEnum::MethodItem(..) => Some(AssocKind::Fn),
297+
_ => None,
298+
}
299+
}
291300
}
292301

293302
#[derive(Clone, Debug)]

Diff for: src/librustdoc/core.rs

+6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ pub struct DocContext<'tcx> {
6969
pub auto_traits: Vec<DefId>,
7070
/// The options given to rustdoc that could be relevant to a pass.
7171
pub render_options: RenderOptions,
72+
/// The traits in scope for a given module.
73+
///
74+
/// See `collect_intra_doc_links::traits_implemented_by` for more details.
75+
/// `map<module, set<trait>>`
76+
pub module_trait_cache: RefCell<FxHashMap<DefId, FxHashSet<DefId>>>,
7277
}
7378

7479
impl<'tcx> DocContext<'tcx> {
@@ -510,6 +515,7 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
510515
.filter(|trait_def_id| tcx.trait_is_auto(*trait_def_id))
511516
.collect(),
512517
render_options,
518+
module_trait_cache: RefCell::new(FxHashMap::default()),
513519
};
514520
debug!("crate: {:?}", tcx.hir().krate());
515521

Diff for: src/librustdoc/html/render/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3612,7 +3612,7 @@ fn render_impl(
36123612
};
36133613

36143614
let (is_hidden, extra_class) =
3615-
if (trait_.is_none() || item.doc_value().is_some() || item.inner.is_associated())
3615+
if (trait_.is_none() || item.doc_value().is_some() || item.inner.is_type_alias())
36163616
&& !is_default_item
36173617
{
36183618
(false, "")

0 commit comments

Comments
 (0)