Skip to content

Commit 9fec487

Browse files
committed
more work on ranges and narrowing of ranges
1 parent 55389ac commit 9fec487

13 files changed

Lines changed: 260 additions & 137 deletions

File tree

.vscode/on_editor_save.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ if is_nattlua and not test_focus then
129129
return
130130
end
131131

132-
if find("jit_options") then
132+
if find("intersect_comparison") then
133+
run_test("test/tests/nattlua/types/number.lua")
134+
elseif find("jit_options") then
133135
run_lua("test/performance/tests.lua")
134136
elseif find("jit_trace_track") or find("test/performance/analyzer.lua") then
135137
run_lua("test/performance/analyzer.lua")

language_server/json.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ local function parse_string(str--[[#: string]], i--[[#: number]])--[[#: string,n
226226
local last--[[#: number | nil]]
227227

228228
for j = i + 1, #str do
229-
local x = str:byte(j)
229+
local x = str:byte(j) --[[# as 0..255]]
230230

231231
if x < 32 then decode_error(str, j, "control character in string") end
232232

Lines changed: 189 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,194 @@
11
local LNumber = require("nattlua.types.number").LNumber
22
local LNumberRange = require("nattlua.types.range").LNumberRange
3-
--[[#local type NumericType = any]]
4-
local operators = {
5-
[">"] = function(a--[[#: number]], b--[[#: number]])
6-
return a > b
7-
end,
8-
["<"] = function(a--[[#: number]], b--[[#: number]])
9-
return a < b
10-
end,
11-
["<="] = function(a--[[#: number]], b--[[#: number]])
12-
return a <= b
13-
end,
14-
[">="] = function(a--[[#: number]], b--[[#: number]])
15-
return a >= b
16-
end,
17-
}
183
local max = math.max
194
local min = math.min
205

216
local function intersect(a_min, a_max, operator, b_min, b_max)
227
if operator == "<" then
23-
if a_min < b_min and a_min < b_max and a_max < b_min and a_min < b_max then
24-
return min(a_min, b_max), min(a_max, b_max - 1), nil, nil
8+
-- Case 1: A < B is always true (A's upper bound < B's lower bound)
9+
if a_max < b_min then
10+
return a_min, a_max, b_min, b_max, nil, nil, nil, nil
2511
end
26-
27-
if a_min >= b_min and a_min >= b_max and a_max >= b_min and a_min >= b_max then
28-
return nil, nil, min(b_min, a_max), min(b_max, a_max)
12+
13+
-- Case 2: A < B is always false (A's lower bound ≥ B's upper bound)
14+
if a_min >= b_max then
15+
return nil, nil, nil, nil, a_min, a_max, b_min, b_max
2916
end
30-
31-
return min(a_min, b_max),
32-
min(a_max, b_max - 1),
33-
min(b_min, a_max),
34-
min(b_max, a_max)
17+
18+
-- Case 3: Ranges overlap, narrowing needed
19+
20+
-- TRUE branch (A < B):
21+
local a_min_true = a_min -- A's lower bound doesn't change
22+
local a_max_true = math.min(a_max, b_max - 1) -- A must be less than B's maximum
23+
local b_min_true = math.max(b_min, a_min + 1) -- B must be greater than A's minimum
24+
local b_max_true = b_max -- B's upper bound doesn't change
25+
26+
-- FALSE branch (A ≥ B):
27+
local a_min_false = math.max(a_min, b_min) -- A must be at least B's minimum
28+
local a_max_false = a_max -- A's upper bound doesn't change
29+
local b_min_false = b_min -- B's lower bound doesn't change
30+
local b_max_false = math.min(b_max, a_max) -- B can't exceed A's maximum
31+
32+
return a_min_true, a_max_true, b_min_true, b_max_true,
33+
a_min_false, a_max_false, b_min_false, b_max_false
3534
elseif operator == ">" then
36-
if a_min > b_min and a_min > b_max and a_max > b_min and a_min > b_max then
37-
return max(a_min, b_max), max(a_max, b_max + 1), nil, nil
35+
-- Return value structure:
36+
-- a_min_true, a_max_true, b_min_true, b_max_true, a_min_false, a_max_false, b_min_false, b_max_false
37+
38+
-- Case 1: A > B is always true (A's lower bound > B's upper bound)
39+
if a_min > b_max then
40+
return a_min, a_max, b_min, b_max, nil, nil, nil, nil
3841
end
39-
40-
if a_min <= b_min and a_min <= b_max and a_max <= b_min and a_min <= b_max then
41-
return nil, nil, max(b_min, a_max), max(b_max, a_max)
42+
43+
-- Case 2: A > B is always false (A's upper bound ≤ B's lower bound)
44+
if a_max <= b_min then
45+
return nil, nil, nil, nil, a_min, a_max, b_min, b_max
4246
end
43-
44-
return max(a_min, b_min + 1),
45-
max(a_max, b_min),
46-
max(b_min, a_min),
47-
max(b_max, b_min)
47+
48+
-- Case 3: Ranges overlap, narrowing needed
49+
50+
-- TRUE branch (A > B):
51+
local a_min_true = math.max(a_min, b_min + 1) -- A must be greater than B's minimum
52+
local a_max_true = a_max -- A's upper bound doesn't change
53+
local b_min_true = b_min -- B's lower bound doesn't change
54+
local b_max_true = math.min(b_max, a_max - 1) -- B must be less than A's maximum
55+
56+
-- FALSE branch (A ≤ B):
57+
local a_min_false = a_min -- A's lower bound doesn't change
58+
local a_max_false = math.min(a_max, b_max) -- A can't exceed B's maximum
59+
local b_min_false = math.max(b_min, a_min) -- B must be at least A's minimum
60+
local b_max_false = b_max -- B's upper bound doesn't change
61+
62+
return a_min_true, a_max_true, b_min_true, b_max_true,
63+
a_min_false, a_max_false, b_min_false, b_max_false
4864
elseif operator == "<=" then
49-
if a_min <= b_min and a_min <= b_max and a_max <= b_min and a_min <= b_max then
50-
return min(a_min, b_max), min(a_max, b_max), nil, nil
65+
-- Case 1: A <= B is always true (A's upper bound <= B's lower bound)
66+
if a_max <= b_min then
67+
return a_min, a_max, b_min, b_max, nil, nil, nil, nil
5168
end
52-
53-
if a_min > b_min and a_min > b_max and a_max > b_min and a_min > b_max then
54-
return nil, nil, min(b_min, a_max), min(b_max, a_max)
69+
70+
-- Case 2: A <= B is always false (A's lower bound > B's upper bound)
71+
if a_min > b_max then
72+
return nil, nil, nil, nil, a_min, a_max, b_min, b_max
5573
end
56-
57-
return min(a_min, b_max),
58-
min(a_max, b_max),
59-
min(b_min, a_max),
60-
min(b_max, a_max - 1)
74+
75+
-- Case 3: Ranges overlap, narrowing needed
76+
77+
-- TRUE branch (A <= B):
78+
local a_min_true = a_min -- A's lower bound doesn't change
79+
local a_max_true = math.min(a_max, b_max) -- A must be less than or equal to B's maximum
80+
local b_min_true = math.max(b_min, a_min) -- B must be at least A's minimum
81+
local b_max_true = b_max -- B's upper bound doesn't change
82+
83+
-- FALSE branch (A > B):
84+
local a_min_false = math.max(a_min, b_max + 1) -- A must be greater than B's maximum
85+
local a_max_false = a_max -- A's upper bound doesn't change
86+
local b_min_false = b_min -- B's lower bound doesn't change
87+
local b_max_false = math.min(b_max, a_min - 1) -- B must be less than A's minimum
88+
89+
return a_min_true, a_max_true, b_min_true, b_max_true,
90+
a_min_false, a_max_false, b_min_false, b_max_false
6191
elseif operator == ">=" then
62-
if a_min >= b_min and a_min >= b_max and a_max >= b_min and a_min >= b_max then
63-
return max(a_min, b_max), max(a_max, b_max), nil, nil
92+
-- Case 1: A >= B is always true (A's lower bound >= B's upper bound)
93+
if a_min >= b_max then
94+
return a_min, a_max, b_min, b_max, nil, nil, nil, nil
6495
end
65-
66-
if a_min < b_min and a_min < b_max and a_max < b_min and a_min < b_max then
67-
return nil, nil, max(b_min, a_max), max(b_max, a_max)
96+
97+
-- Case 2: A >= B is always false (A's upper bound < B's lower bound)
98+
if a_max < b_min then
99+
return nil, nil, nil, nil, a_min, a_max, b_min, b_max
68100
end
69-
70-
return max(a_min, b_min),
71-
max(a_max, b_min),
72-
max(b_min, a_min + 1),
73-
max(b_max, b_min)
101+
102+
-- Case 3: Ranges overlap, narrowing needed
103+
104+
-- TRUE branch (A >= B):
105+
local a_min_true = math.max(a_min, b_min) -- A must be at least B's minimum
106+
local a_max_true = a_max -- A's upper bound doesn't change
107+
local b_min_true = b_min -- B's lower bound doesn't change
108+
local b_max_true = math.min(b_max, a_max) -- B can't exceed A's maximum
109+
110+
-- FALSE branch (A < B):
111+
local a_min_false = a_min -- A's lower bound doesn't change
112+
local a_max_false = math.min(a_max, b_min - 1) -- A must be less than B's minimum
113+
local b_min_false = math.max(b_min, a_max + 1) -- B must be greater than A's maximum
114+
local b_max_false = b_max -- B's upper bound doesn't change
115+
116+
return a_min_true, a_max_true, b_min_true, b_max_true,
117+
a_min_false, a_max_false, b_min_false, b_max_false
74118
elseif operator == "==" then
75-
if a_max < b_min or b_max < a_min then return nil, nil, b_min, b_max end
76-
77-
if a_min == a_max and b_min == b_max and a_min == b_max then
78-
return a_min, a_max, nil, nil
119+
-- Case 1: A == B is impossible (non-overlapping ranges)
120+
if a_max < b_min or b_max < a_min then
121+
return nil, nil, nil, nil, a_min, a_max, b_min, b_max
79122
end
80123

81-
if a_min <= b_max and b_min <= a_max then
82-
if a_min == a_max and min(a_max, b_max) == b_max then
83-
return max(a_min, b_min), min(a_max, b_max), b_min, b_max - 1
84-
end
124+
-- Case 2: A == B is always true (both are the same single value)
125+
if a_min == a_max and b_min == b_max and a_min == b_min then
126+
return a_min, a_max, b_min, b_max, nil, nil, nil, nil
127+
end
85128

86-
if a_min == a_max and max(a_min, b_min) == b_min then
87-
return max(a_min, b_min), min(a_max, b_max), b_min + 1, b_max
129+
-- Case 3: Ranges overlap, narrowing needed
130+
131+
-- TRUE branch (A == B): intersection of the two ranges
132+
local a_min_true = math.max(a_min, b_min)
133+
local a_max_true = math.min(a_max, b_max)
134+
local b_min_true = a_min_true -- For equality, the ranges must have the same values
135+
local b_max_true = a_max_true
136+
137+
-- FALSE branch (A != B): all values where they don't overlap
138+
local a_min_false1 = a_min
139+
local a_max_false1 = math.min(a_max, b_min - 1)
140+
local a_min_false2 = math.max(a_min, b_max + 1)
141+
local a_max_false2 = a_max
142+
143+
local b_min_false1 = b_min
144+
local b_max_false1 = math.min(b_max, a_min - 1)
145+
local b_min_false2 = math.max(b_min, a_max + 1)
146+
local b_max_false2 = b_max
147+
148+
-- Combine the false ranges if possible
149+
local a_min_false, a_max_false
150+
if a_max_false1 >= a_min_false1 and a_max_false2 >= a_min_false2 then
151+
a_min_false = math.min(a_min_false1, a_min_false2)
152+
a_max_false = math.max(a_max_false1, a_max_false2)
153+
elseif a_max_false1 >= a_min_false1 then
154+
a_min_false = a_min_false1
155+
a_max_false = a_max_false1
156+
elseif a_max_false2 >= a_min_false2 then
157+
a_min_false = a_min_false2
158+
a_max_false = a_max_false2
159+
else
160+
a_min_false = nil
161+
a_max_false = nil
162+
end
163+
164+
local b_min_false, b_max_false
165+
if b_max_false1 >= b_min_false1 and b_max_false2 >= b_min_false2 then
166+
b_min_false = math.min(b_min_false1, b_min_false2)
167+
b_max_false = math.max(b_max_false1, b_max_false2)
168+
elseif b_max_false1 >= b_min_false1 then
169+
b_min_false = b_min_false1
170+
b_max_false = b_max_false1
171+
elseif b_max_false2 >= b_min_false2 then
172+
b_min_false = b_min_false2
173+
b_max_false = b_max_false2
174+
else
175+
b_min_false = nil
176+
b_max_false = nil
88177
end
89178

90-
return max(a_min, b_min), min(a_max, b_max), b_min, b_max
91-
end
179+
return a_min_true, a_max_true, b_min_true, b_max_true,
180+
a_min_false, a_max_false, b_min_false, b_max_false
92181
elseif operator == "~=" then
93-
local x, y, z, w = intersect(b_min, b_max, "==", a_min, a_max)
94-
return z, w, x, y
182+
-- Implement ~= as the logical inverse of ==
183+
local a_min_true, a_max_true, b_min_true, b_max_true, a_min_false, a_max_false, b_min_false, b_max_false =
184+
intersect(a_min, a_max, "==", b_min, b_max)
185+
186+
-- Swap the true and false branches to represent the logical NOT
187+
return a_min_false, a_max_false, b_min_false, b_max_false, a_min_true, a_max_true, b_min_true, b_max_true
95188
end
96189
end
97190

98-
local function intersect_comparison(a--[[#: NumericType]], b--[[#: NumericType]], operator--[[#: keysof<|operators|>]])--[[#: NumericType | nil,NumericType | nil]]
191+
local function intersect_comparison(a--[[#: NumericType]], b--[[#: NumericType]], operator--[[#: string]])--[[#: NumericType | nil,NumericType | nil]]
99192
-- TODO: not sure if this makes sense
100193
if a:IsNan() or b:IsNan() then return a, b end
101194

@@ -104,26 +197,41 @@ local function intersect_comparison(a--[[#: NumericType]], b--[[#: NumericType]]
104197
local a_max = a.Type == "range" and a:GetMax() or a.Data or not a.Data and math.huge or a_min
105198
local b_min = b.Type == "range" and b:GetMin() or b.Data or b.Data or -math.huge
106199
local b_max = b.Type == "range" and b:GetMax() or b.Data or not b.Data and math.huge or b_min
107-
local a_min_res, a_max_res, b_min_res, b_max_res = intersect(a_min, a_max, operator, b_min, b_max)
108-
local result_a, result_b
200+
local a_min_res_true, a_max_res_true, b_min_res_true, b_max_res_true, a_min_res_false, a_max_res_false, b_min_res_false, b_max_res_false = intersect(a_min, a_max, operator, b_min, b_max)
201+
local result_a_true, result_a_false, result_b_true, result_b_false
109202

110-
if a_min_res and a_max_res then
111-
if a_min_res == a_max_res then
112-
result_a = LNumber(a_min_res)
203+
if a_min_res_true and a_max_res_true then
204+
if a_min_res_true == a_max_res_true then
205+
result_a_true = LNumber(a_min_res_true)
206+
else
207+
result_a_true = LNumberRange(a_min_res_true, a_max_res_true)
208+
end
209+
end
210+
211+
if a_min_res_false and a_max_res_false then
212+
if a_min_res_false == a_max_res_false then
213+
result_a_false = LNumber(a_min_res_false)
214+
else
215+
result_a_false = LNumberRange(a_min_res_false, a_max_res_false)
216+
end
217+
end
218+
if b_min_res_true and b_max_res_true then
219+
if b_min_res_true == b_max_res_true then
220+
result_b_true = LNumber(b_min_res_true)
113221
else
114-
result_a = LNumberRange(a_min_res, a_max_res)
222+
result_b_true = LNumberRange(b_min_res_true, b_max_res_true)
115223
end
116224
end
117225

118-
if b_min_res and b_max_res then
119-
if b_min_res == b_max_res then
120-
result_b = LNumber(b_min_res)
226+
if b_min_res_false and b_max_res_false then
227+
if b_min_res_false == b_max_res_false then
228+
result_b_false = LNumber(b_min_res_false)
121229
else
122-
result_b = LNumberRange(b_min_res, b_max_res)
230+
result_b_false = LNumberRange(b_min_res_false, b_max_res_false)
123231
end
124232
end
125233

126-
return result_a, result_b
234+
return result_a_true, result_a_false, result_b_true, result_b_false
127235
end
128236

129237
return intersect_comparison

nattlua/analyzer/operators/binary.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ local intersect_comparison = require("nattlua.analyzer.intersect_comparison")
148148
local function number_comparison(self, l, r, op, invert)
149149
local nl, nr = intersect_comparison(l, r, op, invert)
150150

151-
if nl and not l:Equal(nl) then self:TrackUpvalueUnion(l, nl, nr) end
151+
if nl and nr then self:TrackUpvalueUnion(l, nl, nr) end
152152

153-
if nr and not r:Equal(nr) then self:TrackUpvalueUnion(r, nr, nl) end
153+
if nr and nl then self:TrackUpvalueUnion(r, nr, nl) end
154154

155155
if nl and nr then
156156
if nl:IsNan() or nr:IsNan() then

nattlua/definitions/lua/table.nlua

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ analyzer function table.insert(tbl: List<|any|>, ...: ...any)
5151
else
5252
pos = analyzer:GetArrayLengthFromTable(tbl)
5353
end
54-
5554
local contract = tbl:GetContract()
5655

5756
if contract then

0 commit comments

Comments
 (0)