Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/extract_function.rs')
-rw-r--r--crates/ide-assists/src/handlers/extract_function.rs344
1 files changed, 243 insertions, 101 deletions
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs
index 9a9adf26a6..231df9b5b3 100644
--- a/crates/ide-assists/src/handlers/extract_function.rs
+++ b/crates/ide-assists/src/handlers/extract_function.rs
@@ -9,14 +9,14 @@ use hir::{
use ide_db::{
FxIndexSet, RootDatabase,
assists::GroupLabel,
- defs::{Definition, NameRefClass},
+ defs::Definition,
famous_defs::FamousDefs,
helpers::mod_path_to_ast,
imports::insert_use::{ImportScope, insert_use},
search::{FileReference, ReferenceCategory, SearchScope},
source_change::SourceChangeBuilder,
syntax_helpers::node_ext::{
- for_each_tail_expr, preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr,
+ for_each_tail_expr, preorder_expr, walk_pat, walk_patterns_in_expr,
},
};
use itertools::Itertools;
@@ -206,10 +206,11 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
{
let scope = builder.make_import_scope_mut(scope);
let control_flow_enum =
- FamousDefs(&ctx.sema, module.krate()).core_ops_ControlFlow();
+ FamousDefs(&ctx.sema, module.krate(ctx.db())).core_ops_ControlFlow();
if let Some(control_flow_enum) = control_flow_enum {
- let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate()));
+ let cfg =
+ ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
let mod_path = module.find_use_path(
ctx.sema.db,
ModuleDef::from(control_flow_enum),
@@ -686,29 +687,6 @@ impl FunctionBody {
}
}
- fn walk_expr(&self, cb: &mut dyn FnMut(ast::Expr)) {
- match self {
- FunctionBody::Expr(expr) => walk_expr(expr, cb),
- FunctionBody::Span { parent, text_range, .. } => {
- parent
- .statements()
- .filter(|stmt| text_range.contains_range(stmt.syntax().text_range()))
- .filter_map(|stmt| match stmt {
- ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr(),
- ast::Stmt::Item(_) => None,
- ast::Stmt::LetStmt(stmt) => stmt.initializer(),
- })
- .for_each(|expr| walk_expr(&expr, cb));
- if let Some(expr) = parent
- .tail_expr()
- .filter(|it| text_range.contains_range(it.syntax().text_range()))
- {
- walk_expr(&expr, cb);
- }
- }
- }
- }
-
fn preorder_expr(&self, cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool) {
match self {
FunctionBody::Expr(expr) => preorder_expr(expr, cb),
@@ -717,10 +695,24 @@ impl FunctionBody {
.statements()
.filter(|stmt| text_range.contains_range(stmt.syntax().text_range()))
.filter_map(|stmt| match stmt {
- ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr(),
+ ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr().map(|e| vec![e]),
ast::Stmt::Item(_) => None,
- ast::Stmt::LetStmt(stmt) => stmt.initializer(),
+ ast::Stmt::LetStmt(stmt) => {
+ let init = stmt.initializer();
+ let let_else = stmt
+ .let_else()
+ .and_then(|le| le.block_expr())
+ .map(ast::Expr::BlockExpr);
+
+ match (init, let_else) {
+ (Some(i), Some(le)) => Some(vec![i, le]),
+ (Some(i), _) => Some(vec![i]),
+ (_, Some(le)) => Some(vec![le]),
+ _ => None,
+ }
+ }
})
+ .flatten()
.for_each(|expr| preorder_expr(&expr, cb));
if let Some(expr) = parent
.tail_expr()
@@ -798,22 +790,14 @@ impl FunctionBody {
let mut self_param = None;
let mut res = FxIndexSet::default();
- fn local_from_name_ref(
- sema: &Semantics<'_, RootDatabase>,
- name_ref: ast::NameRef,
- ) -> Option<hir::Local> {
- match NameRefClass::classify(sema, &name_ref) {
- Some(
- NameRefClass::Definition(Definition::Local(local_ref), _)
- | NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ },
- ) => Some(local_ref),
- _ => None,
- }
- }
+ let (text_range, element) = match self {
+ FunctionBody::Expr(expr) => (expr.syntax().text_range(), Either::Left(expr)),
+ FunctionBody::Span { parent, text_range, .. } => (*text_range, Either::Right(parent)),
+ };
let mut add_name_if_local = |local_ref: Local| {
- let InFile { file_id, value } = local_ref.primary_source(sema.db).source;
// locals defined inside macros are not relevant to us
+ let InFile { file_id, value } = local_ref.primary_source(sema.db).source;
if !file_id.is_macro() {
match value {
Either::Right(it) => {
@@ -825,59 +809,11 @@ impl FunctionBody {
}
}
};
- self.walk_expr(&mut |expr| match expr {
- ast::Expr::PathExpr(path_expr) => {
- if let Some(local) = path_expr
- .path()
- .and_then(|it| it.as_single_name_ref())
- .and_then(|name_ref| local_from_name_ref(sema, name_ref))
- {
- add_name_if_local(local);
- }
- }
- ast::Expr::ClosureExpr(closure_expr) => {
- if let Some(body) = closure_expr.body() {
- body.syntax()
- .descendants()
- .filter_map(ast::NameRef::cast)
- .filter_map(|name_ref| local_from_name_ref(sema, name_ref))
- .for_each(&mut add_name_if_local);
- }
- }
- ast::Expr::MacroExpr(expr) => {
- if let Some(tt) = expr.macro_call().and_then(|call| call.token_tree()) {
- tt.syntax()
- .descendants_with_tokens()
- .filter_map(SyntaxElement::into_token)
- .filter(|it| {
- matches!(it.kind(), SyntaxKind::STRING | SyntaxKind::IDENT | T![self])
- })
- .for_each(|t| {
- if ast::String::can_cast(t.kind()) {
- if let Some(parts) =
- ast::String::cast(t).and_then(|s| sema.as_format_args_parts(&s))
- {
- parts
- .into_iter()
- .filter_map(|(_, value)| value.and_then(|it| it.left()))
- .filter_map(|path| match path {
- PathResolution::Local(local) => Some(local),
- _ => None,
- })
- .for_each(&mut add_name_if_local);
- }
- } else {
- sema.descend_into_macros_exact(t)
- .into_iter()
- .filter_map(|t| t.parent().and_then(ast::NameRef::cast))
- .filter_map(|name_ref| local_from_name_ref(sema, name_ref))
- .for_each(&mut add_name_if_local);
- }
- });
- }
- }
- _ => (),
- });
+
+ if let Some(locals) = sema.locals_used(element, text_range) {
+ locals.into_iter().for_each(&mut add_name_if_local);
+ }
+
(res, self_param)
}
@@ -2060,7 +1996,7 @@ fn fix_param_usages(
.filter_map(|reference| path_element_of_reference(syntax, reference))
.map(|expr| tm.make_mut(&expr));
- usages_for_param.push((param, usages.collect()));
+ usages_for_param.push((param, usages.unique().collect()));
}
let res = tm.make_syntax_mut(syntax);
@@ -4250,7 +4186,7 @@ fn $0fun_name() -> Result<i32, i64> {
check_assist(
extract_function,
r#"
-//- minicore: option
+//- minicore: option, add, builtin_impls
fn bar() -> Option<i32> { None }
fn foo() -> Option<()> {
let n = bar()?;
@@ -4314,7 +4250,7 @@ fn $0fun_name() -> Option<()> {
check_assist(
extract_function,
r#"
-//- minicore: result
+//- minicore: result, add, builtin_impls
fn foo() -> Result<(), i64> {
let n = 1;
$0let k = foo()?;
@@ -4345,7 +4281,7 @@ fn $0fun_name() -> Result<i32, i64> {
check_assist(
extract_function,
r#"
-//- minicore: option
+//- minicore: option, add, builtin_impls
fn foo() -> Option<()> {
let n = 1;
$0let k = foo()?;
@@ -4382,7 +4318,7 @@ fn $0fun_name() -> Option<i32> {
check_assist(
extract_function,
r#"
-//- minicore: result
+//- minicore: result, add, builtin_impls
fn foo() -> Result<(), i64> {
let n = 1;
$0let k = foo()?;
@@ -4441,7 +4377,7 @@ fn foo() -> Option<()> {
check_assist(
extract_function,
r#"
-//- minicore: result
+//- minicore: result, add, builtin_impls
fn foo() -> Result<(), i64> {
let n = 1;
$0let k = foo()?;
@@ -6233,4 +6169,210 @@ fn $0fun_name(a: i32, b: i32) {
cov_mark::check!(extract_function_in_braces_is_not_applicable);
check_assist_not_applicable(extract_function, r"fn foo(arr: &mut $0[$0i32]) {}");
}
+
+ #[test]
+ fn issue_20965_panic() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: fmt
+#[derive(Debug)]
+struct Foo(&'static str);
+
+impl Foo {
+ fn text(&self) -> &str { self.0 }
+}
+
+fn main() {
+ let s = Foo("");
+ $0print!("{}{}", s, s);$0
+ let _ = s.text() == "";
+}"#,
+ r#"
+#[derive(Debug)]
+struct Foo(&'static str);
+
+impl Foo {
+ fn text(&self) -> &str { self.0 }
+}
+
+fn main() {
+ let s = Foo("");
+ fun_name(&s);
+ let _ = s.text() == "";
+}
+
+fn $0fun_name(s: &Foo) {
+ *print!("{}{}", s, s);
+}"#,
+ );
+ }
+
+ #[test]
+ fn parameter_is_added_used_in_eq_expression_in_macro() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: fmt
+fn foo() {
+ let v = 123;
+ $0print!("{v:?}{}", v == 123);$0
+}"#,
+ r#"
+fn foo() {
+ let v = 123;
+ fun_name(v);
+}
+
+fn $0fun_name(v: i32) {
+ print!("{v:?}{}", v == 123);
+}"#,
+ );
+ }
+
+ #[test]
+ fn no_parameter_for_variable_used_only_let_else() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> u32 {
+ let x = 5;
+
+ $0let Some(y) = Some(1) else {
+ return x * 2;
+ };$0
+
+ y
+}"#,
+ r#"
+fn foo() -> u32 {
+ let x = 5;
+
+ let y = match fun_name(x) {
+ Ok(value) => value,
+ Err(value) => return value,
+ };
+
+ y
+}
+
+fn $0fun_name(x: u32) -> Result<_, u32> {
+ let Some(y) = Some(1) else {
+ return Err(x * 2);
+ };
+ Ok(y)
+}"#,
+ );
+ }
+
+ #[test]
+ fn deeply_nested_macros() {
+ check_assist(
+ extract_function,
+ r#"
+macro_rules! m {
+ ($val:ident) => { $val };
+}
+
+macro_rules! n {
+ ($v1:ident, $v2:ident) => { m!($v1) + $v2 };
+}
+
+macro_rules! o {
+ ($v1:ident, $v2:ident, $v3:ident) => { n!($v1, $v2) + $v3 };
+}
+
+fn foo() -> u32 {
+ let v1 = 1;
+ let v2 = 2;
+ $0let v3 = 3;
+ o!(v1, v2, v3)$0
+}"#,
+ r#"
+macro_rules! m {
+ ($val:ident) => { $val };
+}
+
+macro_rules! n {
+ ($v1:ident, $v2:ident) => { m!($v1) + $v2 };
+}
+
+macro_rules! o {
+ ($v1:ident, $v2:ident, $v3:ident) => { n!($v1, $v2) + $v3 };
+}
+
+fn foo() -> u32 {
+ let v1 = 1;
+ let v2 = 2;
+ fun_name(v1, v2)
+}
+
+fn $0fun_name(v1: u32, v2: u32) -> u32 {
+ let v3 = 3;
+ o!(v1, v2, v3)
+}"#,
+ );
+ }
+
+ #[test]
+ fn pattern_assignment() {
+ check_assist(
+ extract_function,
+ r#"
+struct Point {x: u32, y: u32};
+
+fn point() -> Point {
+ Point { x: 45, y: 50 };
+}
+
+fn foo() {
+ let mut a = 1;
+ let mut b = 3;
+ $0Point { x: a, y: b } = point();$0
+}
+"#,
+ r#"
+struct Point {x: u32, y: u32};
+
+fn point() -> Point {
+ Point { x: 45, y: 50 };
+}
+
+fn foo() {
+ let mut a = 1;
+ let mut b = 3;
+ fun_name(a, b);
+}
+
+fn $0fun_name(mut a: u32, mut b: u32) {
+ Point { x: a, y: b } = point();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn tuple_assignment() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let mut a = 3;
+ let mut b = 4;
+ $0(a, b) = (b, a);$0
+}
+"#,
+ r#"
+fn foo() {
+ let mut a = 3;
+ let mut b = 4;
+ fun_name(a, b);
+}
+
+fn $0fun_name(mut a: i32, mut b: i32) {
+ (a, b) = (b, a);
+}
+"#,
+ );
+ }
}