Auto merge of #12825 - Veykril:trait-assoc-search, r=Veykril

fix: Fix search for associated trait items being inconsistent
This commit is contained in:
bors 2022-07-20 12:00:14 +00:00
commit 0ded8e734f
4 changed files with 125 additions and 43 deletions

View file

@ -7,16 +7,14 @@
use std::{convert::TryInto, mem, sync::Arc}; use std::{convert::TryInto, mem, sync::Arc};
use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
use hir::{ use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility};
AsAssocItem, DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility,
};
use once_cell::unsync::Lazy; use once_cell::unsync::Lazy;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
use crate::{ use crate::{
defs::{Definition, NameClass, NameRefClass}, defs::{Definition, NameClass, NameRefClass},
traits::convert_to_def_in_trait, traits::{as_trait_assoc_def, convert_to_def_in_trait},
RootDatabase, RootDatabase,
}; };
@ -314,6 +312,7 @@ impl Definition {
_ => None, _ => None,
}, },
def: self, def: self,
trait_assoc_def: as_trait_assoc_def(sema.db, self),
sema, sema,
scope: None, scope: None,
include_self_kw_refs: None, include_self_kw_refs: None,
@ -325,6 +324,8 @@ impl Definition {
#[derive(Clone)] #[derive(Clone)]
pub struct FindUsages<'a> { pub struct FindUsages<'a> {
def: Definition, def: Definition,
/// If def is an assoc item from a trait or trait impl, this is the corresponding item of the trait definition
trait_assoc_def: Option<Definition>,
sema: &'a Semantics<'a, RootDatabase>, sema: &'a Semantics<'a, RootDatabase>,
scope: Option<SearchScope>, scope: Option<SearchScope>,
include_self_kw_refs: Option<hir::Type>, include_self_kw_refs: Option<hir::Type>,
@ -375,7 +376,7 @@ impl<'a> FindUsages<'a> {
let sema = self.sema; let sema = self.sema;
let search_scope = { let search_scope = {
let base = self.def.search_scope(sema.db); let base = self.trait_assoc_def.unwrap_or(self.def).search_scope(sema.db);
match &self.scope { match &self.scope {
None => base, None => base,
Some(scope) => base.intersection(scope), Some(scope) => base.intersection(scope),
@ -621,7 +622,13 @@ impl<'a> FindUsages<'a> {
sink(file_id, reference) sink(file_id, reference)
} }
Some(NameRefClass::Definition(def)) Some(NameRefClass::Definition(def))
if convert_to_def_in_trait(self.sema.db, def) == self.def => if match self.trait_assoc_def {
Some(trait_assoc_def) => {
// we have a trait assoc item, so force resolve all assoc items to their trait version
convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def
}
None => self.def == def,
} =>
{ {
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let reference = FileReference { let reference = FileReference {
@ -711,30 +718,22 @@ impl<'a> FindUsages<'a> {
} }
false false
} }
// Resolve trait impl function definitions to the trait definition's version if self.def is the trait definition's
Some(NameClass::Definition(def)) if def != self.def => { Some(NameClass::Definition(def)) if def != self.def => {
/* poor man's try block */ // if the def we are looking for is a trait (impl) assoc item, we'll have to resolve the items to trait definition assoc item
(|| { if !matches!(
let this_trait = self self.trait_assoc_def,
.def Some(trait_assoc_def)
.as_assoc_item(self.sema.db)? if convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def
.containing_trait_or_trait_impl(self.sema.db)?; ) {
let trait_ = def return false;
.as_assoc_item(self.sema.db)? }
.containing_trait_or_trait_impl(self.sema.db)?; let FileRange { file_id, range } = self.sema.original_range(name.syntax());
(trait_ == this_trait && self.def.name(self.sema.db) == def.name(self.sema.db)) let reference = FileReference {
.then(|| { range,
let FileRange { file_id, range } = name: ast::NameLike::Name(name.clone()),
self.sema.original_range(name.syntax()); category: None,
let reference = FileReference { };
range, sink(file_id, reference)
name: ast::NameLike::Name(name.clone()),
category: None,
};
sink(file_id, reference)
})
})()
.unwrap_or(false)
} }
_ => false, _ => false,
} }

View file

@ -71,26 +71,44 @@ pub fn get_missing_assoc_items(
/// Converts associated trait impl items to their trait definition counterpart /// Converts associated trait impl items to their trait definition counterpart
pub(crate) fn convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition { pub(crate) fn convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition {
use hir::AssocItem::*;
(|| { (|| {
let assoc = def.as_assoc_item(db)?; let assoc = def.as_assoc_item(db)?;
let trait_ = assoc.containing_trait_impl(db)?; let trait_ = assoc.containing_trait_impl(db)?;
let name = match assoc { assoc_item_of_trait(db, assoc, trait_)
Function(it) => it.name(db),
Const(it) => it.name(db)?,
TypeAlias(it) => it.name(db),
};
let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
(Function(trait_func), Function(_)) => trait_func.name(db) == name,
(Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
(TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
_ => false,
})?;
Some(Definition::from(item))
})() })()
.unwrap_or(def) .unwrap_or(def)
} }
/// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
pub(crate) fn as_trait_assoc_def(db: &dyn HirDatabase, def: Definition) -> Option<Definition> {
let assoc = def.as_assoc_item(db)?;
let trait_ = match assoc.container(db) {
hir::AssocItemContainer::Trait(_) => return Some(def),
hir::AssocItemContainer::Impl(i) => i.trait_(db),
}?;
assoc_item_of_trait(db, assoc, trait_)
}
fn assoc_item_of_trait(
db: &dyn HirDatabase,
assoc: hir::AssocItem,
trait_: hir::Trait,
) -> Option<Definition> {
use hir::AssocItem::*;
let name = match assoc {
Function(it) => it.name(db),
Const(it) => it.name(db)?,
TypeAlias(it) => it.name(db),
};
let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
(Function(trait_func), Function(_)) => trait_func.name(db) == name,
(Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
(TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
_ => false,
})?;
Some(Definition::from(item))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use base_db::{fixture::ChangeFixture, FilePosition}; use base_db::{fixture::ChangeFixture, FilePosition};

View file

@ -1307,6 +1307,70 @@ fn foo((
//^^^read //^^^read
let foo; let foo;
} }
"#,
);
}
#[test]
fn test_hl_trait_impl_methods() {
check(
r#"
trait Trait {
fn func$0(self) {}
//^^^^
}
impl Trait for () {
fn func(self) {}
//^^^^
}
fn main() {
<()>::func(());
//^^^^
().func();
//^^^^
}
"#,
);
check(
r#"
trait Trait {
fn func(self) {}
//^^^^
}
impl Trait for () {
fn func$0(self) {}
//^^^^
}
fn main() {
<()>::func(());
//^^^^
().func();
//^^^^
}
"#,
);
check(
r#"
trait Trait {
fn func(self) {}
//^^^^
}
impl Trait for () {
fn func(self) {}
//^^^^
}
fn main() {
<()>::func(());
//^^^^
().func$0();
//^^^^
}
"#, "#,
); );
} }

View file

@ -73,6 +73,7 @@ pub(crate) fn find_all_refs(
}); });
let mut usages = let mut usages =
def.usages(sema).set_scope(search_scope.clone()).include_self_refs().all(); def.usages(sema).set_scope(search_scope.clone()).include_self_refs().all();
if literal_search { if literal_search {
retain_adt_literal_usages(&mut usages, def, sema); retain_adt_literal_usages(&mut usages, def, sema);
} }
@ -105,7 +106,7 @@ pub(crate) fn find_all_refs(
} }
None => { None => {
let search = make_searcher(false); let search = make_searcher(false);
Some(find_defs(sema, &syntax, position.offset)?.into_iter().map(search).collect()) Some(find_defs(sema, &syntax, position.offset)?.map(search).collect())
} }
} }
} }