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 | 267 |
1 files changed, 174 insertions, 93 deletions
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index 4b7314be46..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; @@ -687,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), @@ -718,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() @@ -799,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) => { @@ -826,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) } @@ -6294,4 +6229,150 @@ fn $0fun_name(v: i32) { }"#, ); } + + #[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); +} +"#, + ); + } } |