mirror of
https://github.com/lua/lua
synced 2024-11-26 06:39:41 +03:00
e8c779736f
The mechanism of "caching the last closure created for a prototype to try to reuse it the next time a closure for that prototype is created" was removed. There are several reasons: - It is hard to find a natural example where this cache has a measurable impact on performance. - Programmers already perceive closure creation as something slow, so they tend to avoid it inside hot paths. (Any case where the cache could reuse a closure can be rewritten predefining the closure in some variable and using that variable.) - The implementation was somewhat complex, due to a bad interaction with the generational collector. (Typically, new closures are new, while prototypes are old. So, the cache breaks the invariant that old objects should not point to new ones.)
271 lines
5.6 KiB
Lua
271 lines
5.6 KiB
Lua
-- $Id: testes/closure.lua $
|
|
-- See Copyright Notice in file all.lua
|
|
|
|
print "testing closures"
|
|
|
|
local A,B = 0,{g=10}
|
|
function f(x)
|
|
local a = {}
|
|
for i=1,1000 do
|
|
local y = 0
|
|
do
|
|
a[i] = function () B.g = B.g+1; y = y+x; return y+A end
|
|
end
|
|
end
|
|
local dummy = function () return a[A] end
|
|
collectgarbage()
|
|
A = 1; assert(dummy() == a[1]); A = 0;
|
|
assert(a[1]() == x)
|
|
assert(a[3]() == x)
|
|
collectgarbage()
|
|
assert(B.g == 12)
|
|
return a
|
|
end
|
|
|
|
local a = f(10)
|
|
-- force a GC in this level
|
|
local x = {[1] = {}} -- to detect a GC
|
|
setmetatable(x, {__mode = 'kv'})
|
|
while x[1] do -- repeat until GC
|
|
local a = A..A..A..A -- create garbage
|
|
A = A+1
|
|
end
|
|
assert(a[1]() == 20+A)
|
|
assert(a[1]() == 30+A)
|
|
assert(a[2]() == 10+A)
|
|
collectgarbage()
|
|
assert(a[2]() == 20+A)
|
|
assert(a[2]() == 30+A)
|
|
assert(a[3]() == 20+A)
|
|
assert(a[8]() == 10+A)
|
|
assert(getmetatable(x).__mode == 'kv')
|
|
assert(B.g == 19)
|
|
|
|
|
|
-- testing equality
|
|
a = {}
|
|
|
|
for i = 1, 5 do a[i] = function (x) return i + a + _ENV end end
|
|
assert(a[3] ~= a[4] and a[4] ~= a[5])
|
|
|
|
do
|
|
local a = function (x) return math.sin(_ENV[x]) end
|
|
local function f()
|
|
return a
|
|
end
|
|
assert(f() == f())
|
|
end
|
|
|
|
|
|
-- testing closures with 'for' control variable
|
|
a = {}
|
|
for i=1,10 do
|
|
a[i] = {set = function(x) i=x end, get = function () return i end}
|
|
if i == 3 then break end
|
|
end
|
|
assert(a[4] == undef)
|
|
a[1].set(10)
|
|
assert(a[2].get() == 2)
|
|
a[2].set('a')
|
|
assert(a[3].get() == 3)
|
|
assert(a[2].get() == 'a')
|
|
|
|
a = {}
|
|
local t = {"a", "b"}
|
|
for i = 1, #t do
|
|
local k = t[i]
|
|
a[i] = {set = function(x, y) i=x; k=y end,
|
|
get = function () return i, k end}
|
|
if i == 2 then break end
|
|
end
|
|
a[1].set(10, 20)
|
|
local r,s = a[2].get()
|
|
assert(r == 2 and s == 'b')
|
|
r,s = a[1].get()
|
|
assert(r == 10 and s == 20)
|
|
a[2].set('a', 'b')
|
|
r,s = a[2].get()
|
|
assert(r == "a" and s == "b")
|
|
|
|
|
|
-- testing closures with 'for' control variable x break
|
|
for i=1,3 do
|
|
f = function () return i end
|
|
break
|
|
end
|
|
assert(f() == 1)
|
|
|
|
for k = 1, #t do
|
|
local v = t[k]
|
|
f = function () return k, v end
|
|
break
|
|
end
|
|
assert(({f()})[1] == 1)
|
|
assert(({f()})[2] == "a")
|
|
|
|
|
|
-- testing closure x break x return x errors
|
|
|
|
local b
|
|
function f(x)
|
|
local first = 1
|
|
while 1 do
|
|
if x == 3 and not first then return end
|
|
local a = 'xuxu'
|
|
b = function (op, y)
|
|
if op == 'set' then
|
|
a = x+y
|
|
else
|
|
return a
|
|
end
|
|
end
|
|
if x == 1 then do break end
|
|
elseif x == 2 then return
|
|
else if x ~= 3 then error() end
|
|
end
|
|
first = nil
|
|
end
|
|
end
|
|
|
|
for i=1,3 do
|
|
f(i)
|
|
assert(b('get') == 'xuxu')
|
|
b('set', 10); assert(b('get') == 10+i)
|
|
b = nil
|
|
end
|
|
|
|
pcall(f, 4);
|
|
assert(b('get') == 'xuxu')
|
|
b('set', 10); assert(b('get') == 14)
|
|
|
|
|
|
local w
|
|
-- testing multi-level closure
|
|
function f(x)
|
|
return function (y)
|
|
return function (z) return w+x+y+z end
|
|
end
|
|
end
|
|
|
|
y = f(10)
|
|
w = 1.345
|
|
assert(y(20)(30) == 60+w)
|
|
|
|
|
|
-- testing closures x break
|
|
do
|
|
local X, Y
|
|
local a = math.sin(0)
|
|
|
|
while a do
|
|
local b = 10
|
|
X = function () return b end -- closure with upvalue
|
|
if a then break end
|
|
end
|
|
|
|
do
|
|
local b = 20
|
|
Y = function () return b end -- closure with upvalue
|
|
end
|
|
|
|
-- upvalues must be different
|
|
assert(X() == 10 and Y() == 20)
|
|
end
|
|
|
|
|
|
-- testing closures x repeat-until
|
|
|
|
local a = {}
|
|
local i = 1
|
|
repeat
|
|
local x = i
|
|
a[i] = function () i = x+1; return x end
|
|
until i > 10 or a[i]() ~= x
|
|
assert(i == 11 and a[1]() == 1 and a[3]() == 3 and i == 4)
|
|
|
|
|
|
-- testing closures created in 'then' and 'else' parts of 'if's
|
|
a = {}
|
|
for i = 1, 10 do
|
|
if i % 3 == 0 then
|
|
local y = 0
|
|
a[i] = function (x) local t = y; y = x; return t end
|
|
elseif i % 3 == 1 then
|
|
goto L1
|
|
error'not here'
|
|
::L1::
|
|
local y = 1
|
|
a[i] = function (x) local t = y; y = x; return t end
|
|
elseif i % 3 == 2 then
|
|
local t
|
|
goto l4
|
|
::l4a:: a[i] = t; goto l4b
|
|
error("should never be here!")
|
|
::l4::
|
|
local y = 2
|
|
t = function (x) local t = y; y = x; return t end
|
|
goto l4a
|
|
error("should never be here!")
|
|
::l4b::
|
|
end
|
|
end
|
|
|
|
for i = 1, 10 do
|
|
assert(a[i](i * 10) == i % 3 and a[i]() == i * 10)
|
|
end
|
|
|
|
print'+'
|
|
|
|
|
|
-- test for correctly closing upvalues in tail calls of vararg functions
|
|
local function t ()
|
|
local function c(a,b) assert(a=="test" and b=="OK") end
|
|
local function v(f, ...) c("test", f() ~= 1 and "FAILED" or "OK") end
|
|
local x = 1
|
|
return v(function() return x end)
|
|
end
|
|
t()
|
|
|
|
|
|
-- test for debug manipulation of upvalues
|
|
local debug = require'debug'
|
|
|
|
do
|
|
local a , b, c = 3, 5, 7
|
|
foo1 = function () return a+b end;
|
|
foo2 = function () return b+a end;
|
|
do
|
|
local a = 10
|
|
foo3 = function () return a+b end;
|
|
end
|
|
end
|
|
|
|
assert(debug.upvalueid(foo1, 1))
|
|
assert(debug.upvalueid(foo1, 2))
|
|
assert(not pcall(debug.upvalueid, foo1, 3))
|
|
assert(debug.upvalueid(foo1, 1) == debug.upvalueid(foo2, 2))
|
|
assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo2, 1))
|
|
assert(debug.upvalueid(foo3, 1))
|
|
assert(debug.upvalueid(foo1, 1) ~= debug.upvalueid(foo3, 1))
|
|
assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo3, 2))
|
|
|
|
assert(debug.upvalueid(string.gmatch("x", "x"), 1) ~= nil)
|
|
|
|
assert(foo1() == 3 + 5 and foo2() == 5 + 3)
|
|
debug.upvaluejoin(foo1, 2, foo2, 2)
|
|
assert(foo1() == 3 + 3 and foo2() == 5 + 3)
|
|
assert(foo3() == 10 + 5)
|
|
debug.upvaluejoin(foo3, 2, foo2, 1)
|
|
assert(foo3() == 10 + 5)
|
|
debug.upvaluejoin(foo3, 2, foo2, 2)
|
|
assert(foo3() == 10 + 3)
|
|
|
|
assert(not pcall(debug.upvaluejoin, foo1, 3, foo2, 1))
|
|
assert(not pcall(debug.upvaluejoin, foo1, 1, foo2, 3))
|
|
assert(not pcall(debug.upvaluejoin, foo1, 0, foo2, 1))
|
|
assert(not pcall(debug.upvaluejoin, print, 1, foo2, 1))
|
|
assert(not pcall(debug.upvaluejoin, {}, 1, foo2, 1))
|
|
assert(not pcall(debug.upvaluejoin, foo1, 1, print, 1))
|
|
|
|
print'OK'
|