fix: consider path used in pattern destructing assignments

fix: assign mutable for variables in pattern destructing assignments
This commit is contained in:
dfireBird 2025-12-15 17:28:05 +05:30
parent 594623cada
commit a44de58d4b
No known key found for this signature in database
GPG key ID: 26D522CA5FC2B93D
3 changed files with 99 additions and 18 deletions

View file

@ -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<Vec<Local>> {
) -> Option<FxIndexSet<Local>> {
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<Local> = Vec::new();
let mut add_to_locals_used = |expr_id| {
if let Expr::Path(path) = &store[expr_id]
let mut locals: FxIndexSet<Local> = 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)

View file

@ -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);
}
"#,
);
}
}

View file

@ -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)
}
}