Skip to content

Commit af4ba46

Browse files
committed
Auto merge of #15405 - lowr:patch/doc-links-to-fields, r=Veykril
Support doc links that resolve to fields Fixes #15331 Also removes `Resolver::resolve_module_path_in_trait_assoc_items()` and reimplements it in hir with other `Resolver` methods to decouple things a bit.
2 parents 783130b + 0c433c2 commit af4ba46

File tree

5 files changed

+219
-68
lines changed

5 files changed

+219
-68
lines changed

crates/hir-def/src/resolver.rs

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ use crate::{
2121
path::{ModPath, Path, PathKind},
2222
per_ns::PerNs,
2323
visibility::{RawVisibility, Visibility},
24-
AdtId, AssocItemId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId,
25-
EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId,
26-
HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId,
27-
MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId,
28-
TypeAliasId, TypeOrConstParamId, TypeOwnerId, TypeParamId, UseId, VariantId,
24+
AdtId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId,
25+
ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId,
26+
ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId,
27+
ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId,
28+
TypeOrConstParamId, TypeOwnerId, TypeParamId, UseId, VariantId,
2929
};
3030

3131
#[derive(Debug, Clone)]
@@ -148,34 +148,6 @@ impl Resolver {
148148
self.resolve_module_path(db, path, BuiltinShadowMode::Module)
149149
}
150150

151-
// FIXME: This shouldn't exist
152-
pub fn resolve_module_path_in_trait_assoc_items(
153-
&self,
154-
db: &dyn DefDatabase,
155-
path: &ModPath,
156-
) -> Option<PerNs> {
157-
let (item_map, module) = self.item_scope();
158-
let (module_res, idx) =
159-
item_map.resolve_path(db, module, path, BuiltinShadowMode::Module, None);
160-
match module_res.take_types()? {
161-
ModuleDefId::TraitId(it) => {
162-
let idx = idx?;
163-
let unresolved = &path.segments()[idx..];
164-
let assoc = match unresolved {
165-
[it] => it,
166-
_ => return None,
167-
};
168-
let &(_, assoc) = db.trait_data(it).items.iter().find(|(n, _)| n == assoc)?;
169-
Some(match assoc {
170-
AssocItemId::FunctionId(it) => PerNs::values(it.into(), Visibility::Public),
171-
AssocItemId::ConstId(it) => PerNs::values(it.into(), Visibility::Public),
172-
AssocItemId::TypeAliasId(it) => PerNs::types(it.into(), Visibility::Public),
173-
})
174-
}
175-
_ => None,
176-
}
177-
}
178-
179151
pub fn resolve_path_in_type_ns(
180152
&self,
181153
db: &dyn DefDatabase,

crates/hir/src/attrs.rs

Lines changed: 146 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
use hir_def::{
44
attr::{AttrsWithOwner, Documentation},
55
item_scope::ItemInNs,
6-
path::ModPath,
7-
resolver::HasResolver,
8-
AttrDefId, GenericParamId, ModuleDefId,
6+
path::{ModPath, Path},
7+
resolver::{HasResolver, Resolver, TypeNs},
8+
AssocItemId, AttrDefId, GenericParamId, ModuleDefId,
99
};
10-
use hir_expand::hygiene::Hygiene;
10+
use hir_expand::{hygiene::Hygiene, name::Name};
1111
use hir_ty::db::HirDatabase;
1212
use syntax::{ast, AstNode};
1313

1414
use crate::{
15-
Adt, AssocItem, Const, ConstParam, Enum, ExternCrateDecl, Field, Function, GenericParam, Impl,
16-
LifetimeParam, Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias,
17-
TypeParam, Union, Variant,
15+
Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, Enum, ExternCrateDecl, Field,
16+
Function, GenericParam, Impl, LifetimeParam, Macro, Module, ModuleDef, Static, Struct, Trait,
17+
TraitAlias, TypeAlias, TypeParam, Union, Variant, VariantDef,
1818
};
1919

2020
pub trait HasAttrs {
@@ -25,7 +25,7 @@ pub trait HasAttrs {
2525
db: &dyn HirDatabase,
2626
link: &str,
2727
ns: Option<Namespace>,
28-
) -> Option<ModuleDef>;
28+
) -> Option<DocLinkDef>;
2929
}
3030

3131
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
@@ -35,6 +35,13 @@ pub enum Namespace {
3535
Macros,
3636
}
3737

