Skip to content

Commit a06d736

Browse files
committed
Add support for struct & trait methods
1 parent bfda0d2 commit a06d736

File tree

1 file changed

+83
-7
lines changed

1 file changed

+83
-7
lines changed

crates/ide/src/link_rewrite.rs

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ pub fn remove_links(markdown: &str) -> String {
9191
}
9292

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

9796
get_doc_link_impl(db, &module_def)
@@ -105,8 +104,31 @@ pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T)
105104
// BUG: For methods
106105
// import_map.path_of(ns) fails, is not designed to resolve methods
107106
fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String> {
108-
eprintln!("get_doc_link_impl: {:#?}", moddef);
109-
let ns = ItemInNs::Types(moddef.clone().into());
107+
// Get the outermost definition for the moduledef. This is used to resolve the public path to the type,
108+
// then we can join the method, field, etc onto it if required.
109+
let target_def: ModuleDef = match moddef {
110+
ModuleDef::Function(f) => match f.as_assoc_item(db).map(|assoc| assoc.container(db)) {
111+
Some(AssocItemContainer::Trait(t)) => t.into(),
112+
Some(AssocItemContainer::ImplDef(imp)) => {
113+
let resolver = ModuleId::from(imp.module(db)).resolver(db.upcast());
114+
let ctx = TyLoweringContext::new(db, &resolver);
115+
Adt::from(
116+
Ty::from_hir(
117+
&ctx,
118+
&imp.target_trait(db).unwrap_or_else(|| imp.target_type(db)),
119+
)
120+
.as_adt()
121+
.map(|t| t.0)
122+
.unwrap(),
123+
)
124+
.into()
125+
}
126+
None => ModuleDef::Function(*f),
127+
},
128+
moddef => *moddef,
129+
};
130+
131+
let ns = ItemInNs::Types(target_def.clone().into());
110132

111133
let module = moddef.module(db)?;
112134
let krate = module.krate();
@@ -117,7 +139,28 @@ fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String>
117139

118140
get_doc_url(db, &krate)
119141
.and_then(|url| url.join(&base).ok())
120-
.and_then(|url| get_symbol_filename(db, &moddef).as_deref().and_then(|f| url.join(f).ok()))
142+
.and_then(|url| {
143+
get_symbol_filename(db, &target_def).as_deref().and_then(|f| url.join(f).ok())
144+
})
145+
.and_then(|url| match moddef {
146+
ModuleDef::Function(f) => {
147+
get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(*f)))
148+
.as_deref()
149+
.and_then(|f| url.join(f).ok())
150+
}
151+
ModuleDef::Const(c) => {
152+
get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(*c)))
153+
.as_deref()
154+
.and_then(|f| url.join(f).ok())
155+
}
156+
ModuleDef::TypeAlias(ty) => {
157+
get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(*ty)))
158+
.as_deref()
159+
.and_then(|f| url.join(f).ok())
160+
}
161+
// TODO: Field <- this requires passing in a definition or something
162+
_ => Some(url),
163+
})
121164
.map(|url| url.into_string())
122165
}
123166

@@ -307,6 +350,12 @@ fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> {
307350
.next()
308351
}
309352

353+
/// Get the root URL for the documentation of a crate.
354+
///
355+
/// ```
356+
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
357+
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
358+
/// ```
310359
fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
311360
krate
312361
.get_html_root_url(db)
@@ -323,8 +372,11 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
323372

324373
/// Get the filename and extension generated for a symbol by rustdoc.
325374
///
326-
/// Example: `struct.Shard.html`
327-
fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<String> {
375+
/// ```
376+
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
377+
/// ^^^^^^^^^^^^^^^^^^^
378+
/// ```
379+
fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> {
328380
Some(match definition {
329381
ModuleDef::Adt(adt) => match adt {
330382
Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
@@ -334,7 +386,7 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
334386
ModuleDef::Module(_) => "index.html".to_string(),
335387
ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
336388
ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
337-
ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t),
389+
ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
338390
ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
339391
ModuleDef::EnumVariant(ev) => {
340392
format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
@@ -344,6 +396,30 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
344396
})
345397
}
346398

399+
enum FieldOrAssocItem {
400+
Field(Field),
401+
AssocItem(AssocItem),
402+
}
403+
404+
/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
405+
///
406+
/// ```
407+
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
408+
/// ^^^^^^^^^^^^^^
409+
/// ```
410+
fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> {
411+
Some(match field_or_assoc {
412+
FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
413+
FieldOrAssocItem::AssocItem(assoc) => match assoc {
414+
// TODO: Rustdoc sometimes uses tymethod instead of method. This case needs to be investigated.
415+
AssocItem::Function(function) => format!("#method.{}", function.name(db)),
416+
// TODO: This might be the old method for documenting associated constants, i32::MAX uses a separate page...
417+
AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?),
418+
AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)),
419+
},
420+
})
421+
}
422+
347423
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
348424
return tokens.max_by_key(priority);
349425
fn priority(n: &SyntaxToken) -> usize {

0 commit comments

Comments
 (0)