-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -- This file is based on Lua 5.x tests -- https://github.com/lua/lua/tree/master/testes print('testing metatables') local unpack = table.unpack assert(getmetatable{} == nil) assert(getmetatable(4) == nil) assert(getmetatable(nil) == nil) a={}; setmetatable(a, {__metatable = "xuxu", __tostring=function(x) return x.name end}) assert(getmetatable(a) == "xuxu") ud=newproxy(true); getmetatable(ud).__metatable = "xuxu" assert(getmetatable(ud) == "xuxu") assert(pcall(getmetatable) == false) assert(pcall(function() return getmetatable() end) == false) assert(select(2, pcall(getmetatable, {})) == nil) assert(select(2, pcall(getmetatable, ud)) == "xuxu") local res,err = pcall(tostring, a) assert(not res and err == "'__tostring' must return a string") -- cannot change a protected metatable assert(pcall(setmetatable, a, {}) == false) a.name = "gororoba" assert(tostring(a) == "gororoba") local a, t = {10,20,30; x="10", y="20"}, {} assert(setmetatable(a,t) == a) assert(getmetatable(a) == t) assert(setmetatable(a,nil) == a) assert(getmetatable(a) == nil) assert(setmetatable(a,t) == a) function f (t, i, e) assert(not e) local p = rawget(t, "parent") return (p and p[i]+3), "dummy return" end t.__index = f a.parent = {z=25, x=12, [4] = 24} assert(a[1] == 10 and a.z == 28 and a[4] == 27 and a.x == "10") collectgarbage() a = setmetatable({}, t) function f(t, i, v) rawset(t, i, v-3) end t.__newindex = f a[1] = 30; a.x = "101"; a[5] = 200 assert(a[1] == 27 and a.x == 98 and a[5] == 197) local c = {} a = setmetatable({}, t) t.__newindex = c a[1] = 10; a[2] = 20; a[3] = 90 assert(c[1] == 10 and c[2] == 20 and c[3] == 90) do local a; a = setmetatable({}, {__index = setmetatable({}, {__index = setmetatable({}, {__index = function (_,n) return a[n-3]+4, "lixo" end})})}) a[0] = 20 for i=0,10 do assert(a[i*3] == 20 + i*4) end end do -- newindex local foi local a = {} for i=1,10 do a[i] = 0; a['a'..i] = 0; end setmetatable(a, {__newindex = function (t,k,v) foi=true; rawset(t,k,v) end}) foi = false; a[1]=0; assert(not foi) foi = false; a['a1']=0; assert(not foi) foi = false; a['a11']=0; assert(foi) foi = false; a[11]=0; assert(foi) foi = false; a[1]=nil; assert(not foi) foi = false; a[1]=nil; assert(foi) end function f (t, ...) return t, {...} end t.__call = f do local x,y = a(unpack{'a', 1}) assert(x==a and y[1]=='a' and y[2]==1 and y[3]==nil) x,y = a() assert(x==a and y[1]==nil) end local b = setmetatable({}, t) setmetatable(b,t) function f(op) return function (...) cap = {[0] = op, ...} ; return (...) end end t.__add = f("add") t.__sub = f("sub") t.__mul = f("mul") t.__div = f("div") t.__mod = f("mod") t.__unm = f("unm") t.__pow = f("pow") assert(b+5 == b) assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==nil) assert(b+'5' == b) assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==nil) assert(5+b == 5) assert(cap[0] == "add" and cap[1] == 5 and cap[2] == b and cap[3]==nil) assert('5'+b == '5') assert(cap[0] == "add" and cap[1] == '5' and cap[2] == b and cap[3]==nil) b=b-3; assert(getmetatable(b) == t) assert(5-a == 5) assert(cap[0] == "sub" and cap[1] == 5 and cap[2] == a and cap[3]==nil) assert('5'-a == '5') assert(cap[0] == "sub" and cap[1] == '5' and cap[2] == a and cap[3]==nil) assert(a*a == a) assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==nil) assert(a/0 == a) assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==nil) assert(a%2 == a) assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==nil) assert(-a == a) assert(cap[0] == "unm" and cap[1] == a) assert(a^4 == a) assert(cap[0] == "pow" and cap[1] == a and cap[2] == 4 and cap[3]==nil) assert(a^'4' == a) assert(cap[0] == "pow" and cap[1] == a and cap[2] == '4' and cap[3]==nil) assert(4^a == 4) assert(cap[0] == "pow" and cap[1] == 4 and cap[2] == a and cap[3]==nil) assert('4'^a == '4') assert(cap[0] == "pow" and cap[1] == '4' and cap[2] == a and cap[3]==nil) t = {} t.__lt = function (a,b,c) collectgarbage() assert(c == nil) if type(a) == 'table' then a = a.x end if type(b) == 'table' then b = b.x end return aOp(1)) and not(Op(1)>Op(2)) and (Op(2)>Op(1))) assert(not(Op('a')>Op('a')) and not(Op('a')>Op('b')) and (Op('b')>Op('a'))) assert((Op(1)>=Op(1)) and not(Op(1)>=Op(2)) and (Op(2)>=Op(1))) assert((Op('a')>=Op('a')) and not(Op('a')>=Op('b')) and (Op('b')>=Op('a'))) end test() t.__le = function (a,b,c) assert(c == nil) if type(a) == 'table' then a = a.x end if type(b) == 'table' then b = b.x end return a<=b, "dummy" end test() -- retest comparisons, now using both `lt' and `le' -- test `partial order' local function Set(x) local y = {} for _,k in pairs(x) do y[k] = 1 end return setmetatable(y, t) end t.__lt = function (a,b) for k in pairs(a) do if not b[k] then return false end b[k] = nil end return next(b) ~= nil end t.__le = nil assert(Set{1,2,3} < Set{1,2,3,4}) assert(not(Set{1,2,3,4} < Set{1,2,3,4})) assert((Set{1,2,3,4} <= Set{1,2,3,4})) assert((Set{1,2,3,4} >= Set{1,2,3,4})) assert((Set{1,3} <= Set{3,5})) -- wrong!! model needs a `le' method ;-) t.__le = function (a,b) for k in pairs(a) do if not b[k] then return false end end return true end assert(not (Set{1,3} <= Set{3,5})) -- now its OK! assert(not(Set{1,3} <= Set{3,5})) assert(not(Set{1,3} >= Set{3,5})) t.__eq = function (a,b) for k in pairs(a) do if not b[k] then return false end b[k] = nil end return next(b) == nil end local s = Set{1,3,5} assert(s == Set{3,5,1}) assert(not rawequal(s, Set{3,5,1})) assert(rawequal(s, s)) assert(Set{1,3,5,1} == Set{3,5,1}) assert(Set{1,3,5} ~= Set{3,5,1,6}) t[Set{1,3,5}] = 1 assert(t[Set{1,3,5}] == nil) -- `__eq' is not valid for table accesses t.__concat = function (a,b,c) assert(c == nil) if type(a) == 'table' then a = a.val end if type(b) == 'table' then b = b.val end if A then return a..b else return setmetatable({val=a..b}, t) end end c = {val="c"}; setmetatable(c, t) d = {val="d"}; setmetatable(d, t) A = true assert(c..d == 'cd') assert(0 .."a".."b"..c..d.."e".."f"..(5+3).."g" == "0abcdef8g") A = false x = c..d assert(getmetatable(x) == t and x.val == 'cd') x = 0 .."a".."b"..c..d.."e".."f".."g" assert(x.val == "0abcdefg") -- test comparison compatibilities local t1, t2, c, d t1 = {}; c = {}; setmetatable(c, t1) d = {} t1.__eq = function () return true end t1.__lt = function () return true end assert(c ~= d and not pcall(function () return c < d end)) setmetatable(d, t1) assert(c == d and c < d and not(d <= c)) t2 = {} t2.__eq = t1.__eq t2.__lt = t1.__lt setmetatable(d, t2) assert(c == d and c < d and not(d <= c)) -- test for several levels of calls local i local tt = { __call = function (t, ...) i = i+1 if t.f then return t.f(...) else return {...} end end } local a = setmetatable({}, tt) local b = setmetatable({f=a}, tt) local c = setmetatable({f=b}, tt) i = 0 x = c(3,4,5) assert(i == 3 and x[1] == 3 and x[3] == 5) print'+' -- testing proxies assert(getmetatable(newproxy()) == nil) assert(getmetatable(newproxy(false)) == nil) assert(getmetatable(newproxy(nil)) == nil) local u = newproxy(true) getmetatable(u).__newindex = function (u,k,v) getmetatable(u)[k] = v end getmetatable(u).__index = function (u,k) return getmetatable(u)[k] end for i=1,10 do u[i] = i end for i=1,10 do assert(u[i] == i) end -- local k = newproxy(u) -- assert(getmetatable(k) == getmetatable(u)) a = {} rawset(a, "x", 1, 2, 3) assert(a.x == 1 and rawget(a, "x", 3) == 1) print '+' --[[ -- testing metatables for basic types mt = {} debug.setmetatable(10, mt) assert(getmetatable(-2) == mt) mt.__index = function (a,b) return a+b end assert((10)[3] == 13) assert((10)["3"] == 13) debug.setmetatable(23, nil) assert(getmetatable(-2) == nil) debug.setmetatable(true, mt) assert(getmetatable(false) == mt) mt.__index = function (a,b) return a or b end assert((true)[false] == true) assert((false)[false] == false) debug.setmetatable(false, nil) assert(getmetatable(true) == nil) debug.setmetatable(nil, mt) assert(getmetatable(nil) == mt) mt.__add = function (a,b) return (a or 0) + (b or 0) end assert(10 + nil == 10) assert(nil + 23 == 23) assert(nil + nil == 0) debug.setmetatable(nil, nil) assert(getmetatable(nil) == nil) debug.setmetatable(nil, {}) ]]-- do -- == must not do ref equality for tables and userdata in presence of __eq local t = {} local u = newproxy(true) -- print() returns nil which is converted to false setmetatable(t, { __eq = print }) getmetatable(u).__eq = print assert(t ~= t) assert(u ~= u) end do -- verify that internal mt flags are set correctly after two table assignments local mt = { {}, -- mixed metatable __index = {X = true}, } local t = setmetatable({}, mt) assert(t.X) -- fails if table flags are set incorrectly end do -- verify __len behavior & error handling local t = {1} setmetatable(t, {}) assert(#t == 1) setmetatable(t, { __len = rawlen }) assert(#t == 1) setmetatable(t, { __len = function() return 42 end }) assert(#t == 42) setmetatable(t, { __len = 42 }) local ok, err = pcall(function() return #t end) assert(not ok and err:match("attempt to call a number value")) setmetatable(t, { __len = function() end }) local ok, err = pcall(function() return #t end) assert(not ok and err:match("'__len' must return a number")) setmetatable(t, { __len = error }) local ok, err = pcall(function() return #t end) assert(not ok and err == t) end -- verify rawlen behavior do local t = {1} setmetatable(t, { __len = 42 }) assert(rawlen(t) == 1) assert(rawlen("foo") == 3) local ok, err = pcall(function() return rawlen(42) end) assert(not ok and err:match("table or string expected")) end -- verify that NaN/nil keys are passed to __newindex even though table assignment with them anywhere in the chain fails do assert(pcall(function() local t = {} t[nil] = 5 end) == false) assert(pcall(function() local t = {} setmetatable(t, { __newindex = {} }) t[nil] = 5 end) == false) assert(pcall(function() local t = {} setmetatable(t, { __newindex = function() end }) t[nil] = 5 end) == true) assert(pcall(function() local t = {} t[0/0] = 5 end) == false) assert(pcall(function() local t = {} setmetatable(t, { __newindex = {} }) t[0/0] = 5 end) == false) assert(pcall(function() local t = {} setmetatable(t, { __newindex = function() end }) t[0/0] = 5 end) == true) end -- verify that __newindex gets called for frozen tables but only if the assignment is to a key absent from the table do local ni = {} local t = table.create(2) t[1] = 42 -- t[2] is semantically absent with storage allocated for it t.a = 1 t.b = 2 t.b = nil -- this sets 'b' value to nil but leaves key as is to exercise more internal paths -- no observable behavior change expected between b and other absent keys setmetatable(t, { __newindex = function(_, k, v) assert(v == 42) table.insert(ni, k) end }) table.freeze(t) -- "redundant" combinations are there to test all three of SETTABLEN/SETTABLEKS/SETTABLE assert(pcall(function() t.a = 42 end) == false) assert(pcall(function() t[1] = 42 end) == false) assert(pcall(function() local key key = "a" t[key] = 42 end) == false) assert(pcall(function() local key key = 1 t[key] = 42 end) == false) -- now repeat the same for keys absent from the table: b (semantically absent), c (physically absent), 2 (semantically absent), 3 (physically absent) assert(pcall(function() t.b = 42 end) == true) assert(pcall(function() t.c = 42 end) == true) assert(pcall(function() local key key = "b" t[key] = 42 end) == true) assert(pcall(function() local key key = "c" t[key] = 42 end) == true) assert(pcall(function() t[2] = 42 end) == true) assert(pcall(function() t[3] = 42 end) == true) assert(pcall(function() local key key = 2 t[key] = 42 end) == true) assert(pcall(function() local key key = 3 t[key] = 42 end) == true) -- validate the assignment sequence local ei = { "b", "c", "b", "c", 2, 3, 2, 3 } assert(#ni == #ei) for k,v in ni do assert(ei[k] == v) end end function testfenv() X = 20; B = 30 local _G = getfenv() setfenv(1, setmetatable({}, {__index=_G})) X = X+10 assert(X == 30 and _G.X == 20) B = false assert(B == false) B = nil assert(B == 30) assert(_G.X == 20) assert(_G == getfenv(0)) assert(pcall(getfenv, 10) == false) assert(pcall(setfenv, setfenv, {}) == false) end testfenv() -- DONT MOVE THIS LINE return 'OK'