Skip to content

Commit d4b4d2f

Browse files
committed
Add support for struct & trait methods
1 parent 929a967 commit d4b4d2f

File tree

2 files changed

+110
-27
lines changed

2 files changed

+110
-27
lines changed

crates/hir/src/doc_links.rs

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22
33
use std::iter::once;
44

5-
use hir_def::resolver::Resolver;
5+
use hir_def::{
6+
resolver::{HasResolver, Resolver},
7+
ModuleId,
8+
};
69
use itertools::Itertools;
710
use syntax::ast::Path;
811
use url::Url;
912

10-
use crate::{db::HirDatabase, Adt, AsName, Crate, Hygiene, ItemInNs, ModPath, ModuleDef};
13+
use crate::{
14+
db::HirDatabase, Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field,
15+
Hygiene, ImplDef, ItemInNs, Local, MacroDef, ModPath, ModuleDef, TypeParam,
16+
};
17+
use hir_ty::{Ty, TyLoweringContext};
1118

1219
pub fn resolve_doc_link<T: Resolvable + Clone>(
1320
db: &dyn HirDatabase,
@@ -21,7 +28,6 @@ pub fn resolve_doc_link<T: Resolvable + Clone>(
2128
}
2229

2330
pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T) -> Option<String> {
24-
eprintln!("hir::doc_links::get_doc_link");
2531
let module_def = definition.clone().try_into_module_def()?;
2632

2733
get_doc_link_impl(db, &module_def)
@@ -35,8 +41,31 @@ pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T)
3541
// BUG: For methods
3642
// import_map.path_of(ns) fails, is not designed to resolve methods
3743
fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String> {
38-
eprintln!("get_doc_link_impl: {:#?}", moddef);
39-
let ns = ItemInNs::Types(moddef.clone().into());
44+
// Get the outermost definition for the moduledef. This is used to resolve the public path to the type,
45+
// then we can join the method, field, etc onto it if required.
46+
let target_def: ModuleDef = match moddef {
47+
ModuleDef::Function(f) => match f.as_assoc_item(db).map(|assoc| assoc.container(db)) {
48+
Some(AssocItemContainer::Trait(t)) => t.into(),
49+
Some(AssocItemContainer::ImplDef(imp)) => {
50+
let resolver = ModuleId::from(imp.module(db)).resolver(db.upcast());
51+
let ctx = TyLoweringContext::new(db, &resolver);
52+
Adt::from(
53+
Ty::from_hir(
54+
&ctx,
55+
&imp.target_trait(db).unwrap_or_else(|| imp.target_type(db)),
56+
)
57+
.as_adt()
58+
.map(|t| t.0)
59+
.unwrap(),
60+
)
61+
.into()
62+
}
63+
None => ModuleDef::Function(*f),
64+
},
65+
moddef => *moddef,
66+
};
67+
68+
let ns = ItemInNs::Types(target_def.clone().into());
4069

4170
let module = moddef.module(db)?;
4271
let krate = module.krate();
@@ -47,7 +76,28 @@ fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String>
4776

4877
get_doc_url(db, &krate)
4978
.and_then(|url| url.join(&base).ok())
50-
.and_then(|url| get_symbol_filename(db, &moddef).as_deref().and_then(|f| url.join(f).ok()))
79+
.and_then(|url| {
80+
get_symbol_filename(db, &target_def).as_deref().and_then(|f| url.join(f).ok())
81+
})
82+
.and_then(|url| match moddef {
83+
ModuleDef::Function(f) => {
84+
get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(*f)))
85+
.as_deref()
86+
.and_then(|f| url.join(f).ok())
87+
}
88+
ModuleDef::Const(c) => {
89+
get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(*c)))
90+
.as_deref()
91+
.and_then(|f| url.join(f).ok())
92+
}
93+
ModuleDef::TypeAlias(ty) => {
94+
get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(*ty)))
95+
.as_deref()
96+
.and_then(|f| url.join(f).ok())
97+
}
98+
// TODO: Field <- this requires passing in a definition or something
99+
_ => Some(url),
100+
})
51101
.map(|url| url.into_string())
52102
}
53103

