9756: internal: `resolve_doc_path` is able to resolve to macros r=Veykril a=Veykril

bors r+

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2021-08-02 12:34:59 +00:00 committed by GitHub
commit e7be544b7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 75 deletions

View File

@ -1,12 +1,15 @@
//! Attributes & documentation for hir types.
use either::Either;
use hir_def::{
attr::{AttrsWithOwner, Documentation},
item_scope::ItemInNs,
path::ModPath,
per_ns::PerNs,
resolver::HasResolver,
AttrDefId, GenericParamId, ModuleDefId,
};
use hir_expand::hygiene::Hygiene;
use hir_expand::{hygiene::Hygiene, MacroDefId};
use hir_ty::db::HirDatabase;
use syntax::ast;
@ -23,7 +26,7 @@ fn resolve_doc_path(
db: &dyn HirDatabase,
link: &str,
ns: Option<Namespace>,
) -> Option<ModuleDef>;
) -> Option<Either<ModuleDef, MacroDef>>;
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
@ -44,9 +47,9 @@ fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
let def = AttrDefId::$def_id(self.into());
db.attrs(def).docs()
}
fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> {
fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<Either<ModuleDef, MacroDef>> {
let def = AttrDefId::$def_id(self.into());
resolve_doc_path(db, def, link, ns).map(ModuleDef::from)
resolve_doc_path(db, def, link, ns).map(|it| it.map_left(ModuleDef::from).map_right(MacroDef::from))
}
}
)*};
@ -76,7 +79,7 @@ fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner {
fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
$enum::$variant(self).docs(db)
}
fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> {
fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<Either<ModuleDef, MacroDef>> {
$enum::$variant(self).resolve_doc_path(db, link, ns)
}
}
@ -108,7 +111,7 @@ fn resolve_doc_path(
db: &dyn HirDatabase,
link: &str,
ns: Option<Namespace>,
) -> Option<ModuleDef> {
) -> Option<Either<ModuleDef, MacroDef>> {
match self {
AssocItem::Function(it) => it.resolve_doc_path(db, link, ns),
AssocItem::Const(it) => it.resolve_doc_path(db, link, ns),
@ -122,7 +125,7 @@ fn resolve_doc_path(
def: AttrDefId,
link: &str,
ns: Option<Namespace>,
) -> Option<ModuleDefId> {
) -> Option<Either<ModuleDefId, MacroDefId>> {
let resolver = match def {
AttrDefId::ModuleId(it) => it.resolver(db.upcast()),
AttrDefId::FieldId(it) => it.parent.resolver(db.upcast()),
@ -140,6 +143,7 @@ fn resolve_doc_path(
GenericParamId::ConstParamId(it) => it.parent,
}
.resolver(db.upcast()),
// FIXME
AttrDefId::MacroDefId(_) => return None,
};
let path = ast::Path::parse(link).ok()?;
@ -151,9 +155,13 @@ fn resolve_doc_path(
resolved
};
match ns {
Some(Namespace::Types) => resolved.take_types(),
Some(Namespace::Values) => resolved.take_values(),
Some(Namespace::Macros) => None,
None => resolved.iter_items().find_map(|it| it.as_module_def_id()),
Some(Namespace::Types) => resolved.take_types().map(Either::Left),
Some(Namespace::Values) => resolved.take_values().map(Either::Left),
Some(Namespace::Macros) => resolved.take_macros().map(Either::Right),
None => resolved.iter_items().next().map(|it| match it {
ItemInNs::Types(it) => Either::Left(it),
ItemInNs::Values(it) => Either::Left(it),
ItemInNs::Macros(it) => Either::Right(it),
}),
}
}

View File

@ -75,6 +75,15 @@ pub(crate) trait TryToNav {
fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>;
}
impl<T: TryToNav, U: TryToNav> TryToNav for Either<T, U> {
fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
match self {
Either::Left(it) => it.try_to_nav(db),
Either::Right(it) => it.try_to_nav(db),
}
}
}
impl NavigationTarget {
pub fn focus_or_full_range(&self) -> TextRange {
self.focus_range.unwrap_or(self.full_range)

View File

@ -4,13 +4,15 @@
use std::convert::{TryFrom, TryInto};
use either::Either;
use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
use stdx::format_to;
use url::Url;
use hir::{
db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, Crate, HasAttrs, ModuleDef,
db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, Crate, HasAttrs, MacroDef,
ModuleDef,
};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
@ -47,7 +49,7 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Defin
return rewritten;
}
if let Definition::ModuleDef(def) = definition {
if let Some(target) = rewrite_url_link(db, def, target) {
if let Some(target) = rewrite_url_link(db, Either::Left(def), target) {
return (target, title.to_string());
}
}
@ -169,7 +171,7 @@ pub(crate) fn resolve_doc_path_for_def(
def: Definition,
link: &str,
ns: Option<hir::Namespace>,
) -> Option<hir::ModuleDef> {
) -> Option<Either<ModuleDef, MacroDef>> {
match def {
Definition::ModuleDef(def) => match def {
hir::ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns),
@ -243,9 +245,9 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(),
};
let frag = get_assoc_item_fragment(db, assoc_item)?;
(def, Some(frag))
(Either::Left(def), Some(frag))
} else {
(def, None)
(Either::Left(def), None)
}
}
Definition::Field(field) => {
@ -254,10 +256,9 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
hir::VariantDef::Union(it) => it.into(),
hir::VariantDef::Variant(it) => it.into(),
};
(def, Some(format!("structfield.{}", field.name(db))))
(Either::Left(def), Some(format!("structfield.{}", field.name(db))))
}
// FIXME macros
Definition::Macro(_) => return None,
Definition::Macro(makro) => (Either::Right(makro), None),
// FIXME impls
Definition::SelfType(_) => return None,
Definition::Local(_) | Definition::GenericParam(_) | Definition::Label(_) => return None,
@ -270,7 +271,7 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
url = url.join(&path).ok()?;
}
url = url.join(&get_symbol_filename(db, &target)?).ok()?;
url = url.join(&get_symbol_filename(db, target)?).ok()?;
url.set_fragment(frag.as_deref());
Some(url.into())
@ -292,24 +293,29 @@ fn rewrite_intra_doc_link(
url = url.join(&path).ok()?;
}
let (resolved, frag) = if let Some(assoc_item) = resolved.as_assoc_item(db) {
let resolved = match assoc_item.container(db) {
AssocItemContainer::Trait(t) => t.into(),
AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(),
let (resolved, frag) =
if let Some(assoc_item) = resolved.left().and_then(|it| it.as_assoc_item(db)) {
let resolved = match assoc_item.container(db) {
AssocItemContainer::Trait(t) => t.into(),
AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(),
};
let frag = get_assoc_item_fragment(db, assoc_item)?;
(Either::Left(resolved), Some(frag))
} else {
(resolved, None)
};
let frag = get_assoc_item_fragment(db, assoc_item)?;
(resolved, Some(frag))
} else {
(resolved, None)
};
url = url.join(&get_symbol_filename(db, &resolved)?).ok()?;
url = url.join(&get_symbol_filename(db, resolved)?).ok()?;
url.set_fragment(frag.as_deref());
Some((url.into(), strip_prefixes_suffixes(title).to_string()))
}
/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<String> {
fn rewrite_url_link(
db: &RootDatabase,
def: Either<ModuleDef, MacroDef>,
target: &str,
) -> Option<String> {
if !(target.contains('#') || target.contains(".html")) {
return None;
}
@ -321,25 +327,35 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
url = url.join(&path).ok()?;
}
url = url.join(&get_symbol_filename(db, &def)?).ok()?;
url = url.join(&get_symbol_filename(db, def)?).ok()?;
url.join(target).ok().map(Into::into)
}
fn crate_of_def(db: &RootDatabase, def: ModuleDef) -> Option<Crate> {
fn crate_of_def(db: &RootDatabase, def: Either<ModuleDef, MacroDef>) -> Option<Crate> {
let krate = match def {
// Definition::module gives back the parent module, we don't want that as it fails for root modules
ModuleDef::Module(module) => module.krate(),
_ => def.module(db)?.krate(),
Either::Left(ModuleDef::Module(module)) => module.krate(),
Either::Left(def) => def.module(db)?.krate(),
Either::Right(def) => def.module(db)?.krate(),
};
Some(krate)
}
fn mod_path_of_def(db: &RootDatabase, def: ModuleDef) -> Option<String> {
def.canonical_module_path(db).map(|it| {
let mut path = String::new();
it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name));
path
})
fn mod_path_of_def(db: &RootDatabase, def: Either<ModuleDef, MacroDef>) -> Option<String> {
match def {
Either::Left(def) => def.canonical_module_path(db).map(|it| {
let mut path = String::new();
it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name));
path
}),
Either::Right(def) => {
def.module(db).map(|it| it.path_to_root(db).into_iter().rev()).map(|it| {
let mut path = String::new();
it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name));
path
})
}
}
}
/// Rewrites a markdown document, applying 'callback' to each link.
@ -405,27 +421,34 @@ fn get_doc_base_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
/// 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 {
Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
Adt::Union(u) => format!("union.{}.html", u.name(db)),
fn get_symbol_filename(
db: &dyn HirDatabase,
definition: Either<ModuleDef, MacroDef>,
) -> Option<String> {
let res = match definition {
Either::Left(definition) => match definition {
ModuleDef::Adt(adt) => match adt {
Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
Adt::Union(u) => format!("union.{}.html", u.name(db)),
},
ModuleDef::Module(m) => match m.name(db) {
Some(name) => format!("{}/index.html", name),
None => String::from("index.html"),
},
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.name()),
ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
ModuleDef::Variant(ev) => {
format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
}
ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?),
ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
},
ModuleDef::Module(m) => match m.name(db) {
Some(name) => format!("{}/index.html", name),
None => String::from("index.html"),
},
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.name()),
ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
ModuleDef::Variant(ev) => {
format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
}
ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?),
ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
})
Either::Right(mac) => format!("macro.{}.html", mac.name(db)?),
};
Some(res)
}
/// Get the fragment required to link to a specific field, method, associated type, or associated constant.

