Skip to content

Commit d8e5882

Browse files
committed
fix(flow): apply deferred narrows after antecedent resolution
When walking backward through flow, collect condition narrows as pending actions instead of applying them while recursively querying antecedent types. The eager path mixed narrowing with antecedent resolution, so stacked guards could re-enter flow inference from inside condition evaluation and build deep recursive chains across repeated truthiness, type-guard, signature-cast, and correlated return-overload checks. Resolve the antecedent type first, then apply the pending narrows in reverse order. That keeps narrowing as a post-pass over an already-resolved input, avoids re-entering the same condition chain while answering the current flow query, and lets same-variable self/member guards wait until earlier guards have narrowed the receiver enough for reliable lookup. Key the flow cache by whether condition narrowing is enabled, and separate assignment source lookup from condition application. Reuse a narrowed source only when the RHS preserves that precision; broader RHS expressions fall back to the antecedent type with condition narrowing disabled so reassignment clears stale branch narrows, while exact literals and compatible partial table/object rewrites still preserve useful narrowing. Add regression coverage for stacked guards, correlated overload joins, pcall aliases, and assign/return diagnostics.
1 parent 3a2cdb9 commit d8e5882

14 files changed

Lines changed: 2757 additions & 707 deletions

File tree

crates/emmylua_code_analysis/src/compilation/test/flow.rs

Lines changed: 837 additions & 0 deletions
Large diffs are not rendered by default.

crates/emmylua_code_analysis/src/compilation/test/pcall_test.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
mod test {
33
use crate::{DiagnosticCode, VirtualWorkspace};
44

5+
const STACKED_PCALL_ALIAS_GUARDS: usize = 180;
6+
57
#[test]
68
fn test_issue_263() {
79
let mut ws = VirtualWorkspace::new_with_init_std_lib();
@@ -141,4 +143,36 @@ mod test {
141143
assert_eq!(ws.expr_ty("status"), ws.ty("boolean|string"));
142144
assert_eq!(ws.expr_ty("payload"), ws.ty("integer|string"));
143145
}
146+
147+
#[test]
148+
fn test_pcall_stacked_alias_guards_build_semantic_model() {
149+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
150+
let repeated_guards =
151+
"if failed then error(result) end\n".repeat(STACKED_PCALL_ALIAS_GUARDS);
152+
let block = format!(
153+
r#"
154+
---@return integer
155+
local function foo()
156+
return 1
157+
end
158+
159+
local ok, result = pcall(foo)
160+
local failed = ok == false
161+
162+
{repeated_guards}
163+
narrowed = result
164+
"#,
165+
);
166+
167+
let file_id = ws.def(&block);
168+
169+
assert!(
170+
ws.analysis
171+
.compilation
172+
.get_semantic_model(file_id)
173+
.is_some(),
174+
"expected semantic model for stacked pcall alias guard repro"
175+
);
176+
assert_eq!(ws.expr_ty("narrowed"), ws.ty("integer"));
177+
}
144178
}

0 commit comments

Comments
 (0)