Fix type inference for and/or idioms and circular dependency in tracer#3391
Fix type inference for and/or idioms and circular dependency in tracer#3391RomanSpector wants to merge 2 commits intoLuaLS:masterfrom
Conversation
- Fix type narrowing for `x == nil and "default" or x` pattern: the `and` handler in the tracer now propagates outNode when the right side is a truthy literal, so the `or` branch correctly infers the variable as non-nil - Fix circular dependency in calcNode for assignments inside if/for blocks: a _compilingAssigns guard prevents incomplete types and stale marks from propagating when the if-handler's getNode(lastAssign) triggers recompilation of an already-compiling setlocal - Filter no-unknown false positives when a node only contains a variable object whose base declaration has a known type Closes LuaLS#2236, LuaLS#2374, LuaLS#2494
There was a problem hiding this comment.
Code Review
This pull request addresses false positive "unknown" diagnostics and type degradation caused by circular dependencies. It introduces a guard in the assignment compilation process and allows falling back to base declarations when a type is not immediately known. Furthermore, it improves type inference for logical "and" expressions by propagating narrowed nodes when the right-hand side is a truthy literal. A suggestion was made to refine the truthiness check in tracer.lua by utilizing vm.compileNode and alwaysTruthy() for a more robust implementation.
| -- When the right side of `and` is a truthy literal (string, number, | ||
| -- true, table, function), the `and` can only be false when the left | ||
| -- side is false. Propagate the narrowed outNode so that patterns | ||
| -- like `x == nil and "default" or x` correctly infer x as non-nil. | ||
| local tp2 = action[2].type | ||
| if tp2 == 'string' | ||
| or tp2 == 'number' | ||
| or tp2 == 'integer' | ||
| or tp2 == 'table' | ||
| or tp2 == 'function' | ||
| or (tp2 == 'boolean' and action[2][1] == true) then | ||
| outNode = outNode1 | ||
| end |
There was a problem hiding this comment.
The current check for a truthy right-hand side of an and expression is limited to literals. This could be made more robust and general by using vm.compileNode and the existing alwaysTruthy() method on the resulting node. This would handle not just literals but any expression that can be determined to be always truthy, such as constants or function calls returning known truthy values.
-- When the right side of `and` is a truthy expression, the `and`
-- can only be false when the left side is false. Propagate the
-- narrowed outNode so that patterns like `x == nil and "default" or x`
-- correctly infer x as non-nil.
local node2 = vm.compileNode(action[2])
if node2:alwaysTruthy() then
outNode = outNode1
end
We intentionally avoid calling |
2276dc2 to
eb1135e
Compare
Summary
x == nil and "default" or xpattern — theandhandler in the tracer now propagatesoutNodewhen the right side is a truthy literal, so theorbranch correctly infers the variable as non-nilcalcNodefor assignments insideif/forblocks — a_compilingAssignsguard prevents incomplete types and stale marks from propagating when the if-handler'sgetNode(lastAssign)triggers recompilation of an already-compiling setlocalno-unknownfalse positives when a node only contains avariableobject whose base declaration has a known typeCloses #2236, #2374, #2494
Examples
Ternary idiom
x == nil and "default" or xAssignments inside
ifblocksVariable in
forloopTest plan
a = a == nil and "test" or a—ainferred asstring, notstring|nilorunknownif true then ... end— no regressiona = a or "test"insideif thenblock —ainferred asstringforloop (test = test + 1) — nounknowndegradationif a and b then/elseifchains — no regression in type narrowing