'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) { static int searcher_preload (lua_State *L) {
const char *name = luaL_checkstring(L, 1); const char *name = luaL_checkstring(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); 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); 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 */ /* else must load package */
lua_pop(L, 1); /* remove 'getfield' result */ lua_pop(L, 1); /* remove 'getfield' result */
findloader(L, name); findloader(L, name);
lua_pushstring(L, name); /* pass name as argument to module loader */ lua_rotate(L, -2, 1); /* function <-> loader data */
lua_insert(L, -2); /* name is 1st argument (before search 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 */ lua_call(L, 2, 1); /* run loader to load module */
/* stack: ...; loader data; result from loader */
if (!lua_isnil(L, -1)) /* non-nil return? */ if (!lua_isnil(L, -1)) /* non-nil return? */
lua_setfield(L, 2, name); /* LOADED[name] = returned value */ 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? */ if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */
lua_pushboolean(L, 1); /* use true as result */ 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 */ 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) #define checksize(S,t) fchecksize(S,sizeof(t),#t)
static void checkHeader (LoadState *S) { static void checkHeader (LoadState *S) {
/* 1st char already checked */ /* skip 1st char (already read and checked) */
checkliteral(S, LUA_SIGNATURE + 1, "not a binary chunk"); checkliteral(S, &LUA_SIGNATURE[1], "not a binary chunk");
if (LoadInt(S) != LUAC_VERSION) if (LoadInt(S) != LUAC_VERSION)
error(S, "version mismatch"); error(S, "version mismatch");
if (LoadByte(S) != LUAC_FORMAT) 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. to determine whether @id{modname} is already loaded.
If it is, then @id{require} returns the value stored If it is, then @id{require} returns the value stored
at @T{package.loaded[modname]}. 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. Otherwise, it tries to find a @emph{loader} for the module.
To find a loader, To find a loader,
@id{require} is guided by the @Lid{package.searchers} sequence. @id{require} is guided by the table @Lid{package.searchers}.
By changing this sequence, 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. we can change how @id{require} looks for a module.
The following explanation is based on the default configuration The following explanation is based on the default configuration
for @Lid{package.searchers}. for @Lid{package.searchers}.
@ -6429,9 +6433,14 @@ it tries an @emph{all-in-one} loader @seeF{package.searchers}.
Once a loader is found, Once a loader is found,
@id{require} calls the loader with two arguments: @id{require} calls the loader with two arguments:
@id{modname} and an extra value dependent on how it got the loader. @id{modname} and an extra value,
(If the loader came from a file, a @emph{loader data},
this extra value is the file name.) 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, If the loader returns any non-nil value,
@id{require} assigns the returned value to @T{package.loaded[modname]}. @id{require} assigns the returned value to @T{package.loaded[modname]}.
If the loader does not return a non-nil value and 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. then @id{require} assigns @Rw{true} to this entry.
In any case, @id{require} returns the In any case, @id{require} returns the
final value of @T{package.loaded[modname]}. 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, If there is any error loading or running the module,
or if it cannot find any loader for the module, or if it cannot find any loader for the module,
@ -6558,16 +6570,20 @@ table used by @Lid{require}.
@LibEntry{package.searchers| @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}. Each entry in this table is a @def{searcher function}.
When looking for a module, When looking for a module,
@Lid{require} calls each of these searchers in ascending order, @Lid{require} calls each of these searchers in ascending order,
with the module name (the argument given to @Lid{require}) as its with the module name (the argument given to @Lid{require}) as its
sole argument. sole argument.
The function can return another function (the module @def{loader}) If the searcher finds the module,
plus an extra value that will be passed to that loader, it returns another function, the module @def{loader},
or a string explaining why it did not find that module 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). (or @nil if it has nothing to say).
Lua initializes this table with four searcher functions. Lua initializes this table with four searcher functions.
@ -6617,9 +6633,9 @@ into one single library,
with each submodule keeping its original open function. with each submodule keeping its original open function.
All searchers except the first one (preload) return as the extra value 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}. 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) 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 NAME = nil
local rr = require(p) local rr, x = require(p)
assert(NAME == n) assert(NAME == n)
assert(REQUIRED == p) assert(REQUIRED == p)
assert(rr == r) assert(rr == r)
assert(ext == x)
end end
a = require"names" a = require"names"
@ -143,27 +144,27 @@ assert(package.searchpath("C", package.path) == D"C.lua")
assert(require"C" == 25) assert(require"C" == 25)
assert(require"C" == 25) assert(require"C" == 25)
AA = nil AA = nil
try('B', 'B.lua', true) try('B', 'B.lua', true, "libs/B.lua")
assert(package.loaded.B) assert(package.loaded.B)
assert(require"B" == true) assert(require"B" == true)
assert(package.loaded.A) assert(package.loaded.A)
assert(require"C" == 25) assert(require"C" == 25)
package.loaded.A = nil package.loaded.A = nil
try('B', nil, true) -- should not reload package try('B', nil, true, nil) -- should not reload package
try('A', 'A.lua', true) try('A', 'A.lua', true, "libs/A.lua")
package.loaded.A = nil package.loaded.A = nil
os.remove(D'A.lua') os.remove(D'A.lua')
AA = {} 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(package.searchpath("A", package.path) == D"A.lc")
assert(require("A") == AA) assert(require("A") == AA)
AA = false AA = false
try('K', 'L', false) -- default option try('K', 'L', false, "libs/L") -- default option
try('K', 'L', false) -- default option (should reload it) try('K', 'L', false, "libs/L") -- default option (should reload it)
assert(rawget(_G, "_REQUIREDNAME") == nil) assert(rawget(_G, "_REQUIREDNAME") == nil)
AA = "x" AA = "x"
try("X", "XXxX", AA) try("X", "XXxX", AA, "libs/XXxX")
removefiles(files) removefiles(files)
@ -183,14 +184,16 @@ files = {
createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n") createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n")
AA = 0 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(AA == 0 and m.AA == 10)
assert(require"P1" == m) assert(require"P1" == m)
assert(require"P1" == m) assert(require"P1" == m)
assert(package.searchpath("P1.xuxu", package.path) == D"P1/xuxu.lua") 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(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.xuxu" == m.xuxu) assert(require"P1.xuxu" == m.xuxu)
assert(require"P1" == m and m.AA == 10) assert(require"P1" == m and m.AA == 10)
@ -267,15 +270,17 @@ else
-- test C modules with prefixes in names -- test C modules with prefixes in names
package.cpath = DC"?" 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 -- check correct access to global environment and correct
-- parameters -- parameters
assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2") assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
assert(lib2.id("x") == "x") assert(lib2.id("x") == "x")
-- test C submodules -- 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(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1")
assert(string.find(ext, "libs/lib1", 1, true))
assert(fs.id(45) == 45) assert(fs.id(45) == 45)
end end
@ -293,10 +298,10 @@ do
return _ENV return _ENV
end end
local pl = require"pl" local pl, ext = require"pl"
assert(require"pl" == pl) assert(require"pl" == pl)
assert(pl.xuxu(10) == 30) 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 package = p
assert(type(package.path) == "string") assert(type(package.path) == "string")