Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir/src/semantics.rs')
| -rw-r--r-- | crates/hir/src/semantics.rs | 84 |
1 files changed, 83 insertions, 1 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index ffb518b1e6..a9af26aa3f 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -16,7 +16,7 @@ use hir_def::{ expr_store::{Body, ExprOrPatSource, HygieneId, path::Path}, hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat}, nameres::{ModuleOrigin, crate_def_map}, - resolver::{self, HasResolver, Resolver, TypeNs}, + resolver::{self, HasResolver, Resolver, TypeNs, ValueNs}, type_ref::Mutability, }; use hir_expand::{ @@ -2192,6 +2192,88 @@ impl<'db> SemanticsImpl<'db> { self.cache(adt_source.value.syntax().ancestors().last().unwrap(), adt_source.file_id); ToDef::to_def(self, adt_source.as_ref()) } + + pub fn locals_used( + &self, + element: Either<&ast::Expr, &ast::StmtList>, + text_range: TextRange, + ) -> Option<Vec<Local>> { + let sa = self.analyze(element.either(|e| e.syntax(), |s| s.syntax()))?; + let store = sa.store()?; + let mut resolver = sa.resolver.clone(); + let def = resolver.body_owner()?; + + let is_not_generated = |path: &Path| { + !path.mod_path().and_then(|path| path.as_ident()).is_some_and(Name::is_generated) + }; + + let exprs = element.either( + |e| vec![e.clone()], + |stmts| { + let mut exprs: Vec<_> = stmts + .statements() + .filter(|stmt| text_range.contains_range(stmt.syntax().text_range())) + .filter_map(|stmt| match stmt { + ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr().map(|e| vec![e]), + ast::Stmt::Item(_) => None, + ast::Stmt::LetStmt(let_stmt) => { + let init = let_stmt.initializer(); + let let_else = let_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() + .collect(); + + if let Some(tail_expr) = stmts.tail_expr() + && text_range.contains_range(tail_expr.syntax().text_range()) + { + exprs.push(tail_expr); + } + exprs + }, + ); + 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] + && 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()); + } + }); + } + }; + + while let Some(expr_id) = exprs.pop() { + let mut has_child = false; + store.walk_child_exprs(expr_id, |id| { + has_child = true; + exprs.push(id); + }); + if !has_child { + add_to_locals_used(expr_id) + } + } + + Some(locals) + } } // FIXME This can't be the best way to do this |