Skip to content

fix: reapply tracer circular dependency fix without breaking if-block narrowing#3436

Open
RomanSpector wants to merge 2 commits into
LuaLS:masterfrom
RomanSpector:fix/tracer-circular-narrowing
Open

fix: reapply tracer circular dependency fix without breaking if-block narrowing#3436
RomanSpector wants to merge 2 commits into
LuaLS:masterfrom
RomanSpector:fix/tracer-circular-narrowing

Conversation

@RomanSpector

Copy link
Copy Markdown
Contributor

Summary

3.18.2 reverted the tracer fix from #3392 ("Fix type inference for and/or idioms and circular dependency in tracer", 46d0faf) because the _compilingAssigns guard broke type narrowing in if-blocks that reassign the variable. This PR reapplies that fix and resolves the regression that caused the revert, restoring the fixes for #2236, #2374 and #2494 (all three are broken again on current master).

Root cause of the regression that triggered the revert

The guard fired when calcNode re-entered an assign that was still being compiled — this happens when the assigned value's type depends on the variable itself (e.g. x = x .. 's'). On the re-entrant path it skipped lookIntoBlock, so nodes[subBlock] was left unset; the if handler then fell back to the branch entry node, dropping the assignment's effect, and the outer walk cached that poisoned narrowing for every position after the block.

The bug was invisible to the test suite: it only manifests when a position inside the branch is compiled before a position after the block — the normal compile order of an editor session, but something single-query-per-file tests never do. That is why CI stayed green while the editor misbehaved:

---@type integer?
local x

if x then
    x = x .. 's'
    print(x) -- hover here first: string
end

print(x) -- then here: was integer? (assignment lost), should be string?

Fix

calcNode now tracks in-flight assigns per coroutine (weak-keyed by coroutine.running(), so an abandoned coroutine can no longer permanently leak the in-flight flag) and counts re-entrant hits. When the outer call finishes compiling and a re-entrant hit happened meanwhile, the tracer drops its nodes/mark caches, so every later lookup recomputes with the final type instead of the poisoned intermediate state. Purges are rare (only self-referential assignments reachable from control-flow handlers) and terminate because vm.compileNode results are cached, so a source can trigger at most one re-entrant compile.

Tests

  • New test/type_inference/reassign_narrow.lua with a dual-query harness that queries a position inside the branch before a position after the block, reproducing the editor compile order:
  • Full test suite passes on win32-x64 (bin/lua-language-server.exe test.lua).

Closes #2236, closes #2374, closes #2494

🤖 Generated with Claude Code

RomanSpector and others added 2 commits July 24, 2026 17:59
… in tracer"

This reverts commit e041833.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The _compilingAssigns guard (3.18.1) prevented stale incomplete types
from propagating when calcNode re-enters an assign that is still being
compiled, but the skipped lookIntoBlock left nodes[subBlock] unset: the
if-handler then merged the branch entry node instead of the assignment
result, and every node cached until the outer compile finished kept
that poisoned narrowing (e.g. reassignment in an if-block was lost for
all code after the block once a position inside the branch had been
compiled first). This is why the fix was reverted in 3.18.2.

Track in-flight assigns per coroutine (so an abandoned coroutine cannot
leak the flag) and count re-entrant hits; when the outer calcNode
finishes compiling and a re-entrant hit happened meanwhile, drop the
tracer node/mark caches so later lookups recompute with the final type.

Adds dual-query regression tests that compile a position inside the
branch before a position after the block, matching the compile order of
a real editor session that single-query tests cannot reproduce.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can not infer basic assignments in nested loop. Type is "lost" of variable within For Loop Unknown type when dividing two numbers (2)

1 participant