mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-15 01:17:27 +00:00
Auto merge of #15933 - 71:inherent-items-in-docs, r=Veykril
feat: resolve inherent and implemented associated items in docs This partially fixes #9694. Supported: - Trait methods and constants. * Due to resolution differences pointed out during the review of the PR, trait associated types are _not_ supported. - Inherent methods, constants and associated types. * Inherent associated types are a [nightly feature](https://github.com/rust-lang/rust/issues/8995), and are supported with no additional work in this PR. Screenshot of VS Code running with the change: <img width="513" alt="image" src="https://github.com/rust-lang/rust-analyzer/assets/7189784/c37ed8b7-b572-4684-8e81-2a817b0027c4"> You can see that the items are resolved (excl. trait associated types) since they are semantically highlighted in the doc comment.
This commit is contained in:
commit
c3c07c66d3
3 changed files with 102 additions and 12 deletions
|
@ -1,5 +1,7 @@
|
|||
//! Attributes & documentation for hir types.
|
||||
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use base_db::FileId;
|
||||
use hir_def::{
|
||||
attr::AttrsWithOwner,
|
||||
|
@ -13,13 +15,13 @@ use hir_expand::{
|
|||
name::Name,
|
||||
span_map::{RealSpanMap, SpanMapRef},
|
||||
};
|
||||
use hir_ty::db::HirDatabase;
|
||||
use hir_ty::{db::HirDatabase, method_resolution};
|
||||
use syntax::{ast, AstNode};
|
||||
|
||||
use crate::{
|
||||
Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, ExternCrateDecl,
|
||||
Field, Function, GenericParam, Impl, LifetimeParam, Macro, Module, ModuleDef, Static, Struct,
|
||||
Trait, TraitAlias, TypeAlias, TypeParam, Union, Variant, VariantDef,
|
||||
Field, Function, GenericParam, HasCrate, Impl, LifetimeParam, Macro, Module, ModuleDef, Static,
|
||||
Struct, Trait, TraitAlias, Type, TypeAlias, TypeParam, Union, Variant, VariantDef,
|
||||
};
|
||||
|
||||
pub trait HasAttrs {
|
||||
|
@ -205,8 +207,14 @@ fn resolve_assoc_or_field(
|
|||
}
|
||||
};
|
||||
|
||||
// FIXME: Resolve associated items here, e.g. `Option::map`. Note that associated items take
|
||||
// precedence over fields.
|
||||
// Resolve inherent items first, then trait items, then fields.
|
||||
if let Some(assoc_item_def) = resolve_assoc_item(db, &ty, &name, ns) {
|
||||
return Some(assoc_item_def);
|
||||
}
|
||||
|
||||
if let Some(impl_trait_item_def) = resolve_impl_trait_item(db, resolver, &ty, &name, ns) {
|
||||
return Some(impl_trait_item_def);
|
||||
}
|
||||
|
||||
let variant_def = match ty.as_adt()? {
|
||||
Adt::Struct(it) => it.into(),
|
||||
|
@ -216,6 +224,69 @@ fn resolve_assoc_or_field(
|
|||
resolve_field(db, variant_def, name, ns)
|
||||
}
|
||||
|
||||
fn resolve_assoc_item(
|
||||
db: &dyn HirDatabase,
|
||||
ty: &Type,
|
||||
name: &Name,
|
||||
ns: Option<Namespace>,
|
||||
) -> Option<DocLinkDef> {
|
||||
ty.iterate_assoc_items(db, ty.krate(db), move |assoc_item| {
|
||||
if assoc_item.name(db)? != *name {
|
||||
return None;
|
||||
}
|
||||
as_module_def_if_namespace_matches(assoc_item, ns)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_impl_trait_item(
|
||||
db: &dyn HirDatabase,
|
||||
resolver: Resolver,
|
||||
ty: &Type,
|
||||
name: &Name,
|
||||
ns: Option<Namespace>,
|
||||
) -> Option<DocLinkDef> {
|
||||
let canonical = ty.canonical();
|
||||
let krate = ty.krate(db);
|
||||
let environment = resolver.generic_def().map_or_else(
|
||||
|| crate::TraitEnvironment::empty(krate.id).into(),
|
||||
|d| db.trait_environment(d),
|
||||
);
|
||||
let traits_in_scope = resolver.traits_in_scope(db.upcast());
|
||||
|
||||
let mut result = None;
|
||||
|
||||
// `ty.iterate_path_candidates()` require a scope, which is not available when resolving
|
||||
// attributes here. Use path resolution directly instead.
|
||||
//
|
||||
// FIXME: resolve type aliases (which are not yielded by iterate_path_candidates)
|
||||
method_resolution::iterate_path_candidates(
|
||||
&canonical,
|
||||
db,
|
||||
environment,
|
||||
&traits_in_scope,
|
||||
method_resolution::VisibleFromModule::None,
|
||||
Some(name),
|
||||
&mut |assoc_item_id| {
|
||||
let assoc_item: AssocItem = assoc_item_id.into();
|
||||
|
||||
debug_assert_eq!(assoc_item.name(db).as_ref(), Some(name));
|
||||
|
||||
// If two traits in scope define the same item, Rustdoc links to no specific trait (for
|
||||
// instance, given two methods `a`, Rustdoc simply links to `method.a` with no
|
||||
// disambiguation) so we just pick the first one we find as well.
|
||||
result = as_module_def_if_namespace_matches(assoc_item, ns);
|
||||
|
||||
if result.is_some() {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn resolve_field(
|
||||
db: &dyn HirDatabase,
|
||||
def: VariantDef,
|
||||
|
@ -228,6 +299,19 @@ fn resolve_field(
|
|||
def.fields(db).into_iter().find(|f| f.name(db) == name).map(DocLinkDef::Field)
|
||||
}
|
||||
|
||||
fn as_module_def_if_namespace_matches(
|
||||
assoc_item: AssocItem,
|
||||
ns: Option<Namespace>,
|
||||
) -> Option<DocLinkDef> {
|
||||
let (def, expected_ns) = match assoc_item {
|
||||
AssocItem::Function(it) => (ModuleDef::Function(it), Namespace::Values),
|
||||
AssocItem::Const(it) => (ModuleDef::Const(it), Namespace::Values),
|
||||
AssocItem::TypeAlias(it) => (ModuleDef::TypeAlias(it), Namespace::Types),
|
||||
};
|
||||
|
||||
(ns.unwrap_or(expected_ns) == expected_ns).then(|| DocLinkDef::ModuleDef(def))
|
||||
}
|
||||
|
||||
fn modpath_from_str(db: &dyn HirDatabase, link: &str) -> Option<ModPath> {
|
||||
// FIXME: this is not how we should get a mod path here.
|
||||
let try_get_modpath = |link: &str| {
|
||||
|
|
|
@ -4121,6 +4121,10 @@ impl Type {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn canonical(&self) -> Canonical<Ty> {
|
||||
hir_ty::replace_errors_with_variables(&self.ty)
|
||||
}
|
||||
|
||||
/// Returns types that this type dereferences to (including this type itself). The returned
|
||||
/// iterator won't yield the same type more than once even if the deref chain contains a cycle.
|
||||
pub fn autoderef(&self, db: &dyn HirDatabase) -> impl Iterator<Item = Type> + '_ {
|
||||
|
|
|
@ -462,14 +462,15 @@ mod module {}
|
|||
fn doc_links_inherent_impl_items() {
|
||||
check_doc_links(
|
||||
r#"
|
||||
// /// [`Struct::CONST`]
|
||||
// /// [`Struct::function`]
|
||||
/// FIXME #9694
|
||||
/// [`Struct::CONST`]
|
||||
/// [`Struct::function`]
|
||||
struct Struct$0;
|
||||
|
||||
impl Struct {
|
||||
const CONST: () = ();
|
||||
// ^^^^^ Struct::CONST
|
||||
fn function() {}
|
||||
// ^^^^^^^^ Struct::function
|
||||
}
|
||||
"#,
|
||||
)
|
||||
|
@ -482,12 +483,13 @@ fn doc_links_trait_impl_items() {
|
|||
trait Trait {
|
||||
type Type;
|
||||
const CONST: usize;
|
||||
// ^^^^^ Struct::CONST
|
||||
fn function();
|
||||
// ^^^^^^^^ Struct::function
|
||||
}
|
||||
// /// [`Struct::Type`]
|
||||
// /// [`Struct::CONST`]
|
||||
// /// [`Struct::function`]
|
||||
/// FIXME #9694
|
||||
// FIXME #9694: [`Struct::Type`]
|
||||
/// [`Struct::CONST`]
|
||||
/// [`Struct::function`]
|
||||
struct Struct$0;
|
||||
|
||||
impl Trait for Struct {
|
||||
|
|
Loading…
Reference in a new issue