Skip to content

Commit ec6569a

Browse files
committed
feat: semantics implementation of locals used for extract function
1 parent ec884b3 commit ec6569a

File tree

5 files changed

+209
-93
lines changed

5 files changed

+209
-93
lines changed

crates/hir-expand/src/name.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ impl Name {
197197
pub fn symbol(&self) -> &Symbol {
198198
&self.symbol
199199
}
200+
201+
pub fn is_generated(&self) -> bool {
202+
self.as_str().starts_with("<ra@gennew>")
203+
}
200204
}
201205

202206
struct Display<'a> {

crates/hir/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3908,6 +3908,31 @@ impl Local {
39083908
.unwrap(),
39093909
}
39103910
}
3911+
3912+
/// The leftmost definition for this local if present.
3913+
pub fn try_primary_source(self, db: &dyn HirDatabase) -> Option<LocalSource> {
3914+
let (body, source_map) = db.body_with_source_map(self.parent);
3915+
match body.self_param.zip(source_map.self_param_syntax()) {
3916+
Some((param, source)) if param == self.binding_id => {
3917+
let root = source.file_syntax(db);
3918+
Some(LocalSource {
3919+
local: self,
3920+
source: source.map(|ast| Either::Right(ast.to_node(&root))),
3921+
})
3922+
}
3923+
_ => source_map.patterns_for_binding(self.binding_id).first().and_then(|&definition| {
3924+
let src = source_map.pat_syntax(definition).ok()?;
3925+
let root = src.file_syntax(db);
3926+
Some(LocalSource {
3927+
local: self,
3928+
source: src.map(|ast| match ast.to_node(&root) {
3929+
Either::Right(ast::Pat::IdentPat(it)) => Either::Left(it),
3930+
_ => unreachable!("local with non ident-pattern"),
3931+
}),
3932+
})
3933+
}),
3934+
}
3935+
}
39113936
}
39123937

39133938
impl PartialOrd for Local {

crates/hir/src/semantics.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use hir_def::{
1616
expr_store::{Body, ExprOrPatSource, HygieneId, path::Path},
1717
hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat},
1818
nameres::{ModuleOrigin, crate_def_map},
19-
resolver::{self, HasResolver, Resolver, TypeNs},
19+
resolver::{self, HasResolver, Resolver, TypeNs, ValueNs},
2020
type_ref::Mutability,
2121
};
2222
use hir_expand::{
@@ -2192,6 +2192,88 @@ impl<'db> SemanticsImpl<'db> {
21922192
self.cache(adt_source.value.syntax().ancestors().last().unwrap(), adt_source.file_id);
21932193
ToDef::to_def(self, adt_source.as_ref())
21942194
}
2195+
2196+
pub fn locals_used(
2197+
&self,
2198+
element: Either<&ast::Expr, &ast::StmtList>,
2199+
text_range: TextRange,
2200+
) -> Option<Vec<Local>> {
2201+
let sa = self.analyze(element.either(|e| e.syntax(), |s| s.syntax()))?;
2202+
let store = sa.store()?;
2203+
let mut resolver = sa.resolver.clone();
2204+
let def = resolver.body_owner()?;
2205+
2206+
let is_not_generated = |path: &Path| {
2207+
!path.mod_path().and_then(|path| path.as_ident()).is_some_and(Name::is_generated)
2208+
};
2209+
2210+
let exprs = element.either(
2211+
|e| vec![e.clone()],
2212+
|stmts| {
2213+
let mut exprs: Vec<_> = stmts
2214+
.statements()
2215+
.filter(|stmt| text_range.contains_range(stmt.syntax().text_range()))
2216+
.filter_map(|stmt| match stmt {
2217+
ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr().map(|e| vec![e]),
2218+
ast::Stmt::Item(_) => None,
2219+
ast::Stmt::LetStmt(let_stmt) => {
2220+
let init = let_stmt.initializer();
2221+
let let_else = let_stmt
2222+
.let_else()
2223+
.and_then(|le| le.block_expr())
2224+
.map(ast::Expr::BlockExpr);
2225+
2226+
match (init, let_else) {
2227+
(Some(i), Some(le)) => Some(vec![i, le]),
2228+
(Some(i), _) => Some(vec![i]),
2229+
(_, Some(le)) => Some(vec![le]),
2230+
_ => None,
2231+
}
2232+
}
2233+
})
2234+
.flatten()
2235+
.collect();
2236+
2237+
if let Some(tail_expr) = stmts.tail_expr()
2238+
&& text_range.contains_range(tail_expr.syntax().text_range())
2239+
{
2240+
exprs.push(tail_expr);
2241+
}
2242+
exprs
2243+
},
2244+
);
2245+
let mut exprs: Vec<_> =
2246+
exprs.into_iter().filter_map(|e| sa.expr_id(e).and_then(|e| e.as_expr())).collect();
2247+
2248+
let mut locals: Vec<Local> = Vec::new();
2249+
let mut add_to_locals_used = |expr_id| {
2250+
if let Expr::Path(path) = &store[expr_id]
2251+
&& is_not_generated(path)
2252+
{
2253+
let _ = resolver.update_to_inner_scope(self.db, def, expr_id);
2254+
resolver
2255+
.resolve_path_in_value_ns_fully(self.db, path, store.expr_path_hygiene(expr_id))
2256+
.inspect(|value| {
2257+
if let ValueNs::LocalBinding(id) = value {
2258+
locals.push((def, *id).into());
2259+
}
2260+
});
2261+
}
2262+
};
2263+
2264+
while let Some(expr_id) = exprs.pop() {
2265+
let mut has_child = false;
2266+
store.walk_child_exprs(expr_id, |id| {
2267+
has_child = true;
2268+
exprs.push(id);
2269+
});
2270+
if !has_child {
2271+
add_to_locals_used(expr_id)
2272+
}
2273+
}
2274+
2275+
Some(locals)
2276+
}
21952277
}
21962278

