Skip to content

Commit

Permalink
fix(callable): __call cannot be in a nested metatable (#489)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tieske authored Nov 7, 2024
1 parent e5e47e0 commit d2dc61b
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ deprecation policy.

see [CONTRIBUTING.md](CONTRIBUTING.md#release-instructions-for-a-new-version) for release instructions

## unreleased
- fix(types): callable would return false positive if `__call` was nested
[#489](https://github.com/lunarmodules/Penlight/pull/489)

## 1.14.0 (2024-Apr-15)
- fix(path): make `path.expanduser` more sturdy
[#469](https://github.com/lunarmodules/Penlight/pull/469)
Expand Down
20 changes: 16 additions & 4 deletions lua/pl/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@ local math_ceil = math.ceil
local assert_arg = utils.assert_arg
local types = {}

--- is the object either a function or a callable object?.
-- @param obj Object to check.
function types.is_callable (obj)
return type(obj) == 'function' or getmetatable(obj) and getmetatable(obj).__call and true
do
-- we prefer debug.getmetatable, but only if available
local gmt = (debug or {}).getmetatable or getmetatable

--- is the object either a function or a callable object?.
-- @param obj Object to check.
function types.is_callable (obj)
if type(obj) == 'function' then
return true
end
local mt = gmt(obj)
if not mt then
return false
end
return type(rawget(mt, "__call")) == "function"
end
end

--- is the object of the specified type?.
Expand Down
15 changes: 15 additions & 0 deletions tests/test-types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ asserteq(types.is_integer(-10.1),false)

asserteq(types.is_callable(asserteq),true)
asserteq(types.is_callable(List),true)
do
local mt = setmetatable({}, {
__index = {
__call = function() return "ok" end
}
})
asserteq(type(mt.__call), "function") -- __call is looked-up through another metatable
local nc = setmetatable({}, mt)
-- proof-of-pudding, let's call it. To verify Lua behaves the same on all engines
local success, result = pcall(function() return nc() end)
assert(result ~= "ok", "expected result to not be 'ok'")
asserteq(success, false)
-- real test now
asserteq(types.is_callable(nc), false) -- NOT callable, since __call is fetched using RAWget by Lua
end

asserteq(types.is_indexable(array),true)
asserteq(types.is_indexable('hello'),nil)
Expand Down

0 comments on commit d2dc61b

Please sign in to comment.