From a06d736b77770e4c1e738086c81b4fd60fcfcb23 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Mon, 31 Aug 2020 20:26:55 +1200 Subject: Add support for struct & trait methods --- crates/ide/src/link_rewrite.rs | 90 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs index 80005c06e..1e102997f 100644 --- a/crates/ide/src/link_rewrite.rs +++ b/crates/ide/src/link_rewrite.rs @@ -91,7 +91,6 @@ pub fn remove_links(markdown: &str) -> String { } pub fn get_doc_link(db: &dyn HirDatabase, definition: &T) -> Option { - eprintln!("hir::doc_links::get_doc_link"); let module_def = definition.clone().try_into_module_def()?; get_doc_link_impl(db, &module_def) @@ -105,8 +104,31 @@ pub fn get_doc_link(db: &dyn HirDatabase, definition: &T) // BUG: For methods // import_map.path_of(ns) fails, is not designed to resolve methods fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option { - eprintln!("get_doc_link_impl: {:#?}", moddef); - let ns = ItemInNs::Types(moddef.clone().into()); + // Get the outermost definition for the moduledef. This is used to resolve the public path to the type, + // then we can join the method, field, etc onto it if required. + let target_def: ModuleDef = match moddef { + ModuleDef::Function(f) => match f.as_assoc_item(db).map(|assoc| assoc.container(db)) { + Some(AssocItemContainer::Trait(t)) => t.into(), + Some(AssocItemContainer::ImplDef(imp)) => { + let resolver = ModuleId::from(imp.module(db)).resolver(db.upcast()); + let ctx = TyLoweringContext::new(db, &resolver); + Adt::from( + Ty::from_hir( + &ctx, + &imp.target_trait(db).unwrap_or_else(|| imp.target_type(db)), + ) + .as_adt() + .map(|t| t.0) + .unwrap(), + ) + .into() + } + None => ModuleDef::Function(*f), + }, + moddef => *moddef, + }; + + let ns = ItemInNs::Types(target_def.clone().into()); let module = moddef.module(db)?; let krate = module.krate(); @@ -117,7 +139,28 @@ fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option get_doc_url(db, &krate) .and_then(|url| url.join(&base).ok()) - .and_then(|url| get_symbol_filename(db, &moddef).as_deref().and_then(|f| url.join(f).ok())) + .and_then(|url| { + get_symbol_filename(db, &target_def).as_deref().and_then(|f| url.join(f).ok()) + }) + .and_then(|url| match moddef { + ModuleDef::Function(f) => { + get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(*f))) + .as_deref() + .and_then(|f| url.join(f).ok()) + } + ModuleDef::Const(c) => { + get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(*c))) + .as_deref() + .and_then(|f| url.join(f).ok()) + } + ModuleDef::TypeAlias(ty) => { + get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(*ty))) + .as_deref() + .and_then(|f| url.join(f).ok()) + } + // TODO: Field <- this requires passing in a definition or something + _ => Some(url), + }) .map(|url| url.into_string()) } @@ -307,6 +350,12 @@ fn ns_from_intra_spec(s: &str) -> Option { .next() } +/// Get the root URL for the documentation of a crate. +/// +/// ``` +/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next +/// ^^^^^^^^^^^^^^^^^^^^^^^^^^ +/// ``` fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { krate .get_html_root_url(db) @@ -323,8 +372,11 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { /// Get the filename and extension generated for a symbol by rustdoc. /// -/// Example: `struct.Shard.html` -fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option { +/// ``` +/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next +/// ^^^^^^^^^^^^^^^^^^^ +/// ``` +fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option { Some(match definition { ModuleDef::Adt(adt) => match adt { Adt::Struct(s) => format!("struct.{}.html", s.name(db)), @@ -334,7 +386,7 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option "index.html".to_string(), ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), - ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t), + ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), ModuleDef::EnumVariant(ev) => { 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 Option { + Some(match field_or_assoc { + FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), + FieldOrAssocItem::AssocItem(assoc) => match assoc { + // TODO: Rustdoc sometimes uses tymethod instead of method. This case needs to be investigated. + AssocItem::Function(function) => format!("#method.{}", function.name(db)), + // TODO: This might be the old method for documenting associated constants, i32::MAX uses a separate page... + AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?), + AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)), + }, + }) +} + fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { -- cgit v1.2.3