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.rs | 344 |
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); +} +"#, + ); + } } |