Implement constructor usage search for almost all items

For all struct kinds, unions and enums, as well as for record- and
tuple-variants but not for unit-variants, as these have no trailing
character we can anchor the search to. Functionality wise it is
implemented though.
This commit is contained in:
Lukas Wirth 2021-02-12 21:30:55 +01:00
parent 88253907f4
commit c395dd1032
2 changed files with 197 additions and 114 deletions

View File

@ -1,6 +1,5 @@
use std::collections::HashMap;
use ide_db::{defs::Definition, search::FileReference}; use ide_db::{defs::Definition, search::FileReference};
use rustc_hash::FxHashMap;
use syntax::{ use syntax::{
ast::{self, AstNode, AstToken}, ast::{self, AstNode, AstToken},
TextRange, TextRange,
@ -111,7 +110,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
.collect::<Result<_, _>>() .collect::<Result<_, _>>()
.map(|b| (file_id, b)) .map(|b| (file_id, b))
}) })
.collect::<Result<HashMap<_, Vec<_>>, _>>()?; .collect::<Result<FxHashMap<_, Vec<_>>, _>>()?;
let init_str = initializer_expr.syntax().text().to_string(); let init_str = initializer_expr.syntax().text().to_string();
let init_in_paren = format!("({})", &init_str); let init_in_paren = format!("({})", &init_str);

View File