38+
/// Subset of `ide_db::Definition` that doc links can resolve to.
39+
pub enum DocLinkDef {
40+
ModuleDef(ModuleDef),
41+
Field(Field),
42+
SelfType(Trait),
43+
}
44+
3845
macro_rules! impl_has_attrs {
3946
($(($def:ident, $def_id:ident),)*) => {$(
4047
impl HasAttrs for $def {
@@ -46,9 +53,14 @@ macro_rules! impl_has_attrs {
4653
let def = AttrDefId::$def_id(self.into());
4754
db.attrs(def).docs()
4855
}
49-
fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> {
56+
fn resolve_doc_path(
57+
self,
58+
db: &dyn HirDatabase,
59+
link: &str,
60+
ns: Option<Namespace>
61+
) -> Option<DocLinkDef> {
5062
let def = AttrDefId::$def_id(self.into());
51-
resolve_doc_path(db, def, link, ns).map(ModuleDef::from)
63+
resolve_doc_path(db, def, link, ns)
5264
}
5365
}
5466
)*};
@@ -79,7 +91,12 @@ macro_rules! impl_has_attrs_enum {
7991
fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
8092
$enum::$variant(self).docs(db)
8193
}
82-
fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> {
94+
fn resolve_doc_path(
95+
self,
96+
db: &dyn HirDatabase,
97+
link: &str,
98+
ns: Option<Namespace>
99+
) -> Option<DocLinkDef> {
83100
$enum::$variant(self).resolve_doc_path(db, link, ns)
84101
}
85102
}
@@ -111,7 +128,7 @@ impl HasAttrs for AssocItem {
111128
db: &dyn HirDatabase,
112129
link: &str,
113130
ns: Option<Namespace>,
114-
) -> Option<ModuleDef> {
131+
) -> Option<DocLinkDef> {
115132
match self {
116133
AssocItem::Function(it) => it.resolve_doc_path(db, link, ns),
117134
AssocItem::Const(it) => it.resolve_doc_path(db, link, ns),
@@ -147,9 +164,9 @@ impl HasAttrs for ExternCrateDecl {
147164
db: &dyn HirDatabase,
148165
link: &str,
149166
ns: Option<Namespace>,
150-
) -> Option<ModuleDef> {
167+
) -> Option<DocLinkDef> {
151168
let def = AttrDefId::ExternCrateId(self.into());
152-
resolve_doc_path(db, def, link, ns).map(ModuleDef::from)
169+
resolve_doc_path(db, def, link, ns)
153170
}
154171
}
155172

@@ -159,7 +176,7 @@ fn resolve_doc_path(
159176
def: AttrDefId,
160177
link: &str,
161178
ns: Option<Namespace>,
162-
) -> Option<ModuleDefId> {
179+
) -> Option<DocLinkDef> {
163180
let resolver = match def {
164181
AttrDefId::ModuleId(it) => it.resolver(db.upcast()),
165182
AttrDefId::FieldId(it) => it.parent.resolver(db.upcast()),
@@ -184,32 +201,128 @@ fn resolve_doc_path(
184201
.resolver(db.upcast()),
185202
};
186203

187-
let modpath = {
188-
// FIXME: this is not how we should get a mod path here
204+
let mut modpath = modpath_from_str(db, link)?;
205+
206+
let resolved = resolver.resolve_module_path_in_items(db.upcast(), &modpath);
207+
if resolved.is_none() {
208+
let last_name = modpath.pop_segment()?;
209+
resolve_assoc_or_field(db, resolver, modpath, last_name, ns)
210+
} else {
211+
let def = match ns {
212+
Some(Namespace::Types) => resolved.take_types(),
213+
Some(Namespace::Values) => resolved.take_values(),
214+
Some(Namespace::Macros) => resolved.take_macros().map(ModuleDefId::MacroId),
215+
None => resolved.iter_items().next().map(|it| match it {
216+
ItemInNs::Types(it) => it,
217+
ItemInNs::Values(it) => it,
218+
ItemInNs::Macros(it) => ModuleDefId::MacroId(it),
219+
}),
220+
};
221+
Some(DocLinkDef::ModuleDef(def?.into()))
222+
}
223+
}
224+
225+
fn resolve_assoc_or_field(
226+
db: &dyn HirDatabase,
227+
resolver: Resolver,
228+
path: ModPath,
229+
name: Name,
230+
ns: Option<Namespace>,
231+
) -> Option<DocLinkDef> {
232+
let path = Path::from_known_path_with_no_generic(path);
233+
// FIXME: This does not handle `Self` on trait definitions, which we should resolve to the
234+
// trait itself.
235+
let base_def = resolver.resolve_path_in_type_ns_fully(db.upcast(), &path)?;
236+
237+
let ty = match base_def {
238+
TypeNs::SelfType(id) => Impl::from(id).self_ty(db),
239+
TypeNs::GenericParam(_) => {
240+
// Even if this generic parameter has some trait bounds, rustdoc doesn't
241+
// resolve `name` to trait items.
242+
return None;
243+
}
244+
TypeNs::AdtId(id) | TypeNs::AdtSelfType(id) => Adt::from(id).ty(db),
245+
TypeNs::EnumVariantId(id) => {
246+
// Enum variants don't have path candidates.
247+
let variant = Variant::from(id);
248+
return resolve_field(db, variant.into(), name, ns);
249+
}
250+
TypeNs::TypeAliasId(id) => {
251+
let alias = TypeAlias::from(id);
252+
if alias.as_assoc_item(db).is_some() {
253+
// We don't normalize associated type aliases, so we have nothing to
254+
// resolve `name` to.
255+
return None;
256+
}
257+
alias.ty(db)
258+
}
259+
TypeNs::BuiltinType(id) => BuiltinType::from(id).ty(db),
260+
TypeNs::TraitId(id) => {
261+
// Doc paths in this context may only resolve to an item of this trait
262+
// (i.e. no items of its supertraits), so we need to handle them here
263+
// independently of others.
264+
return db.trait_data(id).items.iter().find(|it| it.0 == name).map(|(_, assoc_id)| {
265+
let def = match *assoc_id {
266+
AssocItemId::FunctionId(it) => ModuleDef::Function(it.into()),
267+
AssocItemId::ConstId(it) => ModuleDef::Const(it.into()),
268+
AssocItemId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()),
269+
};
270+
DocLinkDef::ModuleDef(def)
271+
});
272+
}
273+
TypeNs::TraitAliasId(_) => {
274+
// XXX: Do these get resolved?
275+
return None;
276+
}
277+
};
278+
279+
// FIXME: Resolve associated items here, e.g. `Option::map`. Note that associated items take
280+
// precedence over fields.
281+
282+
let variant_def = match ty.as_adt()? {
283+
Adt::Struct(it) => it.into(),
284+
Adt::Union(it) => it.into(),
285+
Adt::Enum(_) => return None,
286+
};
287+
resolve_field(db, variant_def, name, ns)
288+
}
289+
290+
fn resolve_field(
291+
db: &dyn HirDatabase,
292+
def: VariantDef,
293+
name: Name,
294+
ns: Option<Namespace>,
295+
) -> Option<DocLinkDef> {
296+
if let Some(Namespace::Types | Namespace::Macros) = ns {
297+
return None;
298+
}
299+
def.fields(db).into_iter().find(|f| f.name(db) == name).map(DocLinkDef::Field)
300+
}
301+
302+
fn modpath_from_str(db: &dyn HirDatabase, link: &str) -> Option<ModPath> {
303+
// FIXME: this is not how we should get a mod path here.
304+
let try_get_modpath = |link: &str| {
189305
let ast_path = ast::SourceFile::parse(&format!("type T = {link};"))
190306
.syntax_node()
191307
.descendants()
192308
.find_map(ast::Path::cast)?;
193309
if ast_path.syntax().text() != link {
194310
return None;
195311
}
196-
ModPath::from_src(db.upcast(), ast_path, &Hygiene::new_unhygienic())?
312+
ModPath::from_src(db.upcast(), ast_path, &Hygiene::new_unhygienic())
197313
};
198314

199-
let resolved = resolver.resolve_module_path_in_items(db.upcast(), &modpath);
200-
let resolved = if resolved.is_none() {
201-
resolver.resolve_module_path_in_trait_assoc_items(db.upcast(), &modpath)?
202-
} else {
203-
resolved
204-
};
205-
match ns {
206-
Some(Namespace::Types) => resolved.take_types(),
207-
Some(Namespace::Values) => resolved.take_values(),
208-
Some(Namespace::Macros) => resolved.take_macros().map(ModuleDefId::MacroId),
209-
None => resolved.iter_items().next().map(|it| match it {
210-
ItemInNs::Types(it) => it,
211-
ItemInNs::Values(it) => it,
212-
ItemInNs::Macros(it) => ModuleDefId::MacroId(it),
213-
}),
315+
let full = try_get_modpath(link);
316+
if full.is_some() {
317+
return full;
214318
}
319+
320+
// Tuple field names cannot be a part of `ModPath` usually, but rustdoc can
321+
// resolve doc paths like `TupleStruct::0`.
322+
// FIXME: Find a better way to handle these.
323+
let (base, maybe_tuple_field) = link.rsplit_once("::")?;
324+
let tuple_field = Name::new_tuple_field(maybe_tuple_field.parse().ok()?);
325+
let mut modpath = try_get_modpath(base)?;
326+
modpath.push_segment(tuple_field);
327+
Some(modpath)
215328
}

crates/hir/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ use triomphe::Arc;
8787
use crate::db::{DefDatabase, HirDatabase};
8888

8989
pub use crate::{
90-
attrs::{HasAttrs, Namespace},
90+
attrs::{DocLinkDef, HasAttrs, Namespace},
9191
diagnostics::{
9292
AnyDiagnostic, BreakOutsideOfLoop, CaseType, ExpectedFunction, InactiveCode,
9393
IncoherentImpl, IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError,

crates/ide-db/src/defs.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
use arrayvec::ArrayVec;
99
use hir::{
10-
Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper,
10+
Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, DocLinkDef,
1111
ExternCrateDecl, Field, Function, GenericParam, HasVisibility, Impl, Label, Local, Macro,
1212
Module, ModuleDef, Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias,
1313
TypeAlias, Variant, Visibility,
@@ -649,3 +649,13 @@ impl From<ModuleDef> for Definition {
649649
}
650650
}
651651
}
652+
653+
impl From<DocLinkDef> for Definition {
654+
fn from(def: DocLinkDef) -> Self {
655+
match def {
656+
DocLinkDef::ModuleDef(it) => it.into(),
657+
DocLinkDef::Field(it) => it.into(),
658+
DocLinkDef::SelfType(it) => it.into(),
659+
}
660+
}
661+
}

0 commit comments

Comments
 (0)