diff --git a/crates/hir/src/doc_links.rs b/crates/hir/src/doc_links.rs index 309b21405e0d..52098a6e8c6c 100644 --- a/crates/hir/src/doc_links.rs +++ b/crates/hir/src/doc_links.rs @@ -2,12 +2,19 @@ use std::iter::once; -use hir_def::resolver::Resolver; +use hir_def::{ + resolver::{HasResolver, Resolver}, + ModuleId, +}; use itertools::Itertools; use syntax::ast::Path; use url::Url; -use crate::{db::HirDatabase, Adt, AsName, Crate, Hygiene, ItemInNs, ModPath, ModuleDef}; +use crate::{ + db::HirDatabase, Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, + Hygiene, ImplDef, ItemInNs, Local, MacroDef, ModPath, ModuleDef, TypeParam, +}; +use hir_ty::{Ty, TyLoweringContext}; pub fn resolve_doc_link( db: &dyn HirDatabase, @@ -21,7 +28,6 @@ pub fn resolve_doc_link( } 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) @@ -35,8 +41,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(); @@ -47,7 +76,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()) } @@ -145,24 +195,12 @@ fn try_resolve_path(db: &dyn HirDatabase, moddef: &ModuleDef, link_target: &str) .map(|url| url.into_string()) } -/// Strip prefixes, suffixes, and inline code marks from the given string. -fn strip_prefixes_suffixes(mut s: &str) -> &str { - s = s.trim_matches('`'); - - [ - (TYPES.0.iter(), TYPES.1.iter()), - (VALUES.0.iter(), VALUES.1.iter()), - (MACROS.0.iter(), MACROS.1.iter()), - ] - .iter() - .for_each(|(prefixes, suffixes)| { - prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); - suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); - }); - let s = s.trim_start_matches("@").trim(); - s -} - +/// 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: &dyn HirDatabase, krate: &Crate) -> Option { krate .get_html_root_url(db) @@ -175,7 +213,10 @@ fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option { /// Get the filename and extension generated for a symbol by rustdoc. /// -/// Example: `struct.Shard.html` +/// ``` +/// 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 { @@ -196,6 +237,30 @@ fn get_symbol_filename(db: &dyn HirDatabase, 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)), + }, + }) +} + struct IntraDocLink<'s> { path: &'s str, namespace: Option, @@ -262,6 +327,24 @@ impl Namespace { } } +/// Strip prefixes, suffixes, and inline code marks from the given string. +fn strip_prefixes_suffixes(mut s: &str) -> &str { + s = s.trim_matches('`'); + + [ + (TYPES.0.iter(), TYPES.1.iter()), + (VALUES.0.iter(), VALUES.1.iter()), + (MACROS.0.iter(), MACROS.1.iter()), + ] + .iter() + .for_each(|(prefixes, suffixes)| { + prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); + suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); + }); + let s = s.trim_start_matches("@").trim(); + s +} + /// Sealed trait used solely for the generic bound on [`resolve_doc_link`]. pub trait Resolvable { fn resolver(&self, db: &dyn HirDatabase) -> Option; diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs index 9496f2d9b00d..fddb838719d2 100644 --- a/crates/ide/src/link_rewrite.rs +++ b/crates/ide/src/link_rewrite.rs @@ -66,8 +66,8 @@ pub fn get_doc_url(db: &RootDatabase, position: &FilePosition) -> Option classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), - ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), + ast::NameRef(name_ref) => dbg!(classify_name_ref(&sema, &name_ref)).map(|d| d.definition(sema.db)), + ast::Name(name) => dbg!(classify_name(&sema, &name)).map(|d| d.definition(sema.db)), _ => None, } };