Skip to content

Commit 980acd2

Browse files
committed
add is_subcalss_of, BREAKING: enhance moon.type() to remove weird class object behavior
1 parent 9bff9ae commit 980acd2

4 files changed

Lines changed: 117 additions & 10 deletions

File tree

docs/standard_lib.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,24 +160,43 @@ inherits from `class`.
160160
class Parent
161161
class Child extends Parent
162162
163-
is_instance_of Child!, Parent -- true
164163
is_instance_of Child!, Child -- true
164+
is_instance_of Child!, Parent -- true (checks parent classes)
165165
is_instance_of Parent!, Child -- false
166166
```
167167

168+
To check if a value is a direct instance of a specific class without considering
169+
inheritance, use `type(value) == MyClass` instead.
170+
171+
### `is_subclass_of(cls, parent)`
172+
173+
Returns `true` if `cls` is a subclass of `parent`, `false` otherwise. Throws an
174+
error if `cls` is not a MoonScript class. Note that a class is not considered a
175+
subclass of itself. Walks the `__parent` chain starting from `cls` to check if
176+
any ancestor matches `parent`.
177+
178+
```moon
179+
class Parent
180+
class Child extends Parent
181+
182+
is_subclass_of Child, Parent -- true
183+
is_subclass_of Parent, Child -- false
184+
is_subclass_of Child, Child -- false
185+
```
186+
168187
### `type(value)`
169188

170-
If `value` is an instance of a MoonScript class, then return its class object.
171-
If `value` is a class table, return the class itself. Returns the result of
172-
calling Lua's built-in `type` for all other values, including `__base` tables
173-
and plain tables.
189+
Returns a class-aware type for a value. If `value` is an instance of a
190+
MoonScript class, returns its class object. If `value` is a class table, returns
191+
the string `"class"`. Returns the result of calling Lua's built-in `type` for
192+
all other values, including `__base` tables and plain tables.
174193

175194
```moon
176195
class MyClass
177196
178197
x = MyClass!
179198
assert type(x) == MyClass
180-
assert type(MyClass) == MyClass
199+
assert type(MyClass) == "class"
181200
assert type(MyClass.__base) == "table"
182201
```
183202

moon/init.lua

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ do
77
local _obj_0 = require("moonscript.util")
88
getfenv, setfenv, dump = _obj_0.getfenv, _obj_0.setfenv, _obj_0.dump
99
end
10-
local p, is_object, is_class, is_instance, is_instance_of, type, debug, run_with_scope, bind_methods, defaultbl, extend, copy, mixin, mixin_object, mixin_table, fold
10+
local p, is_object, is_class, is_instance, is_instance_of, is_subclass_of, type, debug, run_with_scope, bind_methods, defaultbl, extend, copy, mixin, mixin_object, mixin_table, fold
1111
p = function(o, ...)
1212
print(dump(o))
1313
if select("#", ...) > 0 then
@@ -45,9 +45,25 @@ is_instance_of = function(value, cls)
4545
end
4646
return false
4747
end
48+
is_subclass_of = function(cls, parent)
49+
if not (is_class(cls)) then
50+
error("is_subclass_of: expected class, got " .. tostring(lua.type(cls)))
51+
end
52+
local check = cls.__parent
53+
while check do
54+
if check == parent then
55+
return true
56+
end
57+
check = check.__parent
58+
end
59+
return false
60+
end
4861
type = function(value)
4962
local base_type = lua.type(value)
5063
if base_type == "table" then
64+
if is_class(value) then
65+
return "class"
66+
end
5167
local cls = value.__class
5268
if cls and rawget(value, "__class") == nil then
5369
return cls
@@ -195,6 +211,7 @@ return {
195211
is_class = is_class,
196212
is_instance = is_instance,
197213
is_instance_of = is_instance_of,
214+
is_subclass_of = is_subclass_of,
198215
type = type,
199216
debug = debug,
200217
run_with_scope = run_with_scope,

moon/init.moon

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,20 @@ is_instance_of = (value, cls) ->
3434
check = check.__parent
3535
false
3636

37+
is_subclass_of = (cls, parent) ->
38+
error "is_subclass_of: expected class, got #{lua.type cls}" unless is_class cls
39+
check = cls.__parent
40+
while check
41+
if check == parent
42+
return true
43+
check = check.__parent
44+
false
45+
3746
type = (value) -> -- class aware type
3847
base_type = lua.type value
3948
if base_type == "table"
49+
if is_class value
50+
return "class"
4051
cls = value.__class
4152
if cls and rawget(value, "__class") == nil
4253
return cls
@@ -153,6 +164,6 @@ fold = (items, fn)->
153164
items[1]
154165

155166
{
156-
:dump, :p, :is_object, :is_class, :is_instance, :is_instance_of, :type, :debug, :run_with_scope, :bind_methods,
167+
:dump, :p, :is_object, :is_class, :is_instance, :is_instance_of, :is_subclass_of, :type, :debug, :run_with_scope, :bind_methods,
157168
:defaultbl, :extend, :copy, :mixin, :mixin_object, :mixin_table, :fold
158169
}

spec/moon_spec.moon

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ describe "moon", ->
99
moon = require "moon"
1010

1111
describe "type", ->
12-
it "returns the class for a class", ->
12+
it "returns 'class' for a class", ->
1313
class Test
14-
assert.equal Test, moon.type Test
14+
assert.equal "class", moon.type Test
1515

1616
it "returns the class for an instance", ->
1717
class Test
@@ -38,6 +38,12 @@ describe "moon", ->
3838
assert.equal "table", moon.type {}
3939
assert.equal "table", moon.type {hello: "world"}
4040

41+
it "returns 'class' for classes with inheritance", ->
42+
class Parent
43+
class Child extends Parent
44+
assert.equal "class", moon.type Parent
45+
assert.equal "class", moon.type Child
46+
4147
it "works with inheritance", ->
4248
class Parent
4349
class Child extends Parent
@@ -307,6 +313,60 @@ describe "moon", ->
307313
assert.falsy moon.is_instance_of A!, B
308314
assert.falsy moon.is_instance_of A!, C
309315

316+
describe "is_subclass_of", ->
317+
it "returns true for direct child", ->
318+
class Parent
319+
class Child extends Parent
320+
assert.truthy moon.is_subclass_of Child, Parent
321+
322+
it "returns true for deep inheritance", ->
323+
class A
324+
class B extends A
325+
class C extends B
326+
assert.truthy moon.is_subclass_of C, A
327+
assert.truthy moon.is_subclass_of C, B
328+
assert.truthy moon.is_subclass_of B, A
329+
330+
it "returns false for same class", ->
331+
class A
332+
assert.falsy moon.is_subclass_of A, A
333+
334+
it "returns false for parent checked against child", ->
335+
class Parent
336+
class Child extends Parent
337+
assert.falsy moon.is_subclass_of Parent, Child
338+
339+
it "returns false for unrelated classes", ->
340+
class A
341+
class B
342+
assert.falsy moon.is_subclass_of A, B
343+
assert.falsy moon.is_subclass_of B, A
344+
345+
it "returns false for class without parent", ->
346+
class A
347+
class B
348+
assert.falsy moon.is_subclass_of A, B
349+
350+
it "returns false when __base is passed as the parent", ->
351+
class Parent
352+
class Child extends Parent
353+
assert.falsy moon.is_subclass_of Child, Parent.__base
354+
assert.falsy moon.is_subclass_of Child, Child.__base
355+
356+
it "errors when first argument is not a class", ->
357+
class Hello
358+
assert.has_error (-> moon.is_subclass_of Hello!, Hello), "is_subclass_of: expected class, got table"
359+
assert.has_error (-> moon.is_subclass_of Hello.__base, Hello), "is_subclass_of: expected class, got table"
360+
assert.has_error (-> moon.is_subclass_of {}, Hello), "is_subclass_of: expected class, got table"
361+
assert.has_error (-> moon.is_subclass_of nil, Hello), "is_subclass_of: expected class, got nil"
362+
assert.has_error (-> moon.is_subclass_of 123, Hello), "is_subclass_of: expected class, got number"
363+
364+
it "errors when __base is passed as the first argument", ->
365+
class Parent
366+
class Child extends Parent
367+
assert.has_error (-> moon.is_subclass_of Parent.__base, Parent), "is_subclass_of: expected class, got table"
368+
assert.has_error (-> moon.is_subclass_of Child.__base, Child), "is_subclass_of: expected class, got table"
369+
310370
it "should fold", ->
311371
numbers = {4,3,5,6,7,2,3}
312372
sum = moon.fold numbers, (a,b) -> a + b

0 commit comments

Comments
 (0)