21972279
// FIXME This can't be the best way to do this

crates/hir/src/source_analyzer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ impl<'db> SourceAnalyzer<'db> {
240240
)
241241
}
242242

243-
fn expr_id(&self, expr: ast::Expr) -> Option<ExprOrPatId> {
243+
pub(crate) fn expr_id(&self, expr: ast::Expr) -> Option<ExprOrPatId> {
244244
let src = InFile { file_id: self.file_id, value: expr };
245245
self.store_sm()?.node_expr(src.as_ref())
246246
}

crates/ide-assists/src/handlers/extract_function.rs

Lines changed: 96 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ use hir::{
99
use ide_db::{
1010
FxIndexSet, RootDatabase,
1111
assists::GroupLabel,
12-
defs::{Definition, NameRefClass},
12+
defs::Definition,
1313
famous_defs::FamousDefs,
1414
helpers::mod_path_to_ast,
1515
imports::insert_use::{ImportScope, insert_use},
1616
search::{FileReference, ReferenceCategory, SearchScope},
1717
source_change::SourceChangeBuilder,
1818
syntax_helpers::node_ext::{
19-
for_each_tail_expr, preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr,
19+
for_each_tail_expr, preorder_expr, walk_pat, walk_patterns_in_expr,
2020
},
2121
};
2222
use itertools::Itertools;
@@ -687,29 +687,6 @@ impl FunctionBody {
687687
}
688688
}
689689

690-
fn walk_expr(&self, cb: &mut dyn FnMut(ast::Expr)) {
691-
match self {
692-
FunctionBody::Expr(expr) => walk_expr(expr, cb),
693-
FunctionBody::Span { parent, text_range, .. } => {
694-
parent
695-
.statements()
696-
.filter(|stmt| text_range.contains_range(stmt.syntax().text_range()))
697-
.filter_map(|stmt| match stmt {
698-
ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr(),
699-
ast::Stmt::Item(_) => None,
700-
ast::Stmt::LetStmt(stmt) => stmt.initializer(),
701-
})
702-
.for_each(|expr| walk_expr(&expr, cb));
703-
if let Some(expr) = parent
704-
.tail_expr()
705-
.filter(|it| text_range.contains_range(it.syntax().text_range()))
706-
{
707-
walk_expr(&expr, cb);
708-
}
709-
}
710-
}
711-
}
712-
713690
fn preorder_expr(&self, cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool) {
714691
match self {
715692
FunctionBody::Expr(expr) => preorder_expr(expr, cb),
@@ -799,22 +776,14 @@ impl FunctionBody {
799776
let mut self_param = None;
800777
let mut res = FxIndexSet::default();
801778

802-
fn local_from_name_ref(
803-
sema: &Semantics<'_, RootDatabase>,
804-
name_ref: ast::NameRef,
805-
) -> Option<hir::Local> {
806-
match NameRefClass::classify(sema, &name_ref) {
807-
Some(
808-
NameRefClass::Definition(Definition::Local(local_ref), _)
809-
| NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ },
810-
) => Some(local_ref),
811-
_ => None,
812-
}
813-
}
779+
let (text_range, element) = match self {
780+
FunctionBody::Expr(expr) => (expr.syntax().text_range(), Either::Left(expr)),
781+
FunctionBody::Span { parent, text_range, .. } => (*text_range, Either::Right(parent)),
782+
};
814783

815784
let mut add_name_if_local = |local_ref: Local| {
816-
let InFile { file_id, value } = local_ref.primary_source(sema.db).source;
817785
// locals defined inside macros are not relevant to us
786+
let InFile { file_id, value } = local_ref.primary_source(sema.db).source;
818787
if !file_id.is_macro() {
819788
match value {
820789
Either::Right(it) => {
@@ -826,59 +795,11 @@ impl FunctionBody {
826795
}
827796
}
828797
};
829-
self.walk_expr(&mut |expr| match expr {
830-
ast::Expr::PathExpr(path_expr) => {
831-
if let Some(local) = path_expr
832-
.path()
833-
.and_then(|it| it.as_single_name_ref())
834-
.and_then(|name_ref| local_from_name_ref(sema, name_ref))
835-
{
836-
add_name_if_local(local);
837-
}
838-
}
839-
ast::Expr::ClosureExpr(closure_expr) => {
840-
if let Some(body) = closure_expr.body() {
841-
body.syntax()
842-
.descendants()
843-
.filter_map(ast::NameRef::cast)
844-
.filter_map(|name_ref| local_from_name_ref(sema, name_ref))
845-
.for_each(&mut add_name_if_local);
846-
}
847-
}
848-
ast::Expr::MacroExpr(expr) => {
849-
if let Some(tt) = expr.macro_call().and_then(|call| call.token_tree()) {
850-
tt.syntax()
851-
.descendants_with_tokens()
852-
.filter_map(SyntaxElement::into_token)
853-
.filter(|it| {
854-
matches!(it.kind(), SyntaxKind::STRING | SyntaxKind::IDENT | T![self])
855-
})
856-
.for_each(|t| {
857-
if ast::String::can_cast(t.kind()) {
858-
if let Some(parts) =
859-
ast::String::cast(t).and_then(|s| sema.as_format_args_parts(&s))
860-
{
861-
parts
862-
.into_iter()
863-
.filter_map(|(_, value)| value.and_then(|it| it.left()))
864-
.filter_map(|path| match path {
865-
PathResolution::Local(local) => Some(local),
866-
_ => None,
867-
})
868-
.for_each(&mut add_name_if_local);
869-
}
870-
} else {
871-
sema.descend_into_macros_exact(t)
872-
.into_iter()
873-
.filter_map(|t| t.parent().and_then(ast::NameRef::cast))
874-
.filter_map(|name_ref| local_from_name_ref(sema, name_ref))
875-
.for_each(&mut add_name_if_local);
876-
}
877-
});
878-
}
879-
}
880-
_ => (),
881-
});
798+
799+
if let Some(locals) = sema.locals_used(element, text_range) {
800+
locals.into_iter().for_each(&mut add_name_if_local);
801+
}
802+
882803
(res, self_param)
883804
}
884805

@@ -6291,6 +6212,90 @@ fn foo() {
62916212
62926213
fn $0fun_name(v: i32) {
62936214
print!("{v:?}{}", v == 123);
6215+
}"#,
6216+
);
6217+
}
6218+
6219+
#[test]
6220+
fn no_parameter_for_variable_used_only_let_else() {
6221+
check_assist(
6222+
extract_function,
6223+
r#"
6224+
fn foo() -> u32 {
6225+
let x = 5;
6226+
6227+
$0let Some(y) = Some(1) else {
6228+
return x * 2;
6229+
};$0
6230+
6231+
y
6232+
}"#,
6233+
r#"
6234+
fn foo() -> u32 {
6235+
let x = 5;
6236+
6237+
let y = match fun_name(x) {
6238+
Ok(value) => value,
6239+
Err(value) => return value,
6240+
};
6241+
6242+
y
6243+
}
6244+
6245+
fn $0fun_name(x: u32) -> Result<_, u32> {
6246+
let Some(y) = Some(1) else {
6247+
return Err(x * 2);
6248+
};
6249+
Ok(y)
6250+
}"#,
6251+
);
6252+
}
6253+
6254+
#[test]
6255+
fn deeply_nested_macros() {
6256+
check_assist(
6257+
extract_function,
6258+
r#"
6259+
macro_rules! m {
6260+
($val:ident) => { $val };
6261+
}
6262+
6263+
macro_rules! n {
6264+
($v1:ident, $v2:ident) => { m!($v1) + $v2 };
6265+
}
6266+
6267+
macro_rules! o {
6268+
($v1:ident, $v2:ident, $v3:ident) => { n!($v1, $v2) + $v3 };
6269+
}
6270+
6271+
fn foo() -> u32 {
6272+
let v1 = 1;
6273+
let v2 = 2;
6274+
$0let v3 = 3;
6275+
o!(v1, v2, v3)$0
6276+
}"#,
6277+
r#"
6278+
macro_rules! m {
6279+
($val:ident) => { $val };
6280+
}
6281+
6282+
macro_rules! n {
6283+
($v1:ident, $v2:ident) => { m!($v1) + $v2 };
6284+
}
6285+
6286+
macro_rules! o {
6287+
($v1:ident, $v2:ident, $v3:ident) => { n!($v1, $v2) + $v3 };
6288+
}
6289+
6290+
fn foo() -> u32 {
6291+
let v1 = 1;
6292+
let v2 = 2;
6293+
fun_name(v1, v2)
6294+
}
6295+
6296+
fn $0fun_name(v1: u32, v2: u32) -> u32 {
6297+
let v3 = 3;
6298+
o!(v1, v2, v3)
62946299
}"#,
62956300
);
62966301
}

0 commit comments

Comments
 (0)