Skip to content

Commit

Permalink
hir: resolve associated items in docs (excl. type aliases)
Browse files Browse the repository at this point in the history
  • Loading branch information
71 committed Jan 3, 2024
1 parent d987137 commit fe6f931
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 12 deletions.
94 changes: 89 additions & 5 deletions crates/hir/src/attrs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Attributes & documentation for hir types.
use std::ops::ControlFlow;

use base_db::FileId;
use hir_def::{
attr::AttrsWithOwner,
Expand All @@ -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 {
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand All @@ -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| {
Expand Down
4 changes: 4 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> + '_ {
Expand Down
16 changes: 9 additions & 7 deletions crates/ide/src/doc_links/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
"#,
)
Expand All @@ -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 {
Expand Down

0 comments on commit fe6f931

Please sign in to comment.