Skip to content

Commit 898a311

Browse files
committed
tweak the is_instance and is_class functions, add more tests
1 parent 5164ab0 commit 898a311

File tree

6 files changed

+85
-9
lines changed

6 files changed

+85
-9
lines changed

docs/standard_lib.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ Returns `true` if `value` is a MoonScript class table, `false` otherwise.
118118
Returns `false` for instances, `__base` tables, plain tables, and non-table
119119
values.
120120

121+
Works by checking that the value is a table with a `__base` field set directly
122+
on it, and that its metatable has a `__call` field (the constructor). Both are
123+
properties unique to MoonScript class tables.
124+
121125
```moon
122126
class MyClass
123127
@@ -132,6 +136,10 @@ Returns `true` if `value` is an instance of a MoonScript class, `false`
132136
otherwise. Returns `false` for class tables, `__base` tables, plain tables, and
133137
non-table values.
134138

139+
Works by checking that the value is a table whose metatable is a `__base` table
140+
(identified by having a self-referencing `__index`), and that the value itself
141+
is not a `__base` table.
142+
135143
```moon
136144
class MyClass
137145

moon/init.lua

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@ p = function(o, ...)
1414
return p(...)
1515
end
1616
end
17-
is_object = function(value) -- deprecated: use is_instance or is_class instead
17+
is_object = function(value)
1818
return lua.type(value) == "table" and value.__class
1919
end
2020
is_class = function(value)
21-
return lua.type(value) == "table" and rawget(value, "__base") ~= nil
21+
if lua.type(value) == "table" and rawget(value, "__base") ~= nil then
22+
local mt = getmetatable(value)
23+
return mt and rawget(mt, "__call") ~= nil
24+
end
25+
return false
2226
end
2327
is_instance = function(value)
2428
if lua.type(value) == "table" then
2529
local mt = getmetatable(value)
26-
return mt and rawget(mt, "__class") ~= nil
30+
return mt and rawget(mt, "__index") == mt and rawget(value, "__index") ~= value
2731
end
2832
return false
2933
end

moon/init.moon

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ is_object = (value) -> -- deprecated: use is_instance or is_class instead
1313
lua.type(value) == "table" and value.__class
1414

1515
is_class = (value) ->
16-
lua.type(value) == "table" and rawget(value, "__base") != nil
16+
if lua.type(value) == "table" and rawget(value, "__base") != nil
17+
mt = getmetatable value
18+
return mt and rawget(mt, "__call") != nil
19+
false
1720

1821
is_instance = (value) ->
1922
if lua.type(value) == "table"
2023
mt = getmetatable value
21-
return mt and rawget(mt, "__class") != nil
24+
return mt and rawget(mt, "__index") == mt and rawget(value, "__index") != value
2225
false
2326

2427
type = (value) -> -- class aware type

moonscript/util.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ local unpack = unpack or table.unpack
44
local type = type
55
local moon = {
66
is_class = function(value)
7-
return type(value) == "table" and rawget(value, "__base") ~= nil
7+
if type(value) == "table" and rawget(value, "__base") ~= nil then
8+
local mt = getmetatable(value)
9+
return mt and rawget(mt, "__call") ~= nil
10+
end
11+
return false
812
end,
913
is_instance = function(value)
1014
if type(value) == "table" then
1115
local mt = getmetatable(value)
12-
return mt and rawget(mt, "__class") ~= nil
16+
return mt and rawget(mt, "__index") == mt and rawget(value, "__index") ~= value
1317
end
1418
return false
1519
end,

moonscript/util.moon

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ type = type
66

77
moon = {
88
is_class: (value) ->
9-
type(value) == "table" and rawget(value, "__base") != nil
9+
if type(value) == "table" and rawget(value, "__base") != nil
10+
mt = getmetatable value
11+
return mt and rawget(mt, "__call") != nil
12+
false
1013

1114
is_instance: (value) ->
1215
if type(value) == "table"
1316
mt = getmetatable value
14-
return mt and rawget(mt, "__class") != nil
17+
return mt and rawget(mt, "__index") == mt and rawget(value, "__index") != value
1518
false
1619

1720
is_a: (thing, t) ->

spec/moon_spec.moon

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ describe "moon", ->
2121
class Test
2222
assert.equal "table", moon.type Test.__base
2323

24+
it "returns 'table' for __base with inheritance", ->
25+
class Parent
26+
class Child extends Parent
27+
assert.equal "table", moon.type Child.__base
28+
assert.equal "table", moon.type Parent.__base
29+
2430
it "returns primitive type for non-tables", ->
2531
assert.equal "number", moon.type 1
2632
assert.equal "boolean", moon.type true
@@ -149,6 +155,12 @@ describe "moon", ->
149155
class Hello
150156
assert.falsy moon.is_class Hello.__base
151157

158+
it "returns false for __base with inheritance", ->
159+
class Parent
160+
class Child extends Parent
161+
assert.falsy moon.is_class Child.__base
162+
assert.falsy moon.is_class Parent.__base
163+
152164
it "returns false for plain tables and non-tables", ->
153165
assert.falsy moon.is_class {}
154166
assert.falsy moon.is_class 123
@@ -163,6 +175,42 @@ describe "moon", ->
163175
assert.truthy moon.is_class Child
164176
assert.falsy moon.is_class Child!
165177

178+
describe "is_instance and is_class with imposter tables", ->
179+
it "rejects table with only __base set", ->
180+
fake = { __base: {} }
181+
assert.falsy moon.is_class fake
182+
assert.falsy moon.is_instance fake
183+
184+
it "rejects table with __base and non-callable metatable", ->
185+
fake = setmetatable { __base: {} }, { __index: {} }
186+
assert.falsy moon.is_class fake
187+
assert.falsy moon.is_instance fake
188+
189+
it "rejects table with self-referencing __index but no metatable", ->
190+
fake = {}
191+
fake.__index = fake
192+
assert.falsy moon.is_class fake
193+
assert.falsy moon.is_instance fake
194+
195+
it "rejects table with __class set directly", ->
196+
fake = { __class: {} }
197+
assert.falsy moon.is_class fake
198+
assert.falsy moon.is_instance fake
199+
200+
it "rejects table whose metatable has __class but not self-referencing __index", ->
201+
mt = { __class: {} }
202+
fake = setmetatable {}, mt
203+
assert.falsy moon.is_class fake
204+
assert.falsy moon.is_instance fake
205+
206+
it "rejects table with self-referencing __index used as its own metatable", ->
207+
-- looks like a __base used as a metatable for itself
208+
fake = {}
209+
fake.__index = fake
210+
setmetatable fake, fake
211+
assert.falsy moon.is_class fake
212+
assert.falsy moon.is_instance fake
213+
166214
describe "is_instance", ->
167215
it "returns true for an instance", ->
168216
class Hello
@@ -176,6 +224,12 @@ describe "moon", ->
176224
class Hello
177225
assert.falsy moon.is_instance Hello.__base
178226

227+
it "returns false for __base with inheritance", ->
228+
class Parent
229+
class Child extends Parent
230+
assert.falsy moon.is_instance Child.__base
231+
assert.falsy moon.is_instance Parent.__base
232+
179233
it "returns false for plain tables and non-tables", ->
180234
assert.falsy moon.is_instance {}
181235
assert.falsy moon.is_instance 123

0 commit comments

Comments
 (0)