From 8c8a91f2ef7acccb99e3737913faad8d48b39571 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 24 Aug 2018 10:17:54 -0300 Subject: [PATCH] Deprecated the emulation of '__le' using '__lt' As hinted in the manual for Lua 5.3, the emulation of the metamethod for '__le' using '__le' has been deprecated. It is slow, complicates the logic, and it is easy to avoid this emulation by defining a proper '__le' function. Moreover, often this emulation was used wrongly, with a programmer assuming that an order is total when it is not (e.g., NaN in floating-point numbers). --- lstate.h | 8 +++++--- ltests.h | 1 + ltm.c | 2 ++ luaconf.h | 8 +++++++- lvm.c | 2 ++ manual/manual.of | 21 +++++++++------------ testes/coroutine.lua | 8 +++----- testes/events.lua | 34 +++++++++++++++------------------- 8 files changed, 44 insertions(+), 40 deletions(-) diff --git a/lstate.h b/lstate.h index 2a36bd96..5461b291 100644 --- a/lstate.h +++ b/lstate.h @@ -138,9 +138,11 @@ typedef struct CallInfo { #define CIST_YPCALL (1<<3) /* call is a yieldable protected call */ #define CIST_TAIL (1<<4) /* call was tail called */ #define CIST_HOOKYIELD (1<<5) /* last hook called yielded */ -#define CIST_LEQ (1<<6) /* using __lt for __le */ -#define CIST_FIN (1<<7) /* call is running a finalizer */ -#define CIST_TRAN (1<<8) /* 'ci' has transfer information */ +#define CIST_FIN (1<<6) /* call is running a finalizer */ +#define CIST_TRAN (1<<7) /* 'ci' has transfer information */ +#if defined(LUA_COMPAT_LT_LE) +#define CIST_LEQ (1<<8) /* using __lt for __le */ +#endif /* active function is a Lua function */ #define isLua(ci) (!((ci)->callstatus & CIST_C)) diff --git a/ltests.h b/ltests.h index 54bc4f5f..d44974a4 100644 --- a/ltests.h +++ b/ltests.h @@ -13,6 +13,7 @@ /* test Lua with compatibility code */ #define LUA_COMPAT_MATHLIB +#define LUA_COMPAT_LT_LE #define LUA_DEBUG diff --git a/ltm.c b/ltm.c index 1c1a18b7..5c148180 100644 --- a/ltm.c +++ b/ltm.c @@ -188,6 +188,7 @@ int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2, TMS event) { if (callbinTM(L, p1, p2, L->top, event)) /* try original event */ return !l_isfalse(s2v(L->top)); +#if defined(LUA_COMPAT_LT_LE) else if (event == TM_LE) { /* try '!(p2 < p1)' for '(p1 <= p2)' */ L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ @@ -197,6 +198,7 @@ int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2, } /* else error will remove this 'ci'; no need to clear mark */ } +#endif luaG_ordererror(L, p1, p2); /* no metamethod found */ return 0; /* to avoid warnings */ } diff --git a/luaconf.h b/luaconf.h index cc8e1bdc..126257dc 100644 --- a/luaconf.h +++ b/luaconf.h @@ -295,7 +295,7 @@ */ /* -@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.2. +@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3. ** You can define it to get all options, or change specific options ** to fit your specific needs. */ @@ -316,6 +316,12 @@ */ #define LUA_COMPAT_APIINTCASTS +/* +@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod +** using '__lt'. +*/ +#define LUA_COMPAT_LT_LE + #endif /* } */ diff --git a/lvm.c b/lvm.c index 7550dcc8..add26942 100644 --- a/lvm.c +++ b/lvm.c @@ -754,10 +754,12 @@ void luaV_finishOp (lua_State *L) { case OP_EQ: { /* note that 'OP_EQI'/'OP_EQK' cannot yield */ int res = !l_isfalse(s2v(L->top - 1)); L->top--; +#if defined(LUA_COMPAT_LT_LE) if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ ci->callstatus ^= CIST_LEQ; /* clear mark */ res = !res; /* negate result */ } +#endif lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); if (res != GETARG_k(inst)) /* condition failed? */ ci->u.l.savedpc++; /* skip jump instruction */ diff --git a/manual/manual.of b/manual/manual.of index 5a8b1b2c..47a551bf 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -474,17 +474,7 @@ The result of the call is always converted to a boolean. @item{@idx{__le}| the less equal (@T{<=}) operation. -Unlike other operations, -the less-equal operation can use two different events. -First, Lua looks for the @idx{__le} metamethod in both operands, -like in the less than operation. -If it cannot find such a metamethod, -then it will try the @idx{__lt} metamethod, -assuming that @T{a <= b} is equivalent to @T{not (b < a)}. -As with the other comparison operators, -the result is always a boolean. -(This use of the @idx{__lt} event can be removed in future versions; -it is also slower than a real @idx{__le} metamethod.) +Behavior similar to the less than operation. } @item{@idx{__index}| @@ -1643,7 +1633,8 @@ all operations @emphx{wrap around}, according to the usual rules of two-complement arithmetic. (In other words, they return the unique representable integer -that is equal modulo @M{2@sp{64}} to the mathematical result.) +that is equal modulo @M{2@sp{n}} to the mathematical result, +where @M{n} is the number of bits of the integer type.) } @sect3{bitwise| @title{Bitwise Operators} @@ -8537,6 +8528,12 @@ For instance, the result of @T{"1" + "2"} now is an integer, not a float. } +@item{ +The use of the @idx{__lt} metamethod to emulate @id{__le} +has been removed. +When needed, this metamethod must be explicitly defined. +} + } } diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 182c1e18..36eae44a 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -1,4 +1,4 @@ --- $Id: testes/coroutine.lua $ +-- $Id: testes/coroutine.lua 2018-07-25 15:31:04 -0300 $ -- See Copyright Notice in file all.lua print "testing coroutines" @@ -619,10 +619,8 @@ end assert(run(function () if (a>=b) then return '>=' else return '<' end end, {"le", "sub"}) == "<") --- '<=' using '<' -mt.__le = nil assert(run(function () if (a<=b) then return '<=' else return '>' end end, - {"lt"}) == "<=") + {"le", "sub"}) == "<=") assert(run(function () if (a==b) then return '==' else return '~=' end end, {"eq"}) == "~=") @@ -677,7 +675,7 @@ do -- a few more tests for comparsion operators return val(a) < val(b) end, } - local mt2 = { __lt = mt1.__lt } -- no __le + local mt2 = { __lt = mt1.__lt, __le = mt1.__le } local function run (f) local co = coroutine.wrap(f) diff --git a/testes/events.lua b/testes/events.lua index 21a822a7..c4d43d51 100644 --- a/testes/events.lua +++ b/testes/events.lua @@ -1,4 +1,4 @@ --- $Id: testes/events.lua $ +-- $Id: testes/events.lua 2018-07-25 15:31:04 -0300 $ -- See Copyright Notice in file all.lua print('testing metatables') @@ -217,6 +217,13 @@ t.__lt = function (a,b,c) return a= 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 @@ -281,10 +271,15 @@ t.__le = function (a,b) return true end -assert(not (Set{1,3} <= Set{3,5})) -- now its OK! +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(not (Set{1,3} <= Set{3,5})) 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 @@ -376,6 +371,7 @@ t1 = {}; c = {}; setmetatable(c, t1) d = {} t1.__eq = function () return true end t1.__lt = function () return true end +t1.__le = function () return false end setmetatable(d, t1) assert(c == d and c < d and not(d <= c)) t2 = {}