mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 00:03:43 +00:00
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:
parent
88253907f4
commit
c395dd1032
@ -1,6 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ide_db::{defs::Definition, search::FileReference};
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, AstToken},
|
||||
TextRange,
|
||||
@ -111,7 +110,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
|
||||
.collect::<Result<_, _>>()
|
||||
.map(|b| (file_id, b))
|
||||
})
|
||||
.collect::<Result<HashMap<_, Vec<_>>, _>>()?;
|
||||
.collect::<Result<FxHashMap<_, Vec<_>>, _>>()?;
|
||||
|
||||
let init_str = initializer_expr.syntax().text().to_string();
|
||||
let init_in_paren = format!("({})", &init_str);
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
pub(crate) mod rename;
|
||||
|
||||
use hir::Semantics;
|
||||
use hir::{PathResolution, Semantics};
|
||||
use ide_db::{
|
||||
base_db::FileId,
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
@ -22,7 +22,7 @@ use rustc_hash::FxHashMap;
|
||||
use syntax::{
|
||||
algo::find_node_at_offset,
|
||||
ast::{self, NameOwner},
|
||||
AstNode, SyntaxNode, TextRange, TokenAtOffset, T,
|
||||
match_ast, AstNode, SyntaxNode, TextRange, T,
|
||||
};
|
||||
|
||||
use crate::{display::TryToNav, FilePosition, NavigationTarget};
|
||||
@ -47,29 +47,43 @@ pub(crate) fn find_all_refs(
|
||||
let _p = profile::span("find_all_refs");
|
||||
let syntax = sema.parse(position.file_id).syntax().clone();
|
||||
|
||||
let (opt_name, ctor_filter): (_, Option<fn(&_) -> bool>) = if let Some(name) =
|
||||
get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
|
||||
{
|
||||
(
|
||||
Some(name),
|
||||
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 (def, is_literal_search) =
|
||||
if let Some(name) = get_name_of_item_declaration(&syntax, position) {
|
||||
(NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true)
|
||||
} else {
|
||||
(find_def(&sema, &syntax, position)?, false)
|
||||
};
|
||||
|
||||
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
|
||||
usages.references.values_mut().for_each(|it| {
|
||||
it.retain(|reference| reference.name.as_name_ref().map_or(false, ctor_filter));
|
||||
});
|
||||
usages.references.retain(|_, it| !it.is_empty());
|
||||
let refs = usages.references.values_mut();
|
||||
match def {
|
||||
Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => {
|
||||
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 decl_range = nav.focus_or_full_range();
|
||||
@ -89,9 +103,9 @@ fn find_def(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
syntax: &SyntaxNode,
|
||||
position: FilePosition,
|
||||
opt_name: Option<ast::Name>,
|
||||
) -> 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)?;
|
||||
Some(class.referenced_or_defined(sema.db))
|
||||
} else if let Some(lifetime) =
|
||||
@ -134,95 +148,96 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio
|
||||
None
|
||||
}
|
||||
|
||||
fn get_struct_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::Struct::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::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))
|
||||
fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
|
||||
let token = syntax.token_at_offset(position.offset).right_biased()?;
|
||||
let kind = token.kind();
|
||||
if kind == T![;] {
|
||||
ast::Struct::cast(token.parent())
|
||||
.filter(|struct_| struct_.field_list().is_none())
|
||||
.and_then(|struct_| struct_.name())
|
||||
} else if kind == T!['{'] {
|
||||
match_ast! {
|
||||
match (token.parent()) {
|
||||
ast::RecordFieldList(rfl) => match_ast! {
|
||||
match (rfl.syntax().parent()?) {
|
||||
ast::Variant(it) => it.name(),
|
||||
ast::Struct(it) => it.name(),
|
||||
ast::Union(it) => it.name(),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(),
|
||||
_ => None,
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
} else if kind == T!['('] {
|
||||
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 {
|
||||
name_ref
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::RecordExpr::cast)
|
||||
.and_then(|l| l.path())
|
||||
.and_then(|p| p.segment())
|
||||
.map(|p| p.name_ref().as_ref() == Some(name_ref))
|
||||
.unwrap_or(false)
|
||||
fn is_enum_lit_name_ref(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
enum_: hir::Enum,
|
||||
name_ref: &ast::NameRef,
|
||||
) -> bool {
|
||||
for ancestor in name_ref.syntax().ancestors() {
|
||||
match_ast! {
|
||||
match ancestor {
|
||||
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 {
|
||||
name_ref
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::PathExpr::cast)
|
||||
.and_then(|p| p.path())
|
||||
.and_then(|p| p.qualifier())
|
||||
.and_then(|p| p.segment())
|
||||
.map(|p| p.name_ref().as_ref() == Some(name_ref))
|
||||
.unwrap_or(false)
|
||||
fn is_lit_name_ref(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
def: hir::ModuleDef,
|
||||
name_ref: &ast::NameRef,
|
||||
) -> bool {
|
||||
for ancestor in name_ref.syntax().ancestors() {
|
||||
match_ast! {
|
||||
match ancestor {
|
||||
ast::PathExpr(path_expr) => {
|
||||
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)]
|
||||
@ -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]
|
||||
fn test_enum_after_space() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo $0{
|
||||
A,
|
||||
B,
|
||||
B(),
|
||||
C{},
|
||||
}
|
||||
fn main() {
|
||||
let f: Foo;
|
||||
f = Foo::A;
|
||||
f = Foo::B();
|
||||
f = Foo::C{};
|
||||
}
|
||||
"#,
|
||||
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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user