From fe6f931ac24f564341584c9dda9ef8d60c05c933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Geis?= Date: Sun, 19 Nov 2023 17:23:08 +0900 Subject: [PATCH] hir: resolve associated items in docs (excl. type aliases) --- crates/hir/src/attrs.rs | 94 +++++++++++++++++++++++++++++-- crates/hir/src/lib.rs | 4 ++ crates/ide/src/doc_links/tests.rs | 16 +++--- 3 files changed, 102 insertions(+), 12 deletions(-) diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index d60d20f5b7ee..bc4d9b9be367 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -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, +) -> Option { + 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, +) -> Option { + 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, +) -> Option { + 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 { // FIXME: this is not how we should get a mod path here. let try_get_modpath = |link: &str| { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 09b56e138241..fc6c2aeb3bea 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -4121,6 +4121,10 @@ impl Type { } } + pub(crate) fn canonical(&self) -> Canonical { + 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 + '_ { diff --git a/crates/ide/src/doc_links/tests.rs b/crates/ide/src/doc_links/tests.rs index f388aea4c379..e1f5ccc228b7 100644 --- a/crates/ide/src/doc_links/tests.rs +++ b/crates/ide/src/doc_links/tests.rs @@ -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 {