Skip to content

Commit 9699de9

Browse files
authored
Merge pull request #1147 from lewis6991/fix/issue1146-stuck-loading
fix(flow): stop cyclic walks after unreachable continue
2 parents 90c1b77 + 460651c commit 9699de9

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[cfg(test)]
22
mod test {
3-
use crate::{DiagnosticCode, LuaType, VirtualWorkspace};
3+
use crate::{DiagnosticCode, EmmyrcLuaVersion, LuaType, VirtualWorkspace};
44
use emmylua_parser::{LuaAstToken, LuaLocalName};
55
use ntest::timeout;
66

@@ -1475,6 +1475,30 @@ end
14751475
assert_eq!(ws.expr_ty("after_loop"), ws.ty("string?"));
14761476
}
14771477

1478+
#[test]
1479+
#[timeout(5000)]
1480+
fn test_unreachable_continue_loop_after_return_does_not_cycle() {
1481+
let mut ws = VirtualWorkspace::new();
1482+
let mut emmyrc = ws.get_emmyrc();
1483+
emmyrc.runtime.version = EmmyrcLuaVersion::LuaJITExt;
1484+
ws.update_emmyrc(emmyrc);
1485+
1486+
ws.def(
1487+
r#"
1488+
local side_effect = 0
1489+
return boolean
1490+
1491+
local continue_counter = 0
1492+
for i = 1, 5 do
1493+
if i % 2 == 0 then
1494+
continue
1495+
end
1496+
continue_counter = continue_counter + 1
1497+
end
1498+
"#,
1499+
);
1500+
}
1501+
14781502
#[test]
14791503
fn test_for_in_loop_post_flow_keeps_incoming_type_after_break() {
14801504
let mut ws = VirtualWorkspace::new_with_init_std_lib();

crates/emmylua_code_analysis/src/semantic/infer/narrow/get_type_at_flow.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,7 +1322,18 @@ impl<'a> FlowTypeEngine<'a> {
13221322
// produces a final type, needs another query first, or reaches a saved
13231323
// continuation point like a branch merge.
13241324
fn evaluate_walk(&mut self, mut walk: QueryWalk) -> Result<SchedulerStep, InferFailReason> {
1325+
// Most walks are acyclic, so only start tracking after loop control can
1326+
// feed a later flow node back into an earlier loop label.
1327+
let mut visited_flow_ids = None::<HashSet<FlowId>>;
1328+
13251329
loop {
1330+
if let Some(visited_flow_ids) = &mut visited_flow_ids
1331+
&& !visited_flow_ids.insert(walk.antecedent_flow_id)
1332+
{
1333+
let result_type = get_var_ref_type(self.db, self.cache, &walk.query.var_ref_id)?;
1334+
return Ok(self.finish_walk(walk, result_type));
1335+
}
1336+
13261337
// Replays can suspend a query and later revisit an older antecedent
13271338
// that another query already finished. Use that cached type instead
13281339
// of walking back through the same replay chain again.
@@ -1356,6 +1367,15 @@ impl<'a> FlowTypeEngine<'a> {
13561367
| FlowNodeKind::Continue
13571368
| FlowNodeKind::Return
13581369
| FlowNodeKind::ForIStat(_) => {
1370+
if matches!(
1371+
&flow_node.kind,
1372+
FlowNodeKind::LoopLabel | FlowNodeKind::Continue
1373+
) && visited_flow_ids.is_none()
1374+
{
1375+
let mut visited = HashSet::new();
1376+
visited.insert(walk.antecedent_flow_id);
1377+
visited_flow_ids = Some(visited);
1378+
}
13591379
walk.antecedent_flow_id = get_single_antecedent(flow_node)?;
13601380
}
13611381
FlowNodeKind::BranchLabel | FlowNodeKind::NamedLabel(_) => {

0 commit comments

Comments
 (0)