Collective declaration for globals ('global *')

This commit is contained in:
Roberto Ierusalimschy
2025-05-13 11:43:10 -03:00
parent 7dc6aae290
commit 3b9dd52be0
14 changed files with 156 additions and 64 deletions

View File

@@ -405,7 +405,12 @@ static int searchvar (FuncState *fs, TString *n, expdesc *var) {
int i; int i;
for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) {
Vardesc *vd = getlocalvardesc(fs, i); Vardesc *vd = getlocalvardesc(fs, i);
if (eqstr(n, vd->vd.name)) { /* found? */ if (vd->vd.name == NULL) { /* 'global *'? */
if (var->u.info == -1) { /* no previous collective declaration? */
var->u.info = fs->firstlocal + i; /* will use this one as default */
}
}
else if (eqstr(n, vd->vd.name)) { /* found? */
if (vd->vd.kind == RDKCTC) /* compile-time constant? */ if (vd->vd.kind == RDKCTC) /* compile-time constant? */
init_exp(var, VCONST, fs->firstlocal + i); init_exp(var, VCONST, fs->firstlocal + i);
else if (vd->vd.kind == GDKREG || vd->vd.kind == GDKCONST) else if (vd->vd.kind == GDKREG || vd->vd.kind == GDKCONST)
@@ -449,18 +454,16 @@ static void marktobeclosed (FuncState *fs) {
** 'var' as 'void' as a flag. ** 'var' as 'void' as a flag.
*/ */
static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
int v = searchvar(fs, n, var); /* look up locals at current level */ int v = searchvar(fs, n, var); /* look up variables at current level */
if (v >= 0) { /* found? */ if (v >= 0) { /* found? */
if (v == VLOCAL && !base) if (v == VLOCAL && !base)
markupval(fs, var->u.var.vidx); /* local will be used as an upval */ markupval(fs, var->u.var.vidx); /* local will be used as an upval */
} }
else { /* not found as local at current level; try upvalues */ else { /* not found at current level; try upvalues */
int idx = searchupvalue(fs, n); /* try existing upvalues */ int idx = searchupvalue(fs, n); /* try existing upvalues */
if (idx < 0) { /* not found? */ if (idx < 0) { /* not found? */
if (fs->prev != NULL) /* more levels? */ if (fs->prev != NULL) /* more levels? */
singlevaraux(fs->prev, n, var, 0); /* try upper levels */ singlevaraux(fs->prev, n, var, 0); /* try upper levels */
else /* no more levels */
init_exp(var, VGLOBAL, -1); /* global by default */
if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */ if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */
idx = newupvalue(fs, n, var); /* will be a new upvalue */ idx = newupvalue(fs, n, var); /* will be a new upvalue */
else /* it is a global or a constant */ else /* it is a global or a constant */
@@ -477,6 +480,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
*/ */
static void buildvar (LexState *ls, TString *varname, expdesc *var) { static void buildvar (LexState *ls, TString *varname, expdesc *var) {
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
init_exp(var, VGLOBAL, -1); /* global by default */
singlevaraux(fs, varname, var, 1); singlevaraux(fs, varname, var, 1);
if (var->k == VGLOBAL) { /* global name? */ if (var->k == VGLOBAL) { /* global name? */
expdesc key; expdesc key;
@@ -1796,20 +1800,33 @@ static void localstat (LexState *ls) {
} }
static void globalstat (LexState *ls) { static lu_byte getglobalattribute (LexState *ls) {
/* globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */
FuncState *fs = ls->fs;
do {
TString *vname = str_checkname(ls);
lu_byte kind = getvarattribute(ls); lu_byte kind = getvarattribute(ls);
if (kind == RDKTOCLOSE) if (kind == RDKTOCLOSE)
luaK_semerror(ls, "global variable ('%s') cannot be to-be-closed", luaK_semerror(ls, "global variables cannot be to-be-closed");
getstr(vname));
/* adjust kind for global variable */ /* adjust kind for global variable */
kind = (kind == VDKREG) ? GDKREG : GDKCONST; return (kind == VDKREG) ? GDKREG : GDKCONST;
}
static void globalstat (LexState *ls) {
/* globalstat -> (GLOBAL) '*' attrib
globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */
FuncState *fs = ls->fs;
if (testnext(ls, '*')) {
lu_byte kind = getglobalattribute(ls);
/* use NULL as name to represent '*' entries */
new_varkind(ls, NULL, kind);
fs->nactvar++; /* activate declaration */
}
else {
do {
TString *vname = str_checkname(ls);
lu_byte kind = getglobalattribute(ls);
new_varkind(ls, vname, kind); new_varkind(ls, vname, kind);
fs->nactvar++; /* activate declaration */ fs->nactvar++; /* activate declaration */
} while (testnext(ls, ',')); } while (testnext(ls, ','));
}
} }
@@ -1983,10 +2000,10 @@ static void statement (LexState *ls) {
case TK_NAME: { case TK_NAME: {
/* compatibility code to parse global keyword when "global" /* compatibility code to parse global keyword when "global"
is not reserved */ is not reserved */
if (eqstr(ls->t.seminfo.ts, luaS_newliteral(ls->L, "global"))) { if (strcmp(getstr(ls->t.seminfo.ts), "global") == 0) {
int lk = luaX_lookahead(ls); int lk = luaX_lookahead(ls);
if (lk == TK_NAME || lk == TK_FUNCTION) { if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) {
/* 'global <name>' or 'global function' */ /* 'global <name>' or 'global *' or 'global function' */
globalstatfunc(ls, line); globalstatfunc(ls, line);
break; break;
} }

View File

@@ -223,14 +223,15 @@ a function's formal parameter is equivalent to a local variable.)
All chunks start with an implicit declaration @T{global *}, All chunks start with an implicit declaration @T{global *},
which declares all free names as global variables; which declares all free names as global variables;
this implicit declaration becomes void inside the scope of any other this preambular declaration becomes void inside the scope of any other
@Rw{global} declaration, regardless of the names being declared. @Rw{global} declaration,
as the following example illustrates:
@verbatim{ @verbatim{
X = 1 -- Ok, global by default X = 1 -- Ok, global by default
do do
global Y -- voids implicit initial declaration global Y -- voids implicit initial declaration
X = 1 -- ERROR, X not declared
Y = 1 -- Ok, Y declared as global Y = 1 -- Ok, Y declared as global
X = 1 -- ERROR, X not declared
end end
X = 2 -- Ok, global by default again X = 2 -- Ok, global by default again
} }
@@ -1110,9 +1111,9 @@ and cannot be used as names:
@index{reserved words} @index{reserved words}
@verbatim{ @verbatim{
and break do else elseif end and break do else elseif end
false for function goto if in false for function global goto if
local nil not or repeat return in local nil not or repeat
then true until while return then true until while
} }
Lua is a case-sensitive language: Lua is a case-sensitive language:
@@ -1653,7 +1654,8 @@ The declaration for locals can include an initialization:
@producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} @producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}}
@producname{stat}@producbody{@Rw{global} attnamelist} @producname{stat}@producbody{@Rw{global} attnamelist}
@producname{attnamelist}@producbody{ @producname{attnamelist}@producbody{
@bnfNter{Name} attrib @bnfrep{@bnfter{,} @bnfNter{Name} attrib}} @bnfNter{Name} @bnfopt{attrib}
@bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}}
} }
If present, an initial assignment has the same semantics If present, an initial assignment has the same semantics
of a multiple assignment @see{assignment}. of a multiple assignment @see{assignment}.
@@ -1662,24 +1664,55 @@ Otherwise, all local variables are initialized with @nil.
Each variable name may be postfixed by an attribute Each variable name may be postfixed by an attribute
(a name between angle brackets): (a name between angle brackets):
@Produc{ @Produc{
@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} @producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}}
} }
There are two possible attributes: There are two possible attributes:
@id{const}, which declares a @emph{constant} or @emph{read-only} variable, @id{const}, which declares a @emph{constant} or @emph{read-only} variable,
@index{constant variable} @index{constant variable}
that is, a variable that cannot be assigned to that is, a variable that cannot be used as the left-hand side of an
after its initialization; assignment,
and @id{close}, which declares a to-be-closed variable @see{to-be-closed}. and @id{close}, which declares a to-be-closed variable @see{to-be-closed}.
A list of variables can contain at most one to-be-closed variable. A list of variables can contain at most one to-be-closed variable.
Only local variables can have the @id{close} attribute. Only local variables can have the @id{close} attribute.
Note that, for global variables, Lua offers also a collective declaration for global variables:
the @emph{read-only} atribute is only a syntactical restriction: @Produc{
@producname{stat}@producbody{@Rw{global} @bnfter{*} @bnfopt{attrib}}
}
This special form implicitly declares
as globals all names not explicitly declared previously.
In particular,
@T{global * <const>} implicitly declares
as read-only globals all names not explicitly declared previously;
see the following example:
@verbatim{ @verbatim{
global X <const> global X
global * <const>
print(math.pi) -- Ok, 'print' and 'math' are read-only
X = 1 -- Ok, declared as read-write
Y = 1 -- Error, Y is read-only
}
As noted in @See{globalenv},
all chunks start with an implicit declaration @T{global *},
but this preambular declaration becomes void inside
the scope of any other @Rw{global} declaration.
Therefore, a program that does not use global declarations
or start with @T{global *}
has free read-write access to any global;
a program that starts with @T{global * <const>}
has free read-only access to any global;
and a program that starts with any other global declaration
(e.g., @T{global none}) can only refer to declared variables.
Note that, for global variables,
the effect of any declaration is only syntactical:
@verbatim{
global X <const>, _G
X = 1 -- ERROR X = 1 -- ERROR
_ENV.X = 1 -- Ok _ENV.X = 1 -- Ok
foo() -- 'foo' can freely change the global X _G.print(X) -- Ok
foo() -- 'foo' can freely change any global
} }
A chunk is also a block @see{chunks}, A chunk is also a block @see{chunks},
@@ -9453,7 +9486,12 @@ change between versions.
@itemize{ @itemize{
@item{ @item{
The control variable in @Rw{for} loops are read only. The word @Rw{global} is a reserved word.
Do not use it as a regular name.
}
@item{
The control variable in @Rw{for} loops is read only.
If you need to change it, If you need to change it,
declare a local variable with the same name in the loop body. declare a local variable with the same name in the loop body.
} }
@@ -9582,12 +9620,14 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
@OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody
@OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist}
@OrNL @Rw{global} attnamelist @OrNL @Rw{global} attnamelist
@OrNL @Rw{global} @bnfter{*} @bnfopt{attrib}
} }
@producname{attnamelist}@producbody{ @producname{attnamelist}@producbody{
@bnfNter{Name} attrib @bnfrep{@bnfter{,} @bnfNter{Name} attrib}} @bnfNter{Name} @bnfopt{attrib}
@bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}}
@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} @producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}}
@producname{retstat}@producbody{@Rw{return} @producname{retstat}@producbody{@Rw{return}
@bnfopt{explist} @bnfopt{@bnfter{;}}} @bnfopt{explist} @bnfopt{@bnfter{;}}}

View File

@@ -2,6 +2,10 @@
-- $Id: testes/all.lua $ -- $Id: testes/all.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const>
global _soft, _port, _nomsg
global T
local version = "Lua 5.5" local version = "Lua 5.5"
if _VERSION ~= version then if _VERSION ~= version then
@@ -34,7 +38,7 @@ if usertests then
end end
-- tests should require debug when needed -- tests should require debug when needed
debug = nil global debug; debug = nil
if usertests then if usertests then
@@ -71,7 +75,7 @@ do -- (
-- track messages for tests not performed -- track messages for tests not performed
local msgs = {} local msgs = {}
function Message (m) global function Message (m)
if not _nomsg then if not _nomsg then
print(m) print(m)
msgs[#msgs+1] = string.sub(m, 3, -3) msgs[#msgs+1] = string.sub(m, 3, -3)

View File

@@ -1,6 +1,8 @@
-- $Id: testes/calls.lua $ -- $Id: testes/calls.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const>
print("testing functions and calls") print("testing functions and calls")
local debug = require "debug" local debug = require "debug"
@@ -22,7 +24,7 @@ assert(not pcall(type))
-- testing local-function recursion -- testing local-function recursion
fact = false global fact; fact = false
do do
local res = 1 local res = 1
local function fact (n) local function fact (n)
@@ -63,7 +65,7 @@ a.b.c:f2('k', 12); assert(a.b.c.k == 12)
print('+') print('+')
t = nil -- 'declare' t global t; t = nil -- 'declare' t
function f(a,b,c) local d = 'a'; t={a,b,c,d} end function f(a,b,c) local d = 'a'; t={a,b,c,d} end
f( -- this line change must be valid f( -- this line change must be valid
@@ -75,7 +77,7 @@ assert(t[1] == 1 and t[2] == 2 and t[3] == 3 and t[4] == 'a')
t = nil -- delete 't' t = nil -- delete 't'
function fat(x) global function fat(x)
if x <= 1 then return 1 if x <= 1 then return 1
else return x*load("return fat(" .. x-1 .. ")", "")() else return x*load("return fat(" .. x-1 .. ")", "")()
end end
@@ -107,7 +109,7 @@ end
_G.deep = nil -- "declaration" (used by 'all.lua') _G.deep = nil -- "declaration" (used by 'all.lua')
function deep (n) global function deep (n)
if n>0 then deep(n-1) end if n>0 then deep(n-1) end
end end
deep(10) deep(10)
@@ -352,7 +354,7 @@ assert(not load(function () return true end))
-- small bug -- small bug
local t = {nil, "return ", "3"} local t = {nil, "return ", "3"}
f, msg = load(function () return table.remove(t, 1) end) local f, msg = load(function () return table.remove(t, 1) end)
assert(f() == nil) -- should read the empty chunk assert(f() == nil) -- should read the empty chunk
-- another small bug (in 5.2.1) -- another small bug (in 5.2.1)
@@ -388,7 +390,8 @@ assert(load("return _ENV", nil, nil, 123)() == 123)
-- load when _ENV is not first upvalue -- load when _ENV is not first upvalue
local x; XX = 123 global XX; local x
XX = 123
local function h () local function h ()
local y=x -- use 'x', so that it becomes 1st upvalue local y=x -- use 'x', so that it becomes 1st upvalue
return XX -- global name return XX -- global name

View File

@@ -1,6 +1,8 @@
-- $Id: testes/closure.lua $ -- $Id: testes/closure.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const>
print "testing closures" print "testing closures"
do -- bug in 5.4.7 do -- bug in 5.4.7

View File

@@ -1,6 +1,8 @@
-- $Id: testes/code.lua $ -- $Id: testes/code.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const>
if T==nil then if T==nil then
(Message or print)('\n >>> testC not active: skipping opcode tests <<<\n') (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n')
return return
@@ -405,8 +407,8 @@ do -- tests for table access in upvalues
end end
-- de morgan -- de morgan
checkequal(function () local a; if not (a or b) then b=a end end, checkequal(function () local a, b; if not (a or b) then b=a end end,
function () local a; if (not a and not b) then b=a end end) function () local a, b; if (not a and not b) then b=a end end)
checkequal(function (l) local a; return 0 <= a and a <= l end, checkequal(function (l) local a; return 0 <= a and a <= l end,
function (l) local a; return not (not(a >= 0) or not(a <= l)) end) function (l) local a; return not (not(a >= 0) or not(a <= l)) end)

View File

@@ -1,6 +1,8 @@
-- $Id: testes/files.lua $ -- $Id: testes/files.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const>
local debug = require "debug" local debug = require "debug"
local maxint = math.maxinteger local maxint = math.maxinteger
@@ -838,13 +840,13 @@ assert(os.date("!\0\0") == "\0\0")
local x = string.rep("a", 10000) local x = string.rep("a", 10000)
assert(os.date(x) == x) assert(os.date(x) == x)
local t = os.time() local t = os.time()
D = os.date("*t", t) global D; D = os.date("*t", t)
assert(os.date(string.rep("%d", 1000), t) == assert(os.date(string.rep("%d", 1000), t) ==
string.rep(os.date("%d", t), 1000)) string.rep(os.date("%d", t), 1000))
assert(os.date(string.rep("%", 200)) == string.rep("%", 100)) assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
local function checkDateTable (t) local function checkDateTable (t)
_G.D = os.date("*t", t) D = os.date("*t", t)
assert(os.time(D) == t) assert(os.time(D) == t)
load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
D.hour==%H and D.min==%M and D.sec==%S and D.hour==%H and D.min==%M and D.sec==%S and

View File

@@ -1,6 +1,10 @@
-- $Id: testes/goto.lua $ -- $Id: testes/goto.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global require
global print, load, assert, string, setmetatable
global collectgarbage, error
print("testing goto and global declarations") print("testing goto and global declarations")
collectgarbage() collectgarbage()
@@ -254,6 +258,8 @@ assert(testG(5) == 10)
do -- test goto's around to-be-closed variable do -- test goto's around to-be-closed variable
global *
-- set 'var' and return an object that will reset 'var' when -- set 'var' and return an object that will reset 'var' when
-- it goes out of scope -- it goes out of scope
local function newobj (var) local function newobj (var)
@@ -265,16 +271,16 @@ do -- test goto's around to-be-closed variable
goto L1 goto L1
::L4:: assert(not X); goto L5 -- varX dead here ::L4:: assert(not varX); goto L5 -- varX dead here
::L1:: ::L1::
local varX <close> = newobj("X") local varX <close> = newobj("X")
assert(X); goto L2 -- varX alive here assert(varX); goto L2 -- varX alive here
::L3:: ::L3::
assert(X); goto L4 -- varX alive here assert(varX); goto L4 -- varX alive here
::L2:: assert(X); goto L3 -- varX alive here ::L2:: assert(varX); goto L3 -- varX alive here
::L5:: -- return ::L5:: -- return
end end
@@ -285,8 +291,7 @@ foo()
-------------------------------------------------------------------------- --------------------------------------------------------------------------
do do
global print, load, T<const>; global assert<const> global T<const>
global string
local function checkerr (code, err) local function checkerr (code, err)
local st, msg = load(code) local st, msg = load(code)
@@ -299,6 +304,7 @@ do
-- global variables cannot be to-be-closed -- global variables cannot be to-be-closed
checkerr("global X<close>", "cannot be") checkerr("global X<close>", "cannot be")
checkerr("global * <close>", "cannot be")
do do
local X = 10 local X = 10
@@ -350,6 +356,12 @@ do
end end
]], "%:2%:") -- correct line in error message ]], "%:2%:") -- correct line in error message
checkerr([[
global * <const>;
print(X) -- Ok to use
Y = 1 -- ERROR
]], "assign to const variable 'Y'")
end end
print'OK' print'OK'

