2 use hir::{PathResolution, Semantics};
6 search::{FileReference, UsageSearchResult},
10 ast::{self, AstNode, AstToken, HasName},
11 SyntaxElement, TextRange,
15 assist_context::{AssistContext, Assists},
19 // Assist: inline_local_variable
21 // Inlines a local variable.
35 pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
36 let file_id = ctx.file_id();
37 let range = ctx.selection_trimmed();
38 let InlineData { let_stmt, delete_let, references, target } =
39 if let Some(path_expr) = ctx.find_node_at_offset::<ast::PathExpr>() {
40 inline_usage(&ctx.sema, path_expr, range, file_id)
41 } else if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
42 inline_let(&ctx.sema, let_stmt, range, file_id)
46 let initializer_expr = let_stmt.initializer()?;
48 let delete_range = delete_let.then(|| {
49 if let Some(whitespace) = let_stmt
51 .next_sibling_or_token()
52 .and_then(SyntaxElement::into_token)
53 .and_then(ast::Whitespace::cast)
56 let_stmt.syntax().text_range().start(),
57 whitespace.syntax().text_range().end(),
60 let_stmt.syntax().text_range()
64 let wrap_in_parens = references
66 .filter_map(|FileReference { range, name, .. }| match name {
67 ast::NameLike::NameRef(name) => Some((range, name)),
70 .map(|(range, name_ref)| {
71 if range != name_ref.syntax().text_range() {
72 // Do not rename inside macros
73 // FIXME: This feels like a bad heuristic for macros
77 name_ref.syntax().ancestors().find(|it| ast::PathExpr::can_cast(it.kind()));
78 let usage_parent_option =
79 usage_node.and_then(|it| it.parent()).and_then(ast::Expr::cast);
80 let usage_parent = match usage_parent_option {
82 None => return Some((range, name_ref, false)),
84 let initializer = matches!(
86 ast::Expr::CallExpr(_)
87 | ast::Expr::IndexExpr(_)
88 | ast::Expr::MethodCallExpr(_)
89 | ast::Expr::FieldExpr(_)
90 | ast::Expr::TryExpr(_)
91 | ast::Expr::Literal(_)
92 | ast::Expr::TupleExpr(_)
93 | ast::Expr::ArrayExpr(_)
94 | ast::Expr::ParenExpr(_)
95 | ast::Expr::PathExpr(_)
96 | ast::Expr::BlockExpr(_),
98 let parent = matches!(
100 ast::Expr::CallExpr(_)
101 | ast::Expr::TupleExpr(_)
102 | ast::Expr::ArrayExpr(_)
103 | ast::Expr::ParenExpr(_)
104 | ast::Expr::ForExpr(_)
105 | ast::Expr::WhileExpr(_)
106 | ast::Expr::BreakExpr(_)
107 | ast::Expr::ReturnExpr(_)
108 | ast::Expr::MatchExpr(_)
109 | ast::Expr::BlockExpr(_)
111 Some((range, name_ref, !(initializer || parent)))
113 .collect::<Option<Vec<_>>>()?;
115 let init_str = initializer_expr.syntax().text().to_string();
116 let init_in_paren = format!("({})", &init_str);
118 let target = match target {
119 ast::NameOrNameRef::Name(it) => it.syntax().text_range(),
120 ast::NameOrNameRef::NameRef(it) => it.syntax().text_range(),
124 AssistId("inline_local_variable", AssistKind::RefactorInline),
128 if let Some(range) = delete_range {
129 builder.delete(range);
131 for (range, name, should_wrap) in wrap_in_parens {
132 let replacement = if should_wrap { &init_in_paren } else { &init_str };
133 if ast::RecordExprField::for_field_name(&name).is_some() {
134 cov_mark::hit!(inline_field_shorthand);
135 builder.insert(range.end(), format!(": {}", replacement));
137 builder.replace(range, replacement.clone())
145 let_stmt: ast::LetStmt,
147 target: ast::NameOrNameRef,
148 references: Vec<FileReference>,
152 sema: &Semantics<'_, RootDatabase>,
153 let_stmt: ast::LetStmt,
156 ) -> Option<InlineData> {
157 let bind_pat = match let_stmt.pat()? {
158 ast::Pat::IdentPat(pat) => pat,
161 if bind_pat.mut_token().is_some() {
162 cov_mark::hit!(test_not_inline_mut_variable);
165 if !bind_pat.syntax().text_range().contains_range(range) {
166 cov_mark::hit!(not_applicable_outside_of_bind_pat);
170 let local = sema.to_def(&bind_pat)?;
171 let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
172 match references.remove(&file_id) {
173 Some(references) => Some(InlineData {
176 target: ast::NameOrNameRef::Name(bind_pat.name()?),
180 cov_mark::hit!(test_not_applicable_if_variable_unused);
187 sema: &Semantics<'_, RootDatabase>,
188 path_expr: ast::PathExpr,
191 ) -> Option<InlineData> {
192 let path = path_expr.path()?;
193 let name = path.as_single_name_ref()?;
194 if !name.syntax().text_range().contains_range(range) {
195 cov_mark::hit!(test_not_inline_selection_too_broad);
199 let local = match sema.resolve_path(&path)? {
200 PathResolution::Local(local) => local,
203 if local.is_mut(sema.db) {
204 cov_mark::hit!(test_not_inline_mut_variable_use);
208 // FIXME: Handle multiple local definitions
209 let bind_pat = match local.source(sema.db).value {
210 Either::Left(ident) => ident,
214 let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
216 let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
217 let mut references = references.remove(&file_id)?;
218 let delete_let = references.len() == 1;
219 references.retain(|fref| fref.name.as_name_ref() == Some(&name));
221 Some(InlineData { let_stmt, delete_let, target: ast::NameOrNameRef::NameRef(name), references })
226 use crate::tests::{check_assist, check_assist_not_applicable};
231 fn test_inline_let_bind_literal_expr() {
233 inline_local_variable,
265 fn test_inline_let_bind_bin_expr() {
267 inline_local_variable,
292 let b = (1 + 1) * 10;
299 fn test_inline_let_bind_function_call_expr() {
301 inline_local_variable,
333 fn test_inline_let_bind_cast_expr() {
335 inline_local_variable,
337 fn bar(a: usize): usize { a }
339 let a$0 = bar(1) as u64;
351 fn bar(a: usize): usize { a }
354 if (bar(1) as u64) > 10 {
357 while (bar(1) as u64) > 10 {
360 let b = (bar(1) as u64) * 10;
367 fn test_inline_let_bind_block_expr() {
369 inline_local_variable,
372 let a$0 = { 10 + 1 };
389 while { 10 + 1 } > 10 {
392 let b = { 10 + 1 } * 10;
399 fn test_inline_let_bind_paren_expr() {
401 inline_local_variable,
404 let a$0 = ( 10 + 1 );
421 while ( 10 + 1 ) > 10 {
424 let b = ( 10 + 1 ) * 10;
431 fn test_not_inline_mut_variable() {
432 cov_mark::check!(test_not_inline_mut_variable);
433 check_assist_not_applicable(
434 inline_local_variable,
444 fn test_not_inline_mut_variable_use() {
445 cov_mark::check!(test_not_inline_mut_variable_use);
446 check_assist_not_applicable(
447 inline_local_variable,
457 fn test_call_expr() {
459 inline_local_variable,
462 let a$0 = bar(10 + 1);
468 let b = bar(10 + 1) * 10;
469 let c = bar(10 + 1) as usize;
475 fn test_index_expr() {
477 inline_local_variable,
480 let x = vec![1, 2, 3];
487 let x = vec![1, 2, 3];
489 let c = x[0] as usize;
495 fn test_method_call_expr() {
497 inline_local_variable,
508 let b = bar.len() * 10;
509 let c = bar.len() as usize;
515 fn test_field_expr() {
517 inline_local_variable,
524 let bar = Bar { foo: 1 };
535 let bar = Bar { foo: 1 };
536 let b = bar.foo * 10;
537 let c = bar.foo as usize;
545 inline_local_variable,
547 fn foo() -> Option<usize> {
555 fn foo() -> Option<usize> {
558 let c = bar? as usize;
567 inline_local_variable,
583 fn test_tuple_expr() {
585 inline_local_variable,
599 fn test_array_expr() {
601 inline_local_variable,
609 let b = [1, 2, 3].len();
617 inline_local_variable,
626 let b = (10 + 20) * 10;
627 let c = (10 + 20) as usize;
633 fn test_path_expr() {
635 inline_local_variable,
653 fn test_block_expr() {
655 inline_local_variable,
665 let c = { 10 } as usize;
671 fn test_used_in_different_expr1() {
673 inline_local_variable,
684 let b = (10 + 20) * 10;
685 let c = (10 + 20, 20);
686 let d = [10 + 20, 10];
693 fn test_used_in_for_expr() {
695 inline_local_variable,
698 let a$0 = vec![10, 20];
703 for i in vec![10, 20] {}
709 fn test_used_in_while_expr() {
711 inline_local_variable,
725 fn test_used_in_break_expr() {
727 inline_local_variable,
745 fn test_used_in_return_expr() {
747 inline_local_variable,
761 fn test_used_in_match_expr() {
763 inline_local_variable,
777 fn inline_field_shorthand() {
778 cov_mark::check!(inline_field_shorthand);
780 inline_local_variable,
798 fn test_not_applicable_if_variable_unused() {
799 cov_mark::check!(test_not_applicable_if_variable_unused);
800 check_assist_not_applicable(
801 inline_local_variable,
811 fn not_applicable_outside_of_bind_pat() {
812 cov_mark::check!(not_applicable_outside_of_bind_pat);
813 check_assist_not_applicable(
814 inline_local_variable,
825 fn works_on_local_usage() {
827 inline_local_variable,
843 fn does_not_remove_let_when_multiple_usages() {
845 inline_local_variable,
864 fn not_applicable_with_non_ident_pattern() {
865 check_assist_not_applicable(
866 inline_local_variable,
877 fn not_applicable_on_local_usage_in_macro() {
878 check_assist_not_applicable(
879 inline_local_variable,
886 m!(xyz$0); // replacing it would break the macro
890 check_assist_not_applicable(
891 inline_local_variable,
898 m!(xyz); // replacing it would break the macro
905 fn test_not_inline_selection_too_broad() {
906 cov_mark::check!(test_not_inline_selection_too_broad);
907 check_assist_not_applicable(
908 inline_local_variable,
920 fn test_inline_ref_in_let() {
922 inline_local_variable,
942 fn test_inline_let_unit_struct() {
943 check_assist_not_applicable(
944 inline_local_variable,