2021-10-29 21:25:12 +01:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
#include "Luau/RequireTracer.h"
|
|
|
|
|
|
|
|
#include "Luau/Ast.h"
|
|
|
|
#include "Luau/Module.h"
|
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
2021-11-05 02:34:35 +00:00
|
|
|
struct RequireTracer : AstVisitor
|
|
|
|
{
|
2021-11-05 15:47:21 +00:00
|
|
|
RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName)
|
2021-11-05 02:34:35 +00:00
|
|
|
: result(result)
|
|
|
|
, fileResolver(fileResolver)
|
|
|
|
, currentModuleName(currentModuleName)
|
|
|
|
, locals(nullptr)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstExprTypeAssertion* expr) override
|
|
|
|
{
|
|
|
|
// suppress `require() :: any`
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstExprCall* expr) override
|
|
|
|
{
|
|
|
|
AstExprGlobal* global = expr->func->as<AstExprGlobal>();
|
|
|
|
|
|
|
|
if (global && global->name == "require" && expr->args.size >= 1)
|
2022-06-17 02:05:14 +01:00
|
|
|
requireCalls.push_back(expr);
|
2021-11-05 02:34:35 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstStatLocal* stat) override
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
|
|
|
|
{
|
|
|
|
AstLocal* local = stat->vars.data[i];
|
|
|
|
AstExpr* expr = stat->values.data[i];
|
|
|
|
|
|
|
|
// track initializing expression to be able to trace modules through locals
|
|
|
|
locals[local] = expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstStatAssign* stat) override
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < stat->vars.size; ++i)
|
|
|
|
{
|
|
|
|
// locals that are assigned don't have a known expression
|
|
|
|
if (AstExprLocal* expr = stat->vars.data[i]->as<AstExprLocal>())
|
|
|
|
locals[expr->local] = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(AstType* node) override
|
|
|
|
{
|
|
|
|
// allow resolving require inside `typeof` annotations
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
AstExpr* getDependent(AstExpr* node)
|
|
|
|
{
|
|
|
|
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
|
|
|
return locals[expr->local];
|
|
|
|
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
|
|
|
|
return expr->expr;
|
|
|
|
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
|
|
|
|
return expr->expr;
|
|
|
|
else if (AstExprCall* expr = node->as<AstExprCall>(); expr && expr->self)
|
|
|
|
return expr->func->as<AstExprIndexName>()->expr;
|
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void process()
|
|
|
|
{
|
|
|
|
ModuleInfo moduleContext{currentModuleName};
|
|
|
|
|
|
|
|
// seed worklist with require arguments
|
2022-06-17 02:05:14 +01:00
|
|
|
work.reserve(requireCalls.size());
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
for (AstExprCall* require : requireCalls)
|
2021-11-05 02:34:35 +00:00
|
|
|
work.push_back(require->args.data[0]);
|
|
|
|
|
|
|
|
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
|
|
|
for (size_t i = 0; i < work.size(); ++i)
|
|
|
|
if (AstExpr* dep = getDependent(work[i]))
|
|
|
|
work.push_back(dep);
|
|
|
|
|
|
|
|
// resolve all expressions to a module info
|
|
|
|
for (size_t i = work.size(); i > 0; --i)
|
|
|
|
{
|
|
|
|
AstExpr* expr = work[i - 1];
|
|
|
|
|
|
|
|
// when multiple expressions depend on the same one we push it to work queue multiple times
|
|
|
|
if (result.exprs.contains(expr))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::optional<ModuleInfo> info;
|
|
|
|
|
|
|
|
if (AstExpr* dep = getDependent(expr))
|
|
|
|
{
|
|
|
|
const ModuleInfo* context = result.exprs.find(dep);
|
|
|
|
|
|
|
|
// locals just inherit their dependent context, no resolution required
|
|
|
|
if (expr->is<AstExprLocal>())
|
|
|
|
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
|
|
|
|
else
|
|
|
|
info = fileResolver->resolveModule(context, expr);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info = fileResolver->resolveModule(&moduleContext, expr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info)
|
|
|
|
result.exprs[expr] = std::move(*info);
|
|
|
|
}
|
|
|
|
|
|
|
|
// resolve all requires according to their argument
|
2022-06-17 02:05:14 +01:00
|
|
|
result.requireList.reserve(requireCalls.size());
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
for (AstExprCall* require : requireCalls)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
AstExpr* arg = require->args.data[0];
|
|
|
|
|
|
|
|
if (const ModuleInfo* info = result.exprs.find(arg))
|
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
result.requireList.push_back({info->name, require->location});
|
2021-11-05 02:34:35 +00:00
|
|
|
|
|
|
|
ModuleInfo infoCopy = *info; // copy *info out since next line invalidates info!
|
|
|
|
result.exprs[require] = std::move(infoCopy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result.exprs[require] = {}; // mark require as unresolved
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RequireTraceResult& result;
|
|
|
|
FileResolver* fileResolver;
|
|
|
|
ModuleName currentModuleName;
|
|
|
|
|
|
|
|
DenseHashMap<AstLocal*, AstExpr*> locals;
|
|
|
|
std::vector<AstExpr*> work;
|
2022-06-17 02:05:14 +01:00
|
|
|
std::vector<AstExprCall*> requireCalls;
|
2021-11-05 02:34:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
RequireTraceResult result;
|
|
|
|
RequireTracer tracer{result, fileResolver, currentModuleName};
|
|
|
|
root->visit(&tracer);
|
|
|
|
tracer.process();
|
|
|
|
return result;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|