@ -11,7 +11,7 @@
pub(crate) mod rename; pub(crate) mod rename;
use hir::Semantics; use hir::{PathResolution, Semantics};
use ide_db::{ use ide_db::{
base_db::FileId, base_db::FileId,
defs::{Definition, NameClass, NameRefClass}, defs::{Definition, NameClass, NameRefClass},
@ -22,7 +22,7 @@ use rustc_hash::FxHashMap;
use syntax::{ use syntax::{
algo::find_node_at_offset, algo::find_node_at_offset,
ast::{self, NameOwner}, ast::{self, NameOwner},
AstNode, SyntaxNode, TextRange, TokenAtOffset, T, match_ast, AstNode, SyntaxNode, TextRange, T,
}; };
use crate::{display::TryToNav, FilePosition, NavigationTarget}; use crate::{display::TryToNav, FilePosition, NavigationTarget};
@ -47,29 +47,43 @@ pub(crate) fn find_all_refs(
let _p = profile::span("find_all_refs"); let _p = profile::span("find_all_refs");
let syntax = sema.parse(position.file_id).syntax().clone(); let syntax = sema.parse(position.file_id).syntax().clone();
let (opt_name, ctor_filter): (_, Option<fn(&_) -> bool>) = if let Some(name) = let (def, is_literal_search) =
get_struct_def_name_for_struct_literal_search(&sema, &syntax, position) if let Some(name) = get_name_of_item_declaration(&syntax, position) {
{ (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true)
( } else {
Some(name), (find_def(&sema, &syntax, position)?, false)
Some(|name_ref| is_record_lit_name_ref(name_ref) || is_call_expr_name_ref(name_ref)), };
)
} else if let Some(name) = get_enum_def_name_for_struct_literal_search(&sema, &syntax, position)
{
(Some(name), Some(is_enum_lit_name_ref))
} else {
(sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset), None)
};
let def = find_def(&sema, &syntax, position, opt_name)?;
let mut usages = def.usages(sema).set_scope(search_scope).all(); let mut usages = def.usages(sema).set_scope(search_scope).all();
if let Some(ctor_filter) = ctor_filter { if is_literal_search {
// filter for constructor-literals // filter for constructor-literals
usages.references.values_mut().for_each(|it| { let refs = usages.references.values_mut();
it.retain(|reference| reference.name.as_name_ref().map_or(false, ctor_filter)); match def {
}); Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => {
usages.references.retain(|_, it| !it.is_empty()); refs.for_each(|it| {
it.retain(|reference| {
reference
.name
.as_name_ref()
.map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref))
})
});
usages.references.retain(|_, it| !it.is_empty());
}
Definition::ModuleDef(def @ hir::ModuleDef::Adt(_))
| Definition::ModuleDef(def @ hir::ModuleDef::Variant(_)) => {
refs.for_each(|it| {
it.retain(|reference| {
reference
.name
.as_name_ref()
.map_or(false, |name_ref| is_lit_name_ref(sema, def, name_ref))
})
});
usages.references.retain(|_, it| !it.is_empty());
}
_ => {}
}
} }
let nav = def.try_to_nav(sema.db)?; let nav = def.try_to_nav(sema.db)?;
let decl_range = nav.focus_or_full_range(); let decl_range = nav.focus_or_full_range();
@ -89,9 +103,9 @@ fn find_def(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
syntax: &SyntaxNode, syntax: &SyntaxNode,
position: FilePosition, position: FilePosition,
opt_name: Option<ast::Name>,
) -> Option<Definition> { ) -> Option<Definition> {
if let Some(name) = opt_name { if let Some(name) = sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset)
{
let class = NameClass::classify(sema, &name)?; let class = NameClass::classify(sema, &name)?;
Some(class.referenced_or_defined(sema.db)) Some(class.referenced_or_defined(sema.db))
} else if let Some(lifetime) = } else if let Some(lifetime) =
@ -134,95 +148,96 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio
None None
} }
fn get_struct_def_name_for_struct_literal_search( fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
sema: &Semantics<RootDatabase>, let token = syntax.token_at_offset(position.offset).right_biased()?;
syntax: &SyntaxNode, let kind = token.kind();
position: FilePosition, if kind == T![;] {
) -> Option<ast::Name> { ast::Struct::cast(token.parent())
if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { .filter(|struct_| struct_.field_list().is_none())
if right.kind() != T!['{'] && right.kind() != T!['('] { .and_then(|struct_| struct_.name())
return None; } else if kind == T!['{'] {
} match_ast! {
if let Some(name) = match (token.parent()) {
sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) ast::RecordFieldList(rfl) => match_ast! {
{ match (rfl.syntax().parent()?) {
return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); ast::Variant(it) => it.name(),
} ast::Struct(it) => it.name(),
if sema ast::Union(it) => it.name(),
.find_node_at_offset_with_descend::<ast::GenericParamList>( _ => None,
&syntax, }
left.text_range().start(), },
) ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(),
.is_some() _ => None,
{
return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
}
}
None
}
fn get_enum_def_name_for_struct_literal_search(
sema: &Semantics<RootDatabase>,
syntax: &SyntaxNode,
position: FilePosition,
) -> Option<ast::Name> {
if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
if right.kind() != T!['{'] && right.kind() != T!['('] {
return None;
}
if let Some(name) =
sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start())
{
return name.syntax().ancestors().find_map(ast::Enum::cast).and_then(|l| l.name());
}
if sema
.find_node_at_offset_with_descend::<ast::GenericParamList>(
&syntax,
left.text_range().start(),
)
.is_some()
{
return left.ancestors().find_map(ast::Enum::cast).and_then(|l| l.name());
}
}
None
}
fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool {
name_ref
.syntax()
.ancestors()
.find_map(ast::CallExpr::cast)
.and_then(|c| match c.expr()? {
ast::Expr::PathExpr(p) => {
Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref))
} }
_ => None, }
}) } else if kind == T!['('] {
.unwrap_or(false) let tfl = ast::TupleFieldList::cast(token.parent())?;
match_ast! {
match (tfl.syntax().parent()?) {
ast::Variant(it) => it.name(),
ast::Struct(it) => it.name(),
_ => None,
}
}
} else {
None
}
} }
fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { fn is_enum_lit_name_ref(
name_ref sema: &Semantics<RootDatabase>,
.syntax() enum_: hir::Enum,
.ancestors() name_ref: &ast::NameRef,
.find_map(ast::RecordExpr::cast) ) -> bool {
.and_then(|l| l.path()) for ancestor in name_ref.syntax().ancestors() {
.and_then(|p| p.segment()) match_ast! {
.map(|p| p.name_ref().as_ref() == Some(name_ref)) match ancestor {
.unwrap_or(false) ast::PathExpr(path_expr) => {
return matches!(
path_expr.path().and_then(|p| sema.resolve_path(&p)),
Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
if variant.parent_enum(sema.db) == enum_
)
},
ast::RecordExpr(record_expr) => {
return matches!(
record_expr.path().and_then(|p| sema.resolve_path(&p)),
Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
if variant.parent_enum(sema.db) == enum_
)
},
_ => (),
}
}
}
false
} }
fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool { fn is_lit_name_ref(
name_ref sema: &Semantics<RootDatabase>,
.syntax() def: hir::ModuleDef,
.ancestors() name_ref: &ast::NameRef,
.find_map(ast::PathExpr::cast) ) -> bool {
.and_then(|p| p.path()) for ancestor in name_ref.syntax().ancestors() {
.and_then(|p| p.qualifier()) match_ast! {
.and_then(|p| p.segment()) match ancestor {
.map(|p| p.name_ref().as_ref() == Some(name_ref)) ast::PathExpr(path_expr) => {
.unwrap_or(false) return matches!(
path_expr.path().and_then(|p| sema.resolve_path(&p)),
Some(PathResolution::Def(def2)) if def == def2
)
},
ast::RecordExpr(record_expr) => {
return matches!(
record_expr.path().and_then(|p| sema.resolve_path(&p)),
Some(PathResolution::Def(def2)) if def == def2
)
},
_ => (),
}
}
}
false
} }
#[cfg(test)] #[cfg(test)]
@ -312,23 +327,92 @@ fn main() {
); );
} }
#[test]
fn test_struct_literal_for_union() {
check(
r#"
union Foo $0{
x: u32
}
fn main() {
let f: Foo;
f = Foo { x: 1 };
}
"#,
expect![[r#"
Foo Union FileId(0) 0..24 6..9
FileId(0) 62..65
"#]],
);
}
#[test] #[test]
fn test_enum_after_space() { fn test_enum_after_space() {
check( check(
r#" r#"
enum Foo $0{ enum Foo $0{
A, A,
B, B(),
C{},
} }
fn main() { fn main() {
let f: Foo; let f: Foo;
f = Foo::A; f = Foo::A;
f = Foo::B();
f = Foo::C{};
} }
"#, "#,
expect![[r#" expect![[r#"
Foo Enum FileId(0) 0..26 5..8 Foo Enum FileId(0) 0..37 5..8
FileId(0) 63..66 FileId(0) 74..77
FileId(0) 90..93
FileId(0) 108..111
"#]],
);
}
#[test]
fn test_variant_record_after_space() {
check(
r#"
enum Foo {
A $0{ n: i32 },
B,
}
fn main() {
let f: Foo;
f = Foo::B;
f = Foo::A { n: 92 };
}
"#,
expect![[r#"
A Variant FileId(0) 15..27 15..16
FileId(0) 95..96
"#]],
);
}
#[test]
fn test_variant_tuple_before_paren() {
check(
r#"
enum Foo {
A$0(i32),
B,
}
fn main() {
let f: Foo;
f = Foo::B;
f = Foo::A(92);
}
"#,
expect![[r#"
A Variant FileId(0) 15..21 15..16
FileId(0) 89..90
"#]], "#]],
); );
} }