From 93b666b928c21851840edf0ea46b29c4263fb466 Mon Sep 17 00:00:00 2001 From: romanspector Date: Sat, 28 Mar 2026 22:50:45 +0300 Subject: [PATCH 1/2] Fix type inference for bitwise operators on integer variables Bitwise operators (<<, >>, &, |, ~) on integer-typed variables returned 'unknown' instead of 'integer'. For example: local a = 1 << 20 -- integer = 1048576 (OK) local b = a << 1 -- was: unknown, now: integer = 2097152 Two fixes: - operator.lua: add integer type fallback for bitwise ops when operands are integer-typed but not literal constants (matching existing behavior for +, -, *, % operators) - value.lua: vm.getInteger now traces through getlocal to the local definition's value when the compiled node doesn't preserve the literal, enabling constant folding for chained expressions Co-Authored-By: Claude Opus 4.6 (1M context) --- changelog.md | 2 ++ script/vm/operator.lua | 9 +++++++++ script/vm/value.lua | 14 +++++++++++++- test/type_inference/common.lua | 10 ++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 343a2e40c..62ad11bd7 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,8 @@ * `CHG` Modified the `ResolveRequire` function to pass the source URI as a third argument. * `CHG` Improved the output of test failures during development +* `FIX` Fix type inference for bitwise operators (`<<`, `>>`, `&`, `|`, `~`) on integer variables +* `FIX` Fix constant value computation for chained bitwise expressions in hover tooltips ## 3.17.1 `2026-01-20` diff --git a/script/vm/operator.lua b/script/vm/operator.lua index e621d139a..c8e35dfd4 100644 --- a/script/vm/operator.lua +++ b/script/vm/operator.lua @@ -267,6 +267,15 @@ vm.binarySwitch = util.switch() end if node then vm.setNode(source, node) + return + end + -- Bitwise ops on integers always produce integer + local uri = guide.getUri(source) + local infer1 = vm.getInfer(source[1]) + local infer2 = vm.getInfer(source[2]) + if infer1:hasType(uri, 'integer') + and infer2:hasType(uri, 'integer') then + vm.setNode(source, vm.declareGlobal('type', 'integer')) end end end) diff --git a/script/vm/value.lua b/script/vm/value.lua index ce031357d..308066c79 100644 --- a/script/vm/value.lua +++ b/script/vm/value.lua @@ -118,6 +118,7 @@ function vm.getInteger(v) end local node = vm.compileNode(v) local result + local hasNonInteger = false for n in node:eachObject() do if n.type == 'integer' then if result then @@ -135,7 +136,18 @@ function vm.getInteger(v) end elseif n.type ~= 'local' and n.type ~= 'global' then - return nil + hasNonInteger = true + end + end + if hasNonInteger and not result then + result = nil + end + -- If value not found via compiled node, try the local's + -- definition value directly (tracer may not preserve literals) + if result == nil and v.type == 'getlocal' then + local loc = v.node + if loc and loc.value then + return vm.getInteger(loc.value) end end return result diff --git a/test/type_inference/common.lua b/test/type_inference/common.lua index c54c82ee1..0d545b3ab 100644 --- a/test/type_inference/common.lua +++ b/test/type_inference/common.lua @@ -146,6 +146,16 @@ TEST 'integer' [[ = 1 << 2 ]] +TEST 'integer' [[ +local a = 1 << 20 +local = a << 1 +]] + +TEST 'integer' [[ +local a = 1 << 20 +local = (a << 1) - 1 +]] + TEST 'unknown' [[ = a .. b ]] From 7592060f10e991c9dc15d7de7b1886d8de30f6d7 Mon Sep 17 00:00:00 2001 From: romanspector Date: Sat, 28 Mar 2026 23:00:23 +0300 Subject: [PATCH 2/2] Address review: fix union type handling and extract helper - value.lua: clear result when any non-integer type is found in node, preventing incorrect integer return for union types (integer|string) - operator.lua: extract getOperandInfers() helper to deduplicate URI/infer retrieval across 5 call sites Co-Authored-By: Claude Opus 4.6 (1M context) --- script/vm/operator.lua | 14 ++++++++------ script/vm/value.lua | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/script/vm/operator.lua b/script/vm/operator.lua index c8e35dfd4..75defe852 100644 --- a/script/vm/operator.lua +++ b/script/vm/operator.lua @@ -58,6 +58,12 @@ vm.OP_UNARY_MAP = util.revertMap(unaryMap) vm.OP_BINARY_MAP = util.revertMap(binaryMap) vm.OP_OTHER_MAP = util.revertMap(otherMap) +---@param source parser.object +---@return uri, vm.infer, vm.infer +local function getOperandInfers(source) + return guide.getUri(source), vm.getInfer(source[1]), vm.getInfer(source[2]) +end + ---@param operators parser.object[] ---@param op string ---@param value? parser.object @@ -270,9 +276,7 @@ vm.binarySwitch = util.switch() return end -- Bitwise ops on integers always produce integer - local uri = guide.getUri(source) - local infer1 = vm.getInfer(source[1]) - local infer2 = vm.getInfer(source[2]) + local uri, infer1, infer2 = getOperandInfers(source) if infer1:hasType(uri, 'integer') and infer2:hasType(uri, 'integer') then vm.setNode(source, vm.declareGlobal('type', 'integer')) @@ -395,9 +399,7 @@ vm.binarySwitch = util.switch() [1] = a .. b, }) else - local uri = guide.getUri(source) - local infer1 = vm.getInfer(source[1]) - local infer2 = vm.getInfer(source[2]) + local uri, infer1, infer2 = getOperandInfers(source) if ( infer1:hasType(uri, 'integer') or infer1:hasType(uri, 'number') diff --git a/script/vm/value.lua b/script/vm/value.lua index 308066c79..f487cda45 100644 --- a/script/vm/value.lua +++ b/script/vm/value.lua @@ -139,7 +139,7 @@ function vm.getInteger(v) hasNonInteger = true end end - if hasNonInteger and not result then + if hasNonInteger then result = nil end -- If value not found via compiled node, try the local's