Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: resolve inherent and implemented associated items in docs #15933

Merged
merged 1 commit into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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