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 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);
|
||||||
|
@ -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
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user