diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index a9af26aa3f66..ccbfd45e3011 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -10,6 +10,7 @@ use std::{ ops::{self, ControlFlow, Not}, }; +use base_db::FxIndexSet; use either::Either; use hir_def::{ DefWithBodyId, FunctionId, MacroId, StructId, TraitId, VariantId, @@ -2197,7 +2198,7 @@ impl<'db> SemanticsImpl<'db> { &self, element: Either<&ast::Expr, &ast::StmtList>, text_range: TextRange, - ) -> Option> { + ) -> Option> { let sa = self.analyze(element.either(|e| e.syntax(), |s| s.syntax()))?; let store = sa.store()?; let mut resolver = sa.resolver.clone(); @@ -2245,31 +2246,49 @@ impl<'db> SemanticsImpl<'db> { let mut exprs: Vec<_> = exprs.into_iter().filter_map(|e| sa.expr_id(e).and_then(|e| e.as_expr())).collect(); - let mut locals: Vec = Vec::new(); - let mut add_to_locals_used = |expr_id| { - if let Expr::Path(path) = &store[expr_id] + let mut locals: FxIndexSet = FxIndexSet::default(); + let mut add_to_locals_used = |id, parent_expr| { + let path = match id { + ExprOrPatId::ExprId(expr_id) => { + if let Expr::Path(path) = &store[expr_id] { + Some(path) + } else { + None + } + } + ExprOrPatId::PatId(pat_id) => { + if let Pat::Path(path) = &store[pat_id] { + Some(path) + } else { + None + } + } + }; + + if let Some(path) = path && is_not_generated(path) { - let _ = resolver.update_to_inner_scope(self.db, def, expr_id); - resolver - .resolve_path_in_value_ns_fully(self.db, path, store.expr_path_hygiene(expr_id)) - .inspect(|value| { - if let ValueNs::LocalBinding(id) = value { - locals.push((def, *id).into()); - } - }); + let _ = resolver.update_to_inner_scope(self.db, def, parent_expr); + let hygiene = store.expr_or_pat_path_hygiene(id); + resolver.resolve_path_in_value_ns_fully(self.db, path, hygiene).inspect(|value| { + if let ValueNs::LocalBinding(id) = value { + locals.insert((def, *id).into()); + } + }); } }; while let Some(expr_id) = exprs.pop() { - let mut has_child = false; + if let Expr::Assignment { target, .. } = store[expr_id] { + store.walk_pats(target, &mut |id| { + add_to_locals_used(ExprOrPatId::PatId(id), expr_id) + }); + }; store.walk_child_exprs(expr_id, |id| { - has_child = true; exprs.push(id); }); - if !has_child { - add_to_locals_used(expr_id) - } + + add_to_locals_used(ExprOrPatId::ExprId(expr_id), expr_id) } Some(locals) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index 19ded49b1850..231df9b5b3e1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -6313,4 +6313,66 @@ fn $0fun_name(v1: u32, v2: u32) -> u32 { }"#, ); } + + #[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); +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index a48438cfa86f..1d865892a22b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -1345,7 +1345,7 @@ impl ReferenceCategory { // If the variable or field ends on the LHS's end then it's a Write // (covers fields and locals). FIXME: This is not terribly accurate. if let Some(lhs) = expr.lhs() - && lhs.syntax().text_range().end() == r.syntax().text_range().end() { + && lhs.syntax().text_range().contains_range(r.syntax().text_range()) { return Some(ReferenceCategory::WRITE) } }