Skip to content

Commit 3dc753c

Browse files
committed
Accept __lt and __le for > and >= operators.
As in Lua, "A comparison a > b is translated to b < a and a >= b is translated to b <= a."
1 parent 910375e commit 3dc753c

File tree

4 files changed

+434
-0
lines changed

4 files changed

+434
-0
lines changed

spec/metamethods/le_spec.lua

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
local util = require("spec.util")
2+
3+
describe("binary metamethod __le using <=", function()
4+
it("can be set on a record", util.check([[
5+
local type Rec = record
6+
x: number
7+
metamethod __call: function(Rec, string, number): string
8+
metamethod __le: function(Rec, Rec): boolean
9+
end
10+
11+
local rec_mt: metatable<Rec>
12+
rec_mt = {
13+
__call = function(self: Rec, s: string, n: number): string
14+
return tostring(self.x + n) .. s
15+
end,
16+
__le = function(a: Rec, b: Rec): boolean
17+
return a.x <= b.x
18+
end
19+
}
20+
21+
local r = setmetatable({ x = 10 } as Rec, rec_mt)
22+
local s = setmetatable({ x = 20 } as Rec, rec_mt)
23+
24+
if r <= s then
25+
print("yes")
26+
end
27+
]]))
28+
29+
it("can be used on a record prototype", util.check([[
30+
local record A
31+
value: number
32+
metamethod __call: function(A, number): A
33+
metamethod __le: function(A, A): boolean
34+
end
35+
local A_mt: metatable<A>
36+
A_mt = {
37+
__call = function(a: A, v: number): A
38+
return setmetatable({value = v} as A, A_mt)
39+
end,
40+
__le = function(a: A, b: A): boolean
41+
return a.value <= b.value
42+
end,
43+
}
44+
45+
A.value = 10
46+
if A <= A then
47+
print("wat!?")
48+
end
49+
]]))
50+
51+
it("can be used via the second argument", util.check([[
52+
local type Rec = record
53+
x: number
54+
metamethod __le: function(number, Rec): Rec
55+
end
56+
57+
local rec_mt: metatable<Rec>
58+
rec_mt = {
59+
__le = function(a: number, b: Rec): boolean
60+
return a <= b.x
61+
end
62+
}
63+
64+
local s = setmetatable({ y = 20 } as Rec, rec_mt)
65+
66+
if 10 <= s then
67+
print("yes")
68+
end
69+
]]))
70+
71+
it("preserves nominal type checking when resolving metamethods for operators", util.check_type_error([[
72+
local type Temperature = record
73+
n: number
74+
metamethod __le: function(t1: Temperature, t2: Temperature): boolean
75+
end
76+
77+
local type Date = record
78+
n: number
79+
metamethod __le: function(t1: Date, t2: Date): boolean
80+
end
81+
82+
local temp2: Temperature = { n = 45 }
83+
local birthday2 : Date = { n = 34 }
84+
85+
setmetatable(temp2, {
86+
__le = function(t1: Temperature, t2: Temperature): boolean
87+
return t1.n <= t2.n
88+
end,
89+
})
90+
91+
setmetatable(birthday2, {
92+
__le = function(t1: Date, t2: Date): boolean
93+
return t1.n <= t2.n
94+
end,
95+
})
96+
97+
if temp2 <= birthday2 then
98+
print("wat")
99+
end
100+
]], {
101+
{ y = 26, msg = "Date is not a Temperature" },
102+
}))
103+
end)
104+
105+
describe("binary metamethod __le using >=", function()
106+
it("can be set on a record", util.check([[
107+
local type Rec = record
108+
x: number
109+
metamethod __call: function(Rec, string, number): string
110+
metamethod __le: function(Rec, Rec): boolean
111+
end
112+
113+
local rec_mt: metatable<Rec>
114+
rec_mt = {
115+
__call = function(self: Rec, s: string, n: number): string
116+
return tostring(self.x + n) .. s
117+
end,
118+
__le = function(a: Rec, b: Rec): boolean
119+
return a.x <= b.x
120+
end
121+
}
122+
123+
local r = setmetatable({ x = 10 } as Rec, rec_mt)
124+
local s = setmetatable({ x = 20 } as Rec, rec_mt)
125+
126+
if s >= r then
127+
print("yes")
128+
end
129+
]]))
130+
131+
it("can be used on a record prototype", util.check([[
132+
local record A
133+
value: number
134+
metamethod __call: function(A, number): A
135+
metamethod __le: function(A, A): boolean
136+
end
137+
local A_mt: metatable<A>
138+
A_mt = {
139+
__call = function(a: A, v: number): A
140+
return setmetatable({value = v} as A, A_mt)
141+
end,
142+
__le = function(a: A, b: A): boolean
143+
return a.value <= b.value
144+
end,
145+
}
146+
147+
A.value = 10
148+
if A >= A then
149+
print("wat!?")
150+
end
151+
]]))
152+
153+
it("can be used via the second argument", util.check([[
154+
local type Rec = record
155+
x: number
156+
metamethod __le: function(number, Rec): Rec
157+
end
158+
159+
local rec_mt: metatable<Rec>
160+
rec_mt = {
161+
__le = function(a: number, b: Rec): boolean
162+
return a <= b.x
163+
end
164+
}
165+
166+
local s = setmetatable({ y = 20 } as Rec, rec_mt)
167+
168+
if s >= 10 then
169+
print("yes")
170+
end
171+
]]))
172+
173+
it("preserves nominal type checking when resolving metamethods for operators", util.check_type_error([[
174+
local type Temperature = record
175+
n: number
176+
metamethod __le: function(t1: Temperature, t2: Temperature): boolean
177+
end
178+
179+
local type Date = record
180+
n: number
181+
metamethod __le: function(t1: Date, t2: Date): boolean
182+
end
183+
184+
local temp2: Temperature = { n = 45 }
185+
local birthday2 : Date = { n = 34 }
186+
187+
setmetatable(temp2, {
188+
__le = function(t1: Temperature, t2: Temperature): boolean
189+
return t1.n <= t2.n
190+
end,
191+
})
192+
193+
setmetatable(birthday2, {
194+
__le = function(t1: Date, t2: Date): boolean
195+
return t1.n <= t2.n
196+
end,
197+
})
198+
199+
if birthday2 >= temp2 then
200+
print("wat")
201+
end
202+
]], {
203+
{ y = 26, msg = "Date is not a Temperature" },
204+
}))
205+
end)

0 commit comments

Comments
 (0)