about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir/src/attrs.rs94
-rw-r--r--crates/hir/src/lib.rs4
-rw-r--r--crates/ide/src/doc_links/tests.rs16
3 files changed, 102 insertions, 12 deletions
diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs
index d60d20f5b7e..bc4d9b9be36 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<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| {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 09b56e13824..fc6c2aeb3be 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<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> + '_ {
diff --git a/crates/ide/src/doc_links/tests.rs b/crates/ide/src/doc_links/tests.rs
index f388aea4c37..e1f5ccc228b 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 {