From 2ebddc0ccd54fb3c0684cca1f63c35cf5acaa971 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Thu, 5 Mar 2026 17:11:04 -0500 Subject: [PATCH 1/3] feat: detect recursive closures properly --- .../src/db_index/declaration/decl_tree.rs | 18 +++++++++++------- .../diagnostic/test/undefined_global_test.rs | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/crates/emmylua_code_analysis/src/db_index/declaration/decl_tree.rs b/crates/emmylua_code_analysis/src/db_index/declaration/decl_tree.rs index 7bb214b4f..258e20fd0 100644 --- a/crates/emmylua_code_analysis/src/db_index/declaration/decl_tree.rs +++ b/crates/emmylua_code_analysis/src/db_index/declaration/decl_tree.rs @@ -151,13 +151,17 @@ impl LuaDeclarationTree { { match scope.get_kind() { LuaScopeKind::LocalOrAssignStat => { - let parent = scope.get_parent(); - if let Some(parent) = parent { - let parent_scope = match self.scopes.get(parent.id as usize) { - Some(scope) => scope, - None => return, - }; - self.walk_up(parent_scope, scope.get_position(), level, f); + if level == 0 { + let parent = scope.get_parent(); + if let Some(parent) = parent { + let parent_scope = match self.scopes.get(parent.id as usize) { + Some(scope) => scope, + None => return, + }; + self.walk_up(parent_scope, scope.get_position(), level, f); + } + } else { + self.base_walk_up(scope, start_pos, level, f); } } LuaScopeKind::Repeat => { diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/undefined_global_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/undefined_global_test.rs index 123b1e7a9..b63a0a95b 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/undefined_global_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/undefined_global_test.rs @@ -20,4 +20,20 @@ mod test { "# )); } + + #[test] + fn test_globals() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); + assert!(ws.check_code_for( + DiagnosticCode::UndefinedGlobal, + r#" + local fact = function(n) + if n == 0 then + return 1 + end + return n * fact(n - 1) + end + "# + )); + } } From 150cb350549c112fe06b6a2f127bafb881356cc7 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Thu, 5 Mar 2026 17:25:24 -0500 Subject: [PATCH 2/3] signature --- .../test/incomplete_signature_doc_test.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs index 5a1df2ab0..af6493fa5 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs @@ -25,14 +25,14 @@ mod tests { ws.enable_full_diagnostic(); // TODO: closures are not detected as functions in the semantic model - // assert!(!ws.check_code_for( - // DiagnosticCode::MissingGlobalDoc, - // r#" - // local c = function(x, y) - // return x + y - // end - // "# - // )); + assert!(!ws.check_code_for( + DiagnosticCode::MissingGlobalDoc, + r#" + local c = function(x, y) + return x + y + end + "# + )); assert!(!ws.check_code_for( DiagnosticCode::IncompleteSignatureDoc, From 3770283bc406e7ce2fec51b08646ca74e087ea76 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Thu, 5 Mar 2026 17:35:03 -0500 Subject: [PATCH 3/3] closure --- .../test/incomplete_signature_doc_test.rs | 3 +-- .../semantic_info/infer_expr_semantic_decl.rs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs index af6493fa5..b640c2b8c 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs @@ -24,9 +24,8 @@ mod tests { let mut ws = VirtualWorkspace::new(); ws.enable_full_diagnostic(); - // TODO: closures are not detected as functions in the semantic model assert!(!ws.check_code_for( - DiagnosticCode::MissingGlobalDoc, + DiagnosticCode::IncompleteSignatureDoc, r#" local c = function(x, y) return x + y diff --git a/crates/emmylua_code_analysis/src/semantic/semantic_info/infer_expr_semantic_decl.rs b/crates/emmylua_code_analysis/src/semantic/semantic_info/infer_expr_semantic_decl.rs index 75f497155..0bfbd6ec9 100644 --- a/crates/emmylua_code_analysis/src/semantic/semantic_info/infer_expr_semantic_decl.rs +++ b/crates/emmylua_code_analysis/src/semantic/semantic_info/infer_expr_semantic_decl.rs @@ -222,6 +222,26 @@ fn infer_closure_expr_semantic_decl( level, ) } + LuaStat::LocalStat(local_stat) => { + let local_name = + local_stat.get_local_name_by_value(LuaExpr::ClosureExpr(closure_expr))?; + let name_token = local_name.get_name_token()?; + infer_token_semantic_decl(db, cache, name_token.syntax().clone(), level) + } + LuaStat::AssignStat(assign_stat) => { + let (vars, exprs) = assign_stat.get_var_and_expr_list(); + let idx = exprs.iter().position(|expr| { + matches!(expr, LuaExpr::ClosureExpr(ce) if ce.syntax() == closure_expr.syntax()) + })?; + let var = vars.get(idx)?; + infer_expr_semantic_decl( + db, + cache, + var.clone().into(), + semantic_guard.next_level()?, + level, + ) + } _ => None, } }