View File

@ -150,7 +150,10 @@ pub(crate) fn hover(
(file_id == position.file_id.into() && mapped_range.contains(position.offset)).then(||(mapped_range, link, ns))
})?;
range = Some(idl_range);
resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
Some(match resolve_doc_path_for_def(db,def, &link,ns)? {
Either::Left(it) => Definition::ModuleDef(it),
Either::Right(it) => Definition::Macro(it),
})
} else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
if let res@Some(_) = try_hover_for_lint(&attr, &token) {
return res;

View File

@ -238,19 +238,22 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri
}
}
fn module_def_to_hl_tag(def: hir::ModuleDef) -> HlTag {
fn module_def_to_hl_tag(def: Either<hir::ModuleDef, hir::MacroDef>) -> HlTag {
let symbol = match def {
hir::ModuleDef::Module(_) => SymbolKind::Module,
hir::ModuleDef::Function(_) => SymbolKind::Function,
hir::ModuleDef::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct,
hir::ModuleDef::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum,
hir::ModuleDef::Adt(hir::Adt::Union(_)) => SymbolKind::Union,
hir::ModuleDef::Variant(_) => SymbolKind::Variant,
hir::ModuleDef::Const(_) => SymbolKind::Const,
hir::ModuleDef::Static(_) => SymbolKind::Static,
hir::ModuleDef::Trait(_) => SymbolKind::Trait,
hir::ModuleDef::TypeAlias(_) => SymbolKind::TypeAlias,
hir::ModuleDef::BuiltinType(_) => return HlTag::BuiltinType,
Either::Left(def) => match def {
hir::ModuleDef::Module(_) => SymbolKind::Module,
hir::ModuleDef::Function(_) => SymbolKind::Function,
hir::ModuleDef::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct,
hir::ModuleDef::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum,
hir::ModuleDef::Adt(hir::Adt::Union(_)) => SymbolKind::Union,
hir::ModuleDef::Variant(_) => SymbolKind::Variant,
hir::ModuleDef::Const(_) => SymbolKind::Const,
hir::ModuleDef::Static(_) => SymbolKind::Static,
hir::ModuleDef::Trait(_) => SymbolKind::Trait,
hir::ModuleDef::TypeAlias(_) => SymbolKind::TypeAlias,
hir::ModuleDef::BuiltinType(_) => return HlTag::BuiltinType,
},
Either::Right(_) => SymbolKind::Macro,
};
HlTag::Symbol(symbol)
}

View File

@ -114,7 +114,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span>
<span class="comment documentation">/// This function is &gt; </span><span class="function documentation injected intra_doc_link">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</span>
<span class="comment documentation">/// [`noop`](noop) is a macro below</span>
<span class="comment documentation">/// </span><span class="macro documentation injected intra_doc_link">[`noop`](noop)</span><span class="comment documentation"> is a macro below</span>
<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation injected intra_doc_link">[`module`]</span>
<span class="comment documentation">///</span>
<span class="comment documentation">/// [`Item`]: module::Item</span>