Skip to content

Commit

Permalink
Add support for struct & trait methods
Browse files Browse the repository at this point in the history
  • Loading branch information
zacps committed Aug 31, 2020
1 parent 929a967 commit d4b4d2f
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 27 deletions.
133 changes: 108 additions & 25 deletions crates/hir/src/doc_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Resolvable + Clone>(
db: &dyn HirDatabase,
Expand All @@ -21,7 +28,6 @@ pub fn resolve_doc_link<T: Resolvable + Clone>(
}

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

get_doc_link_impl(db, &module_def)
Expand All @@ -35,8 +41,31 @@ pub fn get_doc_link<T: Resolvable + Clone>(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<String> {
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();
Expand All @@ -47,7 +76,28 @@ fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String>

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())
}

Expand Down Expand Up @@ -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<Url> {
krate
.get_html_root_url(db)
Expand All @@ -175,7 +213,10 @@ fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option<Url> {

/// 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<String> {
Some(match definition {
ModuleDef::Adt(adt) => match adt {
Expand All @@ -196,6 +237,30 @@ fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<S
})
}

enum FieldOrAssocItem {
Field(Field),
AssocItem(AssocItem),
}

/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
///
/// ```
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
/// ^^^^^^^^^^^^^^
/// ```
fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> {
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<Namespace>,
Expand Down Expand Up @@ -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<Resolver>;
Expand Down
4 changes: 2 additions & 2 deletions crates/ide/src/link_rewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ pub fn get_doc_url(db: &RootDatabase, position: &FilePosition) -> Option<Documen
let node = token.parent();
let definition = match_ast! {
match node {
ast::NameRef(name_ref) => 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,
}
};
Expand Down

0 comments on commit d4b4d2f

Please sign in to comment.