@@ -145,24 +195,12 @@ fn try_resolve_path(db: &dyn HirDatabase, moddef: &ModuleDef, link_target: &str)
145195
.map(|url| url.into_string())
146196
}
147197

148-
/// Strip prefixes, suffixes, and inline code marks from the given string.
149-
fn strip_prefixes_suffixes(mut s: &str) -> &str {
150-
s = s.trim_matches('`');
151-
152-
[
153-
(TYPES.0.iter(), TYPES.1.iter()),
154-
(VALUES.0.iter(), VALUES.1.iter()),
155-
(MACROS.0.iter(), MACROS.1.iter()),
156-
]
157-
.iter()
158-
.for_each(|(prefixes, suffixes)| {
159-
prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix));
160-
suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix));
161-
});
162-
let s = s.trim_start_matches("@").trim();
163-
s
164-
}
165-
198+
/// Get the root URL for the documentation of a crate.
199+
///
200+
/// ```
201+
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
202+
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
203+
/// ```
166204
fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option<Url> {
167205
krate
168206
.get_html_root_url(db)
@@ -175,7 +213,10 @@ fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option<Url> {
175213

176214
/// Get the filename and extension generated for a symbol by rustdoc.
177215
///
178-
/// Example: `struct.Shard.html`
216+
/// ```
217+
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
218+
/// ^^^^^^^^^^^^^^^^^^^
219+
/// ```
179220
fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> {
180221
Some(match definition {
181222
ModuleDef::Adt(adt) => match adt {
@@ -196,6 +237,30 @@ fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<S
196237
})
197238
}
198239

240+
enum FieldOrAssocItem {
241+
Field(Field),
242+
AssocItem(AssocItem),
243+
}
244+
245+
/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
246+
///
247+
/// ```
248+
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
249+
/// ^^^^^^^^^^^^^^
250+
/// ```
251+
fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> {
252+
Some(match field_or_assoc {
253+
FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
254+
FieldOrAssocItem::AssocItem(assoc) => match assoc {
255+
// TODO: Rustdoc sometimes uses tymethod instead of method. This case needs to be investigated.
256+
AssocItem::Function(function) => format!("#method.{}", function.name(db)),
257+
// TODO: This might be the old method for documenting associated constants, i32::MAX uses a separate page...
258+
AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?),
259+
AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)),
260+
},
261+
})
262+
}
263+
199264
struct IntraDocLink<'s> {
200265
path: &'s str,
201266
namespace: Option<Namespace>,
@@ -262,6 +327,24 @@ impl Namespace {
262327
}
263328
}
264329

330+
/// Strip prefixes, suffixes, and inline code marks from the given string.
331+
fn strip_prefixes_suffixes(mut s: &str) -> &str {
332+
s = s.trim_matches('`');
333+
334+
[
335+
(TYPES.0.iter(), TYPES.1.iter()),
336+
(VALUES.0.iter(), VALUES.1.iter()),
337+
(MACROS.0.iter(), MACROS.1.iter()),
338+
]
339+
.iter()
340+
.for_each(|(prefixes, suffixes)| {
341+
prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix));
342+
suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix));
343+
});
344+
let s = s.trim_start_matches("@").trim();
345+
s
346+
}
347+
265348
/// Sealed trait used solely for the generic bound on [`resolve_doc_link`].
266349
pub trait Resolvable {
267350
fn resolver(&self, db: &dyn HirDatabase) -> Option<Resolver>;

crates/ide/src/link_rewrite.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ pub fn get_doc_url(db: &RootDatabase, position: &FilePosition) -> Option<Documen
6666
let node = token.parent();
6767
let definition = match_ast! {
6868
match node {
69-
ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
70-
ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
69+
ast::NameRef(name_ref) => dbg!(classify_name_ref(&sema, &name_ref)).map(|d| d.definition(sema.db)),
70+
ast::Name(name) => dbg!(classify_name(&sema, &name)).map(|d| d.definition(sema.db)),
7171
_ => None,
7272
}
7373
};

0 commit comments

Comments
 (0)