mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 04:23:25 +00:00
Merge #8317
8317: Convert tuple struct to named struct assist r=Veykril a=unexge Closes https://github.com/rust-analyzer/rust-analyzer/issues/8192 Co-authored-by: unexge <unexge@gmail.com>
This commit is contained in:
commit
85bab7539a
5 changed files with 636 additions and 7 deletions
|
@ -0,0 +1,516 @@
|
||||||
|
use ide_db::defs::{Definition, NameRefClass};
|
||||||
|
use syntax::{
|
||||||
|
ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
|
||||||
|
match_ast, SyntaxNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
|
||||||
|
|
||||||
|
// Assist: convert_tuple_struct_to_named_struct
|
||||||
|
//
|
||||||
|
// Converts tuple struct to struct with named fields.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// struct Point$0(f32, f32);
|
||||||
|
//
|
||||||
|
// impl Point {
|
||||||
|
// pub fn new(x: f32, y: f32) -> Self {
|
||||||
|
// Point(x, y)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn x(&self) -> f32 {
|
||||||
|
// self.0
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn y(&self) -> f32 {
|
||||||
|
// self.1
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// ->
|
||||||
|
// ```
|
||||||
|
// struct Point { field1: f32, field2: f32 }
|
||||||
|
//
|
||||||
|
// impl Point {
|
||||||
|
// pub fn new(x: f32, y: f32) -> Self {
|
||||||
|
// Point { field1: x, field2: y }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn x(&self) -> f32 {
|
||||||
|
// self.field1
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn y(&self) -> f32 {
|
||||||
|
// self.field2
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
pub(crate) fn convert_tuple_struct_to_named_struct(
|
||||||
|
acc: &mut Assists,
|
||||||
|
ctx: &AssistContext,
|
||||||
|
) -> Option<()> {
|
||||||
|
let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
|
||||||
|
let tuple_fields = match strukt.field_list()? {
|
||||||
|
ast::FieldList::TupleFieldList(it) => it,
|
||||||
|
ast::FieldList::RecordFieldList(_) => return None,
|
||||||
|
};
|
||||||
|
let strukt_def = ctx.sema.to_def(&strukt)?;
|
||||||
|
|
||||||
|
let target = strukt.syntax().text_range();
|
||||||
|
acc.add(
|
||||||
|
AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
|
||||||
|
"Convert to named struct",
|
||||||
|
target,
|
||||||
|
|edit| {
|
||||||
|
let names = generate_names(tuple_fields.fields());
|
||||||
|
edit_field_references(ctx, edit, tuple_fields.fields(), &names);
|
||||||
|
edit_struct_references(ctx, edit, strukt_def, &names);
|
||||||
|
edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_struct_def(
|
||||||
|
ctx: &AssistContext,
|
||||||
|
edit: &mut AssistBuilder,
|
||||||
|
strukt: &ast::Struct,
|
||||||
|
tuple_fields: ast::TupleFieldList,
|
||||||
|
names: Vec<ast::Name>,
|
||||||
|
) {
|
||||||
|
let record_fields = tuple_fields
|
||||||
|
.fields()
|
||||||
|
.zip(names)
|
||||||
|
.filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
|
||||||
|
let record_fields = ast::make::record_field_list(record_fields);
|
||||||
|
let tuple_fields_text_range = tuple_fields.syntax().text_range();
|
||||||
|
|
||||||
|
edit.edit_file(ctx.frange.file_id);
|
||||||
|
|
||||||
|
if let Some(w) = strukt.where_clause() {
|
||||||
|
edit.delete(w.syntax().text_range());
|
||||||
|
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
|
||||||
|
edit.insert(tuple_fields_text_range.start(), w.syntax().text());
|
||||||
|
edit.insert(tuple_fields_text_range.start(), ",");
|
||||||
|
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
|
||||||
|
} else {
|
||||||
|
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
|
||||||
|
}
|
||||||
|
|
||||||
|
edit.replace(tuple_fields_text_range, record_fields.to_string());
|
||||||
|
strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_struct_references(
|
||||||
|
ctx: &AssistContext,
|
||||||
|
edit: &mut AssistBuilder,
|
||||||
|
strukt: hir::Struct,
|
||||||
|
names: &[ast::Name],
|
||||||
|
) {
|
||||||
|
let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
|
||||||
|
let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all();
|
||||||
|
|
||||||
|
let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
|
||||||
|
match_ast! {
|
||||||
|
match node {
|
||||||
|
ast::TupleStructPat(tuple_struct_pat) => {
|
||||||
|
edit.replace(
|
||||||
|
tuple_struct_pat.syntax().text_range(),
|
||||||
|
ast::make::record_pat_with_fields(
|
||||||
|
tuple_struct_pat.path()?,
|
||||||
|
ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
|
||||||
|
|(pat, name)| {
|
||||||
|
ast::make::record_pat_field(
|
||||||
|
ast::make::name_ref(&name.to_string()),
|
||||||
|
pat,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// for tuple struct creations like Foo(42)
|
||||||
|
ast::CallExpr(call_expr) => {
|
||||||
|
let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
|
||||||
|
|
||||||
|
// this also includes method calls like Foo::new(42), we should skip them
|
||||||
|
if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
|
||||||
|
match NameRefClass::classify(&ctx.sema, &name_ref) {
|
||||||
|
Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
|
||||||
|
Some(NameRefClass::Definition(def)) if def == strukt_def => {},
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
|
||||||
|
|
||||||
|
edit.replace(
|
||||||
|
call_expr.syntax().text_range(),
|
||||||
|
ast::make::record_expr(
|
||||||
|
path,
|
||||||
|
ast::make::record_expr_field_list(arg_list.args().zip(names).map(
|
||||||
|
|(expr, name)| {
|
||||||
|
ast::make::record_expr_field(
|
||||||
|
ast::make::name_ref(&name.to_string()),
|
||||||
|
Some(expr),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
};
|
||||||
|
|
||||||
|
for (file_id, refs) in usages {
|
||||||
|
edit.edit_file(file_id);
|
||||||
|
for r in refs {
|
||||||
|
for node in r.name.syntax().ancestors() {
|
||||||
|
if edit_node(edit, node).is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_field_references(
|
||||||
|
ctx: &AssistContext,
|
||||||
|
edit: &mut AssistBuilder,
|
||||||
|
fields: impl Iterator<Item = ast::TupleField>,
|
||||||
|
names: &[ast::Name],
|
||||||
|
) {
|
||||||
|
for (field, name) in fields.zip(names) {
|
||||||
|
let field = match ctx.sema.to_def(&field) {
|
||||||
|
Some(it) => it,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let def = Definition::Field(field);
|
||||||
|
let usages = def.usages(&ctx.sema).all();
|
||||||
|
for (file_id, refs) in usages {
|
||||||
|
edit.edit_file(file_id);
|
||||||
|
for r in refs {
|
||||||
|
if let Some(name_ref) = r.name.as_name_ref() {
|
||||||
|
edit.replace(name_ref.syntax().text_range(), name.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
|
||||||
|
fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_applicable_other_than_tuple_struct() {
|
||||||
|
check_assist_not_applicable(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"struct Foo$0 { bar: u32 };"#,
|
||||||
|
);
|
||||||
|
check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_simple_struct() {
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
struct Inner;
|
||||||
|
struct A$0(Inner);
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn new(inner: Inner) -> A {
|
||||||
|
A(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_default() -> A {
|
||||||
|
A::new(Inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> Inner {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
r#"
|
||||||
|
struct Inner;
|
||||||
|
struct A { field1: Inner }
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn new(inner: Inner) -> A {
|
||||||
|
A { field1: inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_default() -> A {
|
||||||
|
A::new(Inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> Inner {
|
||||||
|
self.field1
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_struct_referenced_via_self_kw() {
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
struct Inner;
|
||||||
|
struct A$0(Inner);
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn new(inner: Inner) -> Self {
|
||||||
|
Self(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_default() -> Self {
|
||||||
|
Self::new(Inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> Inner {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
r#"
|
||||||
|
struct Inner;
|
||||||
|
struct A { field1: Inner }
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn new(inner: Inner) -> Self {
|
||||||
|
Self { field1: inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_default() -> Self {
|
||||||
|
Self::new(Inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> Inner {
|
||||||
|
self.field1
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_destructured_struct() {
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
struct Inner;
|
||||||
|
struct A$0(Inner);
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn into_inner(self) -> Inner {
|
||||||
|
let A(first) = self;
|
||||||
|
first
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner_via_self(self) -> Inner {
|
||||||
|
let Self(first) = self;
|
||||||
|
first
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
r#"
|
||||||
|
struct Inner;
|
||||||
|
struct A { field1: Inner }
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn into_inner(self) -> Inner {
|
||||||
|
let A { field1: first } = self;
|
||||||
|
first
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner_via_self(self) -> Inner {
|
||||||
|
let Self { field1: first } = self;
|
||||||
|
first
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_struct_with_visibility() {
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
struct A$0(pub u32, pub(crate) u64);
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn new() -> A {
|
||||||
|
A(42, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_first(self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_second(self) -> u64 {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
r#"
|
||||||
|
struct A { pub field1: u32, pub(crate) field2: u64 }
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
fn new() -> A {
|
||||||
|
A { field1: 42, field2: 42 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_first(self) -> u32 {
|
||||||
|
self.field1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_second(self) -> u64 {
|
||||||
|
self.field2
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_struct_with_wrapped_references() {
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
struct Inner$0(u32);
|
||||||
|
struct Outer(Inner);
|
||||||
|
|
||||||
|
impl Outer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(Inner(42))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> u32 {
|
||||||
|
(self.0).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner_destructed(self) -> u32 {
|
||||||
|
let Outer(Inner(x)) = self;
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
r#"
|
||||||
|
struct Inner { field1: u32 }
|
||||||
|
struct Outer(Inner);
|
||||||
|
|
||||||
|
impl Outer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(Inner { field1: 42 })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> u32 {
|
||||||
|
(self.0).field1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner_destructed(self) -> u32 {
|
||||||
|
let Outer(Inner { field1: x }) = self;
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
struct Inner(u32);
|
||||||
|
struct Outer$0(Inner);
|
||||||
|
|
||||||
|
impl Outer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(Inner(42))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> u32 {
|
||||||
|
(self.0).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner_destructed(self) -> u32 {
|
||||||
|
let Outer(Inner(x)) = self;
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
r#"
|
||||||
|
struct Inner(u32);
|
||||||
|
struct Outer { field1: Inner }
|
||||||
|
|
||||||
|
impl Outer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { field1: Inner(42) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> u32 {
|
||||||
|
(self.field1).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner_destructed(self) -> u32 {
|
||||||
|
let Outer { field1: Inner(x) } = self;
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_struct_with_multi_file_references() {
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
//- /main.rs
|
||||||
|
struct Inner;
|
||||||
|
struct A$0(Inner);
|
||||||
|
|
||||||
|
mod foo;
|
||||||
|
|
||||||
|
//- /foo.rs
|
||||||
|
use crate::{A, Inner};
|
||||||
|
fn f() {
|
||||||
|
let a = A(Inner);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
//- /main.rs
|
||||||
|
struct Inner;
|
||||||
|
struct A { field1: Inner }
|
||||||
|
|
||||||
|
mod foo;
|
||||||
|
|
||||||
|
//- /foo.rs
|
||||||
|
use crate::{A, Inner};
|
||||||
|
fn f() {
|
||||||
|
let a = A { field1: Inner };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_struct_with_where_clause() {
|
||||||
|
check_assist(
|
||||||
|
convert_tuple_struct_to_named_struct,
|
||||||
|
r#"
|
||||||
|
struct Wrap$0<T>(T)
|
||||||
|
where
|
||||||
|
T: Display;
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Wrap<T>
|
||||||
|
where
|
||||||
|
T: Display,
|
||||||
|
{ field1: T }
|
||||||
|
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -120,6 +120,7 @@ mod handlers {
|
||||||
mod convert_comment_block;
|
mod convert_comment_block;
|
||||||
mod convert_iter_for_each_to_for;
|
mod convert_iter_for_each_to_for;
|
||||||
mod convert_into_to_from;
|
mod convert_into_to_from;
|
||||||
|
mod convert_tuple_struct_to_named_struct;
|
||||||
mod early_return;
|
mod early_return;
|
||||||
mod expand_glob_import;
|
mod expand_glob_import;
|
||||||
mod extract_function;
|
mod extract_function;
|
||||||
|
@ -190,6 +191,7 @@ mod handlers {
|
||||||
convert_comment_block::convert_comment_block,
|
convert_comment_block::convert_comment_block,
|
||||||
convert_iter_for_each_to_for::convert_iter_for_each_to_for,
|
convert_iter_for_each_to_for::convert_iter_for_each_to_for,
|
||||||
convert_into_to_from::convert_into_to_from,
|
convert_into_to_from::convert_into_to_from,
|
||||||
|
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
|
||||||
early_return::convert_to_guarded_return,
|
early_return::convert_to_guarded_return,
|
||||||
expand_glob_import::expand_glob_import,
|
expand_glob_import::expand_glob_import,
|
||||||
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
|
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
|
||||||
|
|
|
@ -291,6 +291,47 @@ fn main() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn doctest_convert_tuple_struct_to_named_struct() {
|
||||||
|
check_doc_test(
|
||||||
|
"convert_tuple_struct_to_named_struct",
|
||||||
|
r#####"
|
||||||
|
struct Point$0(f32, f32);
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
pub fn new(x: f32, y: f32) -> Self {
|
||||||
|
Point(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x(&self) -> f32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn y(&self) -> f32 {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#####,
|
||||||
|
r#####"
|
||||||
|
struct Point { field1: f32, field2: f32 }
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
pub fn new(x: f32, y: f32) -> Self {
|
||||||
|
Point { field1: x, field2: y }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x(&self) -> f32 {
|
||||||
|
self.field1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn y(&self) -> f32 {
|
||||||
|
self.field2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#####,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn doctest_expand_glob_import() {
|
fn doctest_expand_glob_import() {
|
||||||
check_doc_test(
|
check_doc_test(
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
use std::{convert::TryInto, mem};
|
use std::{convert::TryInto, mem};
|
||||||
|
|
||||||
use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
|
use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
|
||||||
use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility};
|
use hir::{
|
||||||
|
DefWithBody, HasAttrs, HasSource, InFile, ModuleDef, 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};
|
||||||
|
@ -295,7 +297,7 @@ impl Definition {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> {
|
pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> {
|
||||||
FindUsages { def: self, sema, scope: None }
|
FindUsages { def: self, sema, scope: None, include_self_kw_refs: false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,9 +305,15 @@ pub struct FindUsages<'a> {
|
||||||
def: &'a Definition,
|
def: &'a Definition,
|
||||||
sema: &'a Semantics<'a, RootDatabase>,
|
sema: &'a Semantics<'a, RootDatabase>,
|
||||||
scope: Option<SearchScope>,
|
scope: Option<SearchScope>,
|
||||||
|
include_self_kw_refs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FindUsages<'a> {
|
impl<'a> FindUsages<'a> {
|
||||||
|
pub fn include_self_kw_refs(mut self, include: bool) -> FindUsages<'a> {
|
||||||
|
self.include_self_kw_refs = include;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> {
|
pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> {
|
||||||
self.set_scope(Some(scope))
|
self.set_scope(Some(scope))
|
||||||
}
|
}
|
||||||
|
@ -352,6 +360,8 @@ impl<'a> FindUsages<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let pat = name.as_str();
|
let pat = name.as_str();
|
||||||
|
let search_for_self = self.include_self_kw_refs;
|
||||||
|
|
||||||
for (file_id, search_range) in search_scope {
|
for (file_id, search_range) in search_scope {
|
||||||
let text = sema.db.file_text(file_id);
|
let text = sema.db.file_text(file_id);
|
||||||
let search_range =
|
let search_range =
|
||||||
|
@ -359,31 +369,47 @@ impl<'a> FindUsages<'a> {
|
||||||
|
|
||||||
let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
|
let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
|
||||||
|
|
||||||
for (idx, _) in text.match_indices(pat) {
|
let mut handle_match = |idx: usize| -> bool {
|
||||||
let offset: TextSize = idx.try_into().unwrap();
|
let offset: TextSize = idx.try_into().unwrap();
|
||||||
if !search_range.contains_inclusive(offset) {
|
if !search_range.contains_inclusive(offset) {
|
||||||
continue;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) {
|
if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) {
|
||||||
match name {
|
match name {
|
||||||
ast::NameLike::NameRef(name_ref) => {
|
ast::NameLike::NameRef(name_ref) => {
|
||||||
if self.found_name_ref(&name_ref, sink) {
|
if self.found_name_ref(&name_ref, sink) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::NameLike::Name(name) => {
|
ast::NameLike::Name(name) => {
|
||||||
if self.found_name(&name, sink) {
|
if self.found_name(&name, sink) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::NameLike::Lifetime(lifetime) => {
|
ast::NameLike::Lifetime(lifetime) => {
|
||||||
if self.found_lifetime(&lifetime, sink) {
|
if self.found_lifetime(&lifetime, sink) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (idx, _) in text.match_indices(pat) {
|
||||||
|
if handle_match(idx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if search_for_self {
|
||||||
|
for (idx, _) in text.match_indices("Self") {
|
||||||
|
if handle_match(idx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -422,6 +448,24 @@ impl<'a> FindUsages<'a> {
|
||||||
};
|
};
|
||||||
sink(file_id, reference)
|
sink(file_id, reference)
|
||||||
}
|
}
|
||||||
|
Some(NameRefClass::Definition(Definition::SelfType(impl_))) => {
|
||||||
|
let ty = impl_.self_ty(self.sema.db);
|
||||||
|
|
||||||
|
if let Some(adt) = ty.as_adt() {
|
||||||
|
if &Definition::ModuleDef(ModuleDef::Adt(adt)) == self.def {
|
||||||
|
let FileRange { file_id, range } =
|
||||||
|
self.sema.original_range(name_ref.syntax());
|
||||||
|
let reference = FileReference {
|
||||||
|
range,
|
||||||
|
name: ast::NameLike::NameRef(name_ref.clone()),
|
||||||
|
access: None,
|
||||||
|
};
|
||||||
|
return sink(file_id, reference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
|
Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
|
||||||
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 = match self.def {
|
let reference = match self.def {
|
||||||
|
|
|
@ -137,6 +137,17 @@ pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast:
|
||||||
ast_from_text(&format!("{}use {};", visibility, use_tree))
|
ast_from_text(&format!("{}use {};", visibility, use_tree))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr {
|
||||||
|
ast_from_text(&format!("fn f() {{ {} {} }}", path, fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_expr_field_list(
|
||||||
|
fields: impl IntoIterator<Item = ast::RecordExprField>,
|
||||||
|
) -> ast::RecordExprFieldList {
|
||||||
|
let fields = fields.into_iter().join(", ");
|
||||||
|
ast_from_text(&format!("fn f() {{ S {{ {} }} }}", fields))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
|
pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
|
||||||
return match expr {
|
return match expr {
|
||||||
Some(expr) => from_text(&format!("{}: {}", name, expr)),
|
Some(expr) => from_text(&format!("{}: {}", name, expr)),
|
||||||
|
@ -339,6 +350,21 @@ pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat {
|
||||||
|
ast_from_text(&format!("fn f({} {}: ()))", path, fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_pat_field_list(
|
||||||
|
fields: impl IntoIterator<Item = ast::RecordPatField>,
|
||||||
|
) -> ast::RecordPatFieldList {
|
||||||
|
let fields = fields.into_iter().join(", ");
|
||||||
|
ast_from_text(&format!("fn f(S {{ {} }}: ()))", fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField {
|
||||||
|
ast_from_text(&format!("fn f(S {{ {}: {} }}: ()))", name_ref, pat))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
|
/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
|
||||||
pub fn path_pat(path: ast::Path) -> ast::Pat {
|
pub fn path_pat(path: ast::Path) -> ast::Pat {
|
||||||
return from_text(&path.to_string());
|
return from_text(&path.to_string());
|
||||||
|
|
Loading…
Reference in a new issue