View File

@@ -3,6 +3,8 @@
print('testing scanner') print('testing scanner')
global * <const>
local debug = require "debug" local debug = require "debug"

View File

@@ -1,6 +1,8 @@
-- $Id: testes/locals.lua $ -- $Id: testes/locals.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const>
print('testing local variables and environments') print('testing local variables and environments')
local debug = require"debug" local debug = require"debug"
@@ -39,9 +41,11 @@ f = nil
local f local f
local x = 1 local x = 1
a = nil do
load('local a = {}')() global a; a = nil
assert(a == nil) load('local a = {}')()
assert(a == nil)
end
function f (a) function f (a)
local _1, _2, _3, _4, _5 local _1, _2, _3, _4, _5
@@ -154,7 +158,7 @@ local _ENV = (function (...) return ... end)(_G, dummy) -- {
do local _ENV = {assert=assert}; assert(true) end do local _ENV = {assert=assert}; assert(true) end
local mt = {_G = _G} local mt = {_G = _G}
local foo,x local foo,x
A = false -- "declare" A global A; A = false -- "declare" A
do local _ENV = mt do local _ENV = mt
function foo (x) function foo (x)
A = x A = x

View File

@@ -1,6 +1,8 @@
-- $Id: testes/nextvar.lua $ -- $Id: testes/nextvar.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const>
print('testing tables, next, and for') print('testing tables, next, and for')
local function checkerror (msg, f, ...) local function checkerror (msg, f, ...)
@@ -345,9 +347,6 @@ end
local nofind = {} local nofind = {}
a,b,c = 1,2,3
a,b,c = nil
-- next uses always the same iteration function -- next uses always the same iteration function
assert(next{} == next{}) assert(next{} == next{})
@@ -396,7 +395,7 @@ for i=0,10000 do
end end
end end
n = {n=0} local n = {n=0}
for i,v in pairs(a) do for i,v in pairs(a) do
n.n = n.n+1 n.n = n.n+1
assert(i and v and a[i] == v) assert(i and v and a[i] == v)

View File

@@ -6,6 +6,8 @@
print('testing pattern matching') print('testing pattern matching')
global * <const>
local function checkerror (msg, f, ...) local function checkerror (msg, f, ...)
local s, err = pcall(f, ...) local s, err = pcall(f, ...)
assert(not s and string.find(err, msg)) assert(not s and string.find(err, msg))

View File

@@ -3,6 +3,7 @@
-- ISO Latin encoding -- ISO Latin encoding
global * <const>
print('testing strings and string library') print('testing strings and string library')

View File

@@ -3,6 +3,8 @@
-- UTF-8 file -- UTF-8 file
global * <const>
print "testing UTF-8 library" print "testing UTF-8 library"
local utf8 = require'utf8' local utf8 = require'utf8'