'require' returns where module was found

The function 'require' returns the *loader data* as a second result.
For file searchers, this data is the path where they found the module.
This commit is contained in:
Roberto Ierusalimschy 2019-04-17 14:57:29 -03:00
parent 2d3f095448
commit ed2872cd3b
4 changed files with 66 additions and 34 deletions

View File

@ -576,9 +576,14 @@ static int searcher_Croot (lua_State *L) {
static int searcher_preload (lua_State *L) {
const char *name = luaL_checkstring(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
if (lua_getfield(L, -1, name) == LUA_TNIL) /* not found? */
if (lua_getfield(L, -1, name) == LUA_TNIL) { /* not found? */
lua_pushfstring(L, "\n\tno field package.preload['%s']", name);
return 1;
return 1;
}
else {
lua_pushliteral(L, ":preload:");
return 2;
}
}
@ -620,17 +625,23 @@ static int ll_require (lua_State *L) {
/* else must load package */
lua_pop(L, 1); /* remove 'getfield' result */
findloader(L, name);
lua_pushstring(L, name); /* pass name as argument to module loader */
lua_insert(L, -2); /* name is 1st argument (before search data) */
lua_rotate(L, -2, 1); /* function <-> loader data */
lua_pushvalue(L, 1); /* name is 1st argument to module loader */
lua_pushvalue(L, -3); /* loader data is 2nd argument */
/* stack: ...; loader data; loader function; mod. name; loader data */
lua_call(L, 2, 1); /* run loader to load module */
/* stack: ...; loader data; result from loader */
if (!lua_isnil(L, -1)) /* non-nil return? */
lua_setfield(L, 2, name); /* LOADED[name] = returned value */
else
lua_pop(L, 1); /* pop nil */
if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */
lua_pushboolean(L, 1); /* use true as result */
lua_pushvalue(L, -1); /* extra copy to be returned */
lua_copy(L, -1, -2); /* replace loader result */
lua_setfield(L, 2, name); /* LOADED[name] = true */
}
return 1;
lua_rotate(L, -2, 1); /* loader data <-> module result */
return 2; /* return module result and loader data */
}
/* }====================================================== */

View File

@ -271,8 +271,8 @@ static void fchecksize (LoadState *S, size_t size, const char *tname) {
#define checksize(S,t) fchecksize(S,sizeof(t),#t)
static void checkHeader (LoadState *S) {
/* 1st char already checked */
checkliteral(S, LUA_SIGNATURE + 1, "not a binary chunk");
/* skip 1st char (already read and checked) */
checkliteral(S, &LUA_SIGNATURE[1], "not a binary chunk");
if (LoadInt(S) != LUAC_VERSION)
error(S, "version mismatch");
if (LoadByte(S) != LUAC_FORMAT)

View File

@ -6408,11 +6408,15 @@ The function starts by looking into the @Lid{package.loaded} table
to determine whether @id{modname} is already loaded.
If it is, then @id{require} returns the value stored
at @T{package.loaded[modname]}.
(The absence of a second result in this case
signals that this call did not have to load the module.)
Otherwise, it tries to find a @emph{loader} for the module.
To find a loader,
@id{require} is guided by the @Lid{package.searchers} sequence.
By changing this sequence,
@id{require} is guided by the table @Lid{package.searchers}.
Each item in this table is a search function,
that searches for the module in a particular way.
By changing this table,
we can change how @id{require} looks for a module.
The following explanation is based on the default configuration
for @Lid{package.searchers}.
@ -6429,9 +6433,14 @@ it tries an @emph{all-in-one} loader @seeF{package.searchers}.
Once a loader is found,
@id{require} calls the loader with two arguments:
@id{modname} and an extra value dependent on how it got the loader.
(If the loader came from a file,
this extra value is the file name.)
@id{modname} and an extra value,
a @emph{loader data},
also returned by the searcher.
The loader data can be any value useful to the module;
for the default searchers,
it indicates where the loader was found.
(For instance, if the loader came from a file,
this extra value is the file path.)
If the loader returns any non-nil value,
@id{require} assigns the returned value to @T{package.loaded[modname]}.
If the loader does not return a non-nil value and
@ -6439,6 +6448,9 @@ has not assigned any value to @T{package.loaded[modname]},
then @id{require} assigns @Rw{true} to this entry.
In any case, @id{require} returns the
final value of @T{package.loaded[modname]}.
Besides that value, @id{require} also returns as a second result
the loader data returned by the searcher,
which indicates how @id{require} found the module.
If there is any error loading or running the module,
or if it cannot find any loader for the module,
@ -6558,16 +6570,20 @@ table used by @Lid{require}.
@LibEntry{package.searchers|
A table used by @Lid{require} to control how to load modules.
A table used by @Lid{require} to control how to find modules.
Each entry in this table is a @def{searcher function}.
When looking for a module,
@Lid{require} calls each of these searchers in ascending order,
with the module name (the argument given to @Lid{require}) as its
sole argument.
The function can return another function (the module @def{loader})
plus an extra value that will be passed to that loader,
or a string explaining why it did not find that module
If the searcher finds the module,
it returns another function, the module @def{loader},
plus an extra value, a @emph{loader data},
that will be passed to that loader and
returned as a second result by @Lid{require}.
If it cannot find the module,
it returns a string explaining why
(or @nil if it has nothing to say).
Lua initializes this table with four searcher functions.
@ -6617,9 +6633,9 @@ into one single library,
with each submodule keeping its original open function.
All searchers except the first one (preload) return as the extra value
the file name where the module was found,
the file path where the module was found,
as returned by @Lid{package.searchpath}.
The first searcher returns no extra value.
The first searcher always returns the string @St{:preload:}.
}

View File

@ -122,12 +122,13 @@ local oldpath = package.path
package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR)
local try = function (p, n, r)
local try = function (p, n, r, ext)
NAME = nil
local rr = require(p)
local rr, x = require(p)
assert(NAME == n)
assert(REQUIRED == p)
assert(rr == r)
assert(ext == x)
end
a = require"names"
@ -143,27 +144,27 @@ assert(package.searchpath("C", package.path) == D"C.lua")
assert(require"C" == 25)
assert(require"C" == 25)
AA = nil
try('B', 'B.lua', true)
try('B', 'B.lua', true, "libs/B.lua")
assert(package.loaded.B)
assert(require"B" == true)
assert(package.loaded.A)
assert(require"C" == 25)
package.loaded.A = nil
try('B', nil, true) -- should not reload package
try('A', 'A.lua', true)
try('B', nil, true, nil) -- should not reload package
try('A', 'A.lua', true, "libs/A.lua")
package.loaded.A = nil
os.remove(D'A.lua')
AA = {}
try('A', 'A.lc', AA) -- now must find second option
try('A', 'A.lc', AA, "libs/A.lc") -- now must find second option
assert(package.searchpath("A", package.path) == D"A.lc")
assert(require("A") == AA)
AA = false
try('K', 'L', false) -- default option
try('K', 'L', false) -- default option (should reload it)
try('K', 'L', false, "libs/L") -- default option
try('K', 'L', false, "libs/L") -- default option (should reload it)
assert(rawget(_G, "_REQUIREDNAME") == nil)
AA = "x"
try("X", "XXxX", AA)
try("X", "XXxX", AA, "libs/XXxX")
removefiles(files)
@ -183,14 +184,16 @@ files = {
createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n")
AA = 0
local m = assert(require"P1")
local m, ext = assert(require"P1")
assert(ext == "libs/P1/init.lua")
assert(AA == 0 and m.AA == 10)
assert(require"P1" == m)
assert(require"P1" == m)
assert(package.searchpath("P1.xuxu", package.path) == D"P1/xuxu.lua")
m.xuxu = assert(require"P1.xuxu")
m.xuxu, ext = assert(require"P1.xuxu")
assert(AA == 0 and m.xuxu.AA == 20)
assert(ext == "libs/P1/xuxu.lua")
assert(require"P1.xuxu" == m.xuxu)
assert(require"P1.xuxu" == m.xuxu)
assert(require"P1" == m and m.AA == 10)
@ -267,15 +270,17 @@ else
-- test C modules with prefixes in names
package.cpath = DC"?"
local lib2 = require"lib2-v2"
local lib2, ext = require"lib2-v2"
assert(string.find(ext, "libs/lib2-v2", 1, true))
-- check correct access to global environment and correct
-- parameters
assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
assert(lib2.id("x") == "x")
-- test C submodules
local fs = require"lib1.sub"
local fs, ext = require"lib1.sub"
assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1")
assert(string.find(ext, "libs/lib1", 1, true))
assert(fs.id(45) == 45)
end
@ -293,10 +298,10 @@ do
return _ENV
end
local pl = require"pl"
local pl, ext = require"pl"
assert(require"pl" == pl)
assert(pl.xuxu(10) == 30)
assert(pl[1] == "pl" and pl[2] == nil)
assert(pl[1] == "pl" and pl[2] == ":preload:" and ext == ":preload:")
package = p
assert(type(package.path) == "string")