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/Frontend.h"
|
|
|
|
|
2022-10-21 18:54:01 +01:00
|
|
|
#include "Luau/BuiltinDefinitions.h"
|
2022-04-07 22:29:01 +01:00
|
|
|
#include "Luau/Clone.h"
|
2022-06-17 02:05:14 +01:00
|
|
|
#include "Luau/Common.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
#include "Luau/Config.h"
|
2023-11-03 23:45:04 +00:00
|
|
|
#include "Luau/ConstraintGenerator.h"
|
2022-06-03 23:15:45 +01:00
|
|
|
#include "Luau/ConstraintSolver.h"
|
2022-12-02 18:09:59 +00:00
|
|
|
#include "Luau/DataFlowGraph.h"
|
2022-09-08 23:14:25 +01:00
|
|
|
#include "Luau/DcrLogger.h"
|
2022-06-17 02:05:14 +01:00
|
|
|
#include "Luau/FileResolver.h"
|
2022-02-18 01:18:01 +00:00
|
|
|
#include "Luau/Parser.h"
|
2021-11-05 02:34:35 +00:00
|
|
|
#include "Luau/Scope.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
#include "Luau/StringUtils.h"
|
2021-11-05 02:34:35 +00:00
|
|
|
#include "Luau/TimeTrace.h"
|
2024-03-30 23:14:44 +00:00
|
|
|
#include "Luau/TypeArena.h"
|
2022-06-17 02:05:14 +01:00
|
|
|
#include "Luau/TypeChecker2.h"
|
2023-10-13 21:20:12 +01:00
|
|
|
#include "Luau/NonStrictTypeChecker.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
#include "Luau/TypeInfer.h"
|
|
|
|
#include "Luau/Variant.h"
|
2024-03-30 23:14:44 +00:00
|
|
|
#include "Luau/VisitType.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <chrono>
|
2023-05-05 22:52:49 +01:00
|
|
|
#include <condition_variable>
|
|
|
|
#include <exception>
|
|
|
|
#include <mutex>
|
2021-10-29 21:25:12 +01:00
|
|
|
#include <stdexcept>
|
2022-12-09 19:57:01 +00:00
|
|
|
#include <string>
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-04-15 00:57:43 +01:00
|
|
|
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
2023-03-03 20:21:14 +00:00
|
|
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
2022-04-15 00:57:43 +01:00
|
|
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
2021-10-29 21:25:12 +01:00
|
|
|
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
2024-06-29 01:34:49 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(LuauCancelFromProgress, false)
|
2024-02-23 20:08:34 +00:00
|
|
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
2023-04-14 19:06:22 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
2024-01-19 18:04:46 +00:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
|
2024-03-30 23:14:44 +00:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false)
|
2024-04-05 21:45:09 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
|
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
|
2024-07-08 22:57:06 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
struct BuildQueueItem
|
|
|
|
{
|
|
|
|
ModuleName name;
|
|
|
|
ModuleName humanReadableName;
|
|
|
|
|
|
|
|
// Parameters
|
|
|
|
std::shared_ptr<SourceNode> sourceNode;
|
|
|
|
std::shared_ptr<SourceModule> sourceModule;
|
|
|
|
Config config;
|
|
|
|
ScopePtr environmentScope;
|
|
|
|
std::vector<RequireCycle> requireCycles;
|
|
|
|
FrontendOptions options;
|
|
|
|
bool recordJsonLog = false;
|
|
|
|
|
|
|
|
// Queue state
|
|
|
|
std::vector<size_t> reverseDeps;
|
|
|
|
int dirtyDependencies = 0;
|
|
|
|
bool processing = false;
|
|
|
|
|
|
|
|
// Result
|
|
|
|
std::exception_ptr exception;
|
|
|
|
ModulePtr module;
|
|
|
|
Frontend::Stats stats;
|
|
|
|
};
|
|
|
|
|
2022-02-18 01:18:01 +00:00
|
|
|
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-02-18 01:18:01 +00:00
|
|
|
for (const HotComment& hc : hotcomments)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-02-18 01:18:01 +00:00
|
|
|
if (!hc.header)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (hc.content == "nocheck")
|
2021-10-29 21:25:12 +01:00
|
|
|
return Mode::NoCheck;
|
|
|
|
|
2022-02-18 01:18:01 +00:00
|
|
|
if (hc.content == "nonstrict")
|
2021-10-29 21:25:12 +01:00
|
|
|
return Mode::Nonstrict;
|
|
|
|
|
2022-02-18 01:18:01 +00:00
|
|
|
if (hc.content == "strict")
|
2021-10-29 21:25:12 +01:00
|
|
|
return Mode::Strict;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
|
|
|
|
{
|
|
|
|
// TODO: What do we do in this situation? This means that the definition
|
|
|
|
// file is exporting a type that is also a persistent type.
|
|
|
|
if (ty->persistent)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
asMutable(ty)->documentationSymbol = rootName;
|
|
|
|
|
2023-01-04 20:53:17 +00:00
|
|
|
if (TableType* ttv = getMutable<TableType>(ty))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
for (auto& [name, prop] : ttv->props)
|
|
|
|
{
|
|
|
|
prop.documentationSymbol = rootName + "." + name;
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 20:53:17 +00:00
|
|
|
else if (ClassType* ctv = getMutable<ClassType>(ty))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
for (auto& [name, prop] : ctv->props)
|
|
|
|
{
|
|
|
|
prop.documentationSymbol = rootName + "." + name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
static ParseResult parseSourceForModule(std::string_view source, Luau::SourceModule& sourceModule, bool captureComments)
|
2022-08-04 23:35:33 +01:00
|
|
|
{
|
|
|
|
ParseOptions options;
|
|
|
|
options.allowDeclarationSyntax = true;
|
2023-03-10 20:21:07 +00:00
|
|
|
options.captureComments = captureComments;
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2023-03-10 20:21:07 +00:00
|
|
|
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
|
|
|
|
sourceModule.root = parseResult.root;
|
|
|
|
sourceModule.mode = Mode::Definition;
|
2023-04-07 22:01:29 +01:00
|
|
|
return parseResult;
|
|
|
|
}
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, ScopePtr targetScope, const std::string& packageName)
|
|
|
|
{
|
2023-08-11 15:42:37 +01:00
|
|
|
CloneState cloneState{globals.builtinTypes};
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2022-12-02 18:09:59 +00:00
|
|
|
std::vector<TypeId> typesToPersist;
|
2023-02-10 19:40:38 +00:00
|
|
|
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size());
|
2022-10-28 11:37:29 +01:00
|
|
|
|
2022-12-02 18:09:59 +00:00
|
|
|
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
|
|
|
{
|
2023-03-10 20:21:07 +00:00
|
|
|
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
|
2022-12-02 18:09:59 +00:00
|
|
|
std::string documentationSymbol = packageName + "/global/" + name;
|
|
|
|
generateDocumentationSymbols(globalTy, documentationSymbol);
|
2023-04-07 22:01:29 +01:00
|
|
|
targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
2022-10-28 11:37:29 +01:00
|
|
|
|
2022-12-02 18:09:59 +00:00
|
|
|
typesToPersist.push_back(globalTy);
|
2022-10-28 11:37:29 +01:00
|
|
|
}
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2023-02-10 19:40:38 +00:00
|
|
|
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
|
2022-12-02 18:09:59 +00:00
|
|
|
{
|
2023-03-10 20:21:07 +00:00
|
|
|
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
|
2022-12-02 18:09:59 +00:00
|
|
|
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
|
|
|
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
2023-04-07 22:01:29 +01:00
|
|
|
targetScope->exportedTypeBindings[name] = globalTy;
|
2022-10-28 11:37:29 +01:00
|
|
|
|
2022-12-02 18:09:59 +00:00
|
|
|
typesToPersist.push_back(globalTy.type);
|
|
|
|
}
|
2022-10-28 11:37:29 +01:00
|
|
|
|
2022-12-02 18:09:59 +00:00
|
|
|
for (TypeId ty : typesToPersist)
|
|
|
|
{
|
|
|
|
persist(ty);
|
2022-08-04 23:35:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
|
|
|
|
const std::string& packageName, bool captureComments, bool typeCheckForAutocomplete)
|
2023-03-10 20:21:07 +00:00
|
|
|
{
|
|
|
|
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
|
|
|
|
|
|
|
Luau::SourceModule sourceModule;
|
2024-01-12 22:25:27 +00:00
|
|
|
sourceModule.name = packageName;
|
|
|
|
sourceModule.humanReadableName = packageName;
|
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
|
2023-03-10 20:21:07 +00:00
|
|
|
if (parseResult.errors.size() > 0)
|
|
|
|
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
|
|
|
|
|
2023-04-14 19:06:22 +01:00
|
|
|
ModulePtr checkedModule = check(sourceModule, Mode::Definition, {}, std::nullopt, /*forAutocomplete*/ false, /*recordJsonLog*/ false, {});
|
2023-03-10 20:21:07 +00:00
|
|
|
|
|
|
|
if (checkedModule->errors.size() > 0)
|
|
|
|
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
|
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
persistCheckedTypes(checkedModule, globals, targetScope, packageName);
|
2023-03-10 20:21:07 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
|
|
|
|
}
|
2023-03-10 20:21:07 +00:00
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
|
|
|
|
{
|
|
|
|
const AstExprIndexName* indexName = pathExpr.as<AstExprIndexName>();
|
|
|
|
if (!indexName)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
std::vector<std::string_view> segments{indexName->index.value};
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
if (AstExprIndexName* in = indexName->expr->as<AstExprIndexName>())
|
|
|
|
{
|
|
|
|
segments.push_back(in->index.value);
|
|
|
|
indexName = in;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as<AstExprGlobal>())
|
|
|
|
{
|
|
|
|
segments.push_back(indexNameAsGlobal->name.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (AstExprLocal* indexNameAsLocal = indexName->expr->as<AstExprLocal>())
|
|
|
|
{
|
|
|
|
segments.push_back(indexNameAsLocal->local->name.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::reverse(segments.begin(), segments.end());
|
|
|
|
return segments;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments)
|
|
|
|
{
|
|
|
|
if (segments.empty())
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
std::vector<std::string_view> result;
|
|
|
|
|
|
|
|
auto it = segments.begin();
|
|
|
|
|
|
|
|
if (*it == "script" && !currentModuleName.empty())
|
|
|
|
{
|
|
|
|
result = split(currentModuleName, '/');
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; it != segments.end(); ++it)
|
|
|
|
{
|
|
|
|
if (result.size() > 1 && *it == "Parent")
|
|
|
|
result.pop_back();
|
|
|
|
else
|
|
|
|
result.push_back(*it);
|
|
|
|
}
|
|
|
|
|
|
|
|
return join(result, "/");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
|
|
|
{
|
|
|
|
std::vector<std::string_view> segments = parsePathExpr(pathExpr);
|
|
|
|
return pathExprToModuleName(currentModuleName, segments);
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
2023-03-17 19:20:37 +00:00
|
|
|
static ErrorVec accumulateErrors(
|
2023-05-05 22:52:49 +01:00
|
|
|
const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes, ModuleResolver& moduleResolver, const ModuleName& name)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2023-11-10 21:10:07 +00:00
|
|
|
DenseHashSet<ModuleName> seen{{}};
|
2021-10-29 21:25:12 +01:00
|
|
|
std::vector<ModuleName> queue{name};
|
|
|
|
|
|
|
|
ErrorVec result;
|
|
|
|
|
|
|
|
while (!queue.empty())
|
|
|
|
{
|
|
|
|
ModuleName next = std::move(queue.back());
|
|
|
|
queue.pop_back();
|
|
|
|
|
2023-11-10 21:10:07 +00:00
|
|
|
if (seen.contains(next))
|
2021-10-29 21:25:12 +01:00
|
|
|
continue;
|
|
|
|
seen.insert(next);
|
|
|
|
|
|
|
|
auto it = sourceNodes.find(next);
|
|
|
|
if (it == sourceNodes.end())
|
|
|
|
continue;
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
const SourceNode& sourceNode = *it->second;
|
2022-06-17 02:05:14 +01:00
|
|
|
queue.insert(queue.end(), sourceNode.requireSet.begin(), sourceNode.requireSet.end());
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
// FIXME: If a module has a syntax error, we won't be able to re-report it here.
|
|
|
|
// The solution is probably to move errors from Module to SourceNode
|
|
|
|
|
2023-04-21 23:14:26 +01:00
|
|
|
auto modulePtr = moduleResolver.getModule(next);
|
|
|
|
if (!modulePtr)
|
2021-10-29 21:25:12 +01:00
|
|
|
continue;
|
|
|
|
|
2023-04-21 23:14:26 +01:00
|
|
|
Module& module = *modulePtr;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
std::sort(module.errors.begin(), module.errors.end(), [](const TypeError& e1, const TypeError& e2) -> bool {
|
|
|
|
return e1.location.begin > e2.location.begin;
|
|
|
|
});
|
|
|
|
|
|
|
|
result.insert(result.end(), module.errors.begin(), module.errors.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::reverse(result.begin(), result.end());
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-03-17 19:20:37 +00:00
|
|
|
static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode)
|
|
|
|
{
|
|
|
|
uint64_t ignoreLints = LintWarning::parseMask(hotcomments);
|
|
|
|
|
|
|
|
lintOptions.warningMask &= ~ignoreLints;
|
|
|
|
|
|
|
|
if (mode != Mode::NoCheck)
|
|
|
|
{
|
|
|
|
lintOptions.disableWarning(Luau::LintWarning::Code_UnknownGlobal);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == Mode::Strict)
|
|
|
|
{
|
|
|
|
lintOptions.disableWarning(Luau::LintWarning::Code_ImplicitReturn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
// Given a source node (start), find all requires that start a transitive dependency path that ends back at start
|
|
|
|
// For each such path, record the full path and the location of the require in the starting module.
|
|
|
|
// Note that this is O(V^2) for a fully connected graph and produces O(V) paths of length O(V)
|
|
|
|
// However, when the graph is acyclic, this is O(V), as well as when only the first cycle is needed (stopAtFirst=true)
|
2023-05-05 22:52:49 +01:00
|
|
|
std::vector<RequireCycle> getRequireCycles(const FileResolver* resolver,
|
|
|
|
const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes, const SourceNode* start, bool stopAtFirst = false)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
std::vector<RequireCycle> result;
|
|
|
|
|
|
|
|
DenseHashSet<const SourceNode*> seen(nullptr);
|
|
|
|
std::vector<const SourceNode*> stack;
|
|
|
|
std::vector<const SourceNode*> path;
|
|
|
|
|
|
|
|
for (const auto& [depName, depLocation] : start->requireLocations)
|
|
|
|
{
|
|
|
|
std::vector<ModuleName> cycle;
|
|
|
|
|
|
|
|
auto dit = sourceNodes.find(depName);
|
|
|
|
if (dit == sourceNodes.end())
|
|
|
|
continue;
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
stack.push_back(dit->second.get());
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
while (!stack.empty())
|
|
|
|
{
|
|
|
|
const SourceNode* top = stack.back();
|
|
|
|
stack.pop_back();
|
|
|
|
|
|
|
|
if (top == nullptr)
|
|
|
|
{
|
|
|
|
// special marker for post-order processing
|
|
|
|
LUAU_ASSERT(!path.empty());
|
|
|
|
top = path.back();
|
|
|
|
path.pop_back();
|
|
|
|
|
|
|
|
// we reached the node! path must form a cycle now
|
|
|
|
if (top == start)
|
|
|
|
{
|
|
|
|
for (const SourceNode* node : path)
|
2023-05-25 22:36:34 +01:00
|
|
|
cycle.push_back(node->name);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-05-25 22:36:34 +01:00
|
|
|
cycle.push_back(top->name);
|
2021-10-29 21:25:12 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!seen.contains(top))
|
|
|
|
{
|
|
|
|
seen.insert(top);
|
|
|
|
|
|
|
|
// push marker for post-order processing
|
|
|
|
path.push_back(top);
|
|
|
|
stack.push_back(nullptr);
|
|
|
|
|
|
|
|
// note: we push require edges in the opposite order
|
|
|
|
// because it's a stack, the last edge to be pushed gets processed first
|
|
|
|
// this ensures that the cyclic path we report is the first one in DFS order
|
|
|
|
for (size_t i = top->requireLocations.size(); i > 0; --i)
|
|
|
|
{
|
|
|
|
const ModuleName& reqName = top->requireLocations[i - 1].first;
|
|
|
|
|
|
|
|
auto rit = sourceNodes.find(reqName);
|
|
|
|
if (rit != sourceNodes.end())
|
2023-05-05 22:52:49 +01:00
|
|
|
stack.push_back(rit->second.get());
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
path.clear();
|
|
|
|
stack.clear();
|
|
|
|
|
|
|
|
if (!cycle.empty())
|
|
|
|
{
|
|
|
|
result.push_back({depLocation, std::move(cycle)});
|
|
|
|
|
|
|
|
if (stopAtFirst)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
// note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start
|
|
|
|
// so it's safe to *only* clear seen vector when we find a cycle
|
|
|
|
// if we don't do it, we will not have correct reporting for some cycles
|
|
|
|
seen.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
double getTimestamp()
|
|
|
|
{
|
|
|
|
using namespace std::chrono;
|
|
|
|
return double(duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count()) / 1e9;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
|
2023-01-04 20:53:17 +00:00
|
|
|
: builtinTypes(NotNull{&builtinTypes_})
|
2022-09-08 23:14:25 +01:00
|
|
|
, fileResolver(fileResolver)
|
2021-10-29 21:25:12 +01:00
|
|
|
, moduleResolver(this)
|
|
|
|
, moduleResolverForAutocomplete(this)
|
2023-03-10 20:21:07 +00:00
|
|
|
, globals(builtinTypes)
|
|
|
|
, globalsForAutocomplete(builtinTypes)
|
2021-10-29 21:25:12 +01:00
|
|
|
, configResolver(configResolver)
|
|
|
|
, options(options)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-07-07 21:10:48 +01:00
|
|
|
void Frontend::parse(const ModuleName& name)
|
|
|
|
{
|
|
|
|
LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend");
|
|
|
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
|
|
|
|
|
|
|
if (getCheckResult(name, false, false))
|
|
|
|
return;
|
|
|
|
|
|
|
|
std::vector<ModuleName> buildQueue;
|
|
|
|
parseGraph(buildQueue, name, false);
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
|
2023-05-05 22:52:49 +01:00
|
|
|
{
|
|
|
|
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend");
|
|
|
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
|
|
|
|
|
|
|
FrontendOptions frontendOptions = optionOverride.value_or(options);
|
2024-06-29 01:34:49 +01:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
|
|
frontendOptions.forAutocomplete = false;
|
2023-05-05 22:52:49 +01:00
|
|
|
|
|
|
|
if (std::optional<CheckResult> result = getCheckResult(name, true, frontendOptions.forAutocomplete))
|
|
|
|
return std::move(*result);
|
|
|
|
|
|
|
|
std::vector<ModuleName> buildQueue;
|
|
|
|
bool cycleDetected = parseGraph(buildQueue, name, frontendOptions.forAutocomplete);
|
|
|
|
|
2023-11-10 21:10:07 +00:00
|
|
|
DenseHashSet<Luau::ModuleName> seen{{}};
|
2023-05-05 22:52:49 +01:00
|
|
|
std::vector<BuildQueueItem> buildQueueItems;
|
|
|
|
addBuildQueueItems(buildQueueItems, buildQueue, cycleDetected, seen, frontendOptions);
|
|
|
|
LUAU_ASSERT(!buildQueueItems.empty());
|
|
|
|
|
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(buildQueueItems.back().name == name);
|
|
|
|
buildQueueItems.back().recordJsonLog = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
checkBuildQueueItems(buildQueueItems);
|
|
|
|
|
|
|
|
// Collect results only for checked modules, 'getCheckResult' produces a different result
|
|
|
|
CheckResult checkResult;
|
|
|
|
|
|
|
|
for (const BuildQueueItem& item : buildQueueItems)
|
|
|
|
{
|
|
|
|
if (item.module->timeout)
|
|
|
|
checkResult.timeoutHits.push_back(item.name);
|
|
|
|
|
2023-07-14 19:08:53 +01:00
|
|
|
// If check was manually cancelled, do not return partial results
|
2023-08-11 15:42:37 +01:00
|
|
|
if (item.module->cancelled)
|
2023-07-14 19:08:53 +01:00
|
|
|
return {};
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
checkResult.errors.insert(checkResult.errors.end(), item.module->errors.begin(), item.module->errors.end());
|
|
|
|
|
|
|
|
if (item.name == name)
|
|
|
|
checkResult.lintResult = item.module->lintResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
return checkResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Frontend::queueModuleCheck(const std::vector<ModuleName>& names)
|
|
|
|
{
|
|
|
|
moduleQueue.insert(moduleQueue.end(), names.begin(), names.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Frontend::queueModuleCheck(const ModuleName& name)
|
|
|
|
{
|
|
|
|
moduleQueue.push_back(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptions> optionOverride,
|
2024-06-29 01:34:49 +01:00
|
|
|
std::function<void(std::function<void()> task)> executeTask, std::function<bool(size_t done, size_t total)> progress)
|
2023-05-05 22:52:49 +01:00
|
|
|
{
|
|
|
|
FrontendOptions frontendOptions = optionOverride.value_or(options);
|
2024-06-29 01:34:49 +01:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
|
|
frontendOptions.forAutocomplete = false;
|
2023-05-05 22:52:49 +01:00
|
|
|
|
|
|
|
// By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown
|
|
|
|
std::vector<ModuleName> currModuleQueue;
|
|
|
|
std::swap(currModuleQueue, moduleQueue);
|
|
|
|
|
2023-11-10 21:10:07 +00:00
|
|
|
DenseHashSet<Luau::ModuleName> seen{{}};
|
2023-05-05 22:52:49 +01:00
|
|
|
std::vector<BuildQueueItem> buildQueueItems;
|
|
|
|
|
|
|
|
for (const ModuleName& name : currModuleQueue)
|
|
|
|
{
|
2023-11-10 21:10:07 +00:00
|
|
|
if (seen.contains(name))
|
2023-05-05 22:52:49 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!isDirty(name, frontendOptions.forAutocomplete))
|
|
|
|
{
|
|
|
|
seen.insert(name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ModuleName> queue;
|
|
|
|
bool cycleDetected = parseGraph(queue, name, frontendOptions.forAutocomplete, [&seen](const ModuleName& name) {
|
2023-11-10 21:10:07 +00:00
|
|
|
return seen.contains(name);
|
2023-05-05 22:52:49 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buildQueueItems.empty())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
// We need a mapping from modules to build queue slots
|
|
|
|
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
|
|
|
{
|
|
|
|
BuildQueueItem& item = buildQueueItems[i];
|
|
|
|
moduleNameToQueue[item.name] = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default task execution is single-threaded and immediate
|
|
|
|
if (!executeTask)
|
|
|
|
{
|
|
|
|
executeTask = [](std::function<void()> task) {
|
|
|
|
task();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::mutex mtx;
|
|
|
|
std::condition_variable cv;
|
|
|
|
std::vector<size_t> readyQueueItems;
|
|
|
|
|
|
|
|
size_t processing = 0;
|
|
|
|
size_t remaining = buildQueueItems.size();
|
|
|
|
|
|
|
|
auto itemTask = [&](size_t i) {
|
|
|
|
BuildQueueItem& item = buildQueueItems[i];
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
checkBuildQueueItem(item);
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
item.exception = std::current_exception();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
std::unique_lock guard(mtx);
|
|
|
|
readyQueueItems.push_back(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
cv.notify_one();
|
|
|
|
};
|
|
|
|
|
|
|
|
auto sendItemTask = [&](size_t i) {
|
|
|
|
BuildQueueItem& item = buildQueueItems[i];
|
|
|
|
|
|
|
|
item.processing = true;
|
|
|
|
processing++;
|
|
|
|
|
|
|
|
executeTask([&itemTask, i]() {
|
|
|
|
itemTask(i);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
auto sendCycleItemTask = [&] {
|
|
|
|
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
|
|
|
{
|
|
|
|
BuildQueueItem& item = buildQueueItems[i];
|
|
|
|
|
|
|
|
if (!item.processing)
|
|
|
|
{
|
|
|
|
sendItemTask(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// In a first pass, check modules that have no dependencies and record info of those modules that wait
|
|
|
|
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
|
|
|
{
|
|
|
|
BuildQueueItem& item = buildQueueItems[i];
|
|
|
|
|
|
|
|
for (const ModuleName& dep : item.sourceNode->requireSet)
|
|
|
|
{
|
|
|
|
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
|
|
|
|
{
|
|
|
|
if (it->second->hasDirtyModule(frontendOptions.forAutocomplete))
|
|
|
|
{
|
|
|
|
item.dirtyDependencies++;
|
|
|
|
|
|
|
|
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.dirtyDependencies == 0)
|
|
|
|
sendItemTask(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not a single item was found, a cycle in the graph was hit
|
|
|
|
if (processing == 0)
|
|
|
|
sendCycleItemTask();
|
|
|
|
|
|
|
|
std::vector<size_t> nextItems;
|
2023-06-16 18:35:18 +01:00
|
|
|
std::optional<size_t> itemWithException;
|
2023-07-14 19:08:53 +01:00
|
|
|
bool cancelled = false;
|
2023-05-05 22:52:49 +01:00
|
|
|
|
|
|
|
while (remaining != 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::unique_lock guard(mtx);
|
|
|
|
|
|
|
|
// If nothing is ready yet, wait
|
2023-06-16 18:35:18 +01:00
|
|
|
cv.wait(guard, [&readyQueueItems] {
|
|
|
|
return !readyQueueItems.empty();
|
|
|
|
});
|
2023-05-05 22:52:49 +01:00
|
|
|
|
|
|
|
// Handle checked items
|
|
|
|
for (size_t i : readyQueueItems)
|
|
|
|
{
|
|
|
|
const BuildQueueItem& item = buildQueueItems[i];
|
2023-06-16 18:35:18 +01:00
|
|
|
|
2023-07-14 19:08:53 +01:00
|
|
|
// If exception was thrown, stop adding new items and wait for processing items to complete
|
|
|
|
if (item.exception)
|
|
|
|
itemWithException = i;
|
2023-06-16 18:35:18 +01:00
|
|
|
|
2023-08-11 15:42:37 +01:00
|
|
|
if (item.module && item.module->cancelled)
|
2023-07-14 19:08:53 +01:00
|
|
|
cancelled = true;
|
|
|
|
|
|
|
|
if (itemWithException || cancelled)
|
|
|
|
break;
|
2023-06-16 18:35:18 +01:00
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
recordItemResult(item);
|
|
|
|
|
|
|
|
// Notify items that were waiting for this dependency
|
|
|
|
for (size_t reverseDep : item.reverseDeps)
|
|
|
|
{
|
|
|
|
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
|
|
|
|
|
|
|
|
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
|
|
|
|
reverseDepItem.dirtyDependencies--;
|
|
|
|
|
|
|
|
// In case of a module cycle earlier, check if unlocked an item that was already processed
|
|
|
|
if (!reverseDepItem.processing && reverseDepItem.dirtyDependencies == 0)
|
|
|
|
nextItems.push_back(reverseDep);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LUAU_ASSERT(processing >= readyQueueItems.size());
|
|
|
|
processing -= readyQueueItems.size();
|
|
|
|
|
|
|
|
LUAU_ASSERT(remaining >= readyQueueItems.size());
|
|
|
|
remaining -= readyQueueItems.size();
|
|
|
|
readyQueueItems.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (progress)
|
2024-06-29 01:34:49 +01:00
|
|
|
{
|
|
|
|
if (FFlag::LuauCancelFromProgress)
|
|
|
|
{
|
|
|
|
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
|
|
|
|
cancelled = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
progress(buildQueueItems.size() - remaining, buildQueueItems.size());
|
|
|
|
}
|
|
|
|
}
|
2023-05-05 22:52:49 +01:00
|
|
|
|
|
|
|
// Items cannot be submitted while holding the lock
|
|
|
|
for (size_t i : nextItems)
|
|
|
|
sendItemTask(i);
|
|
|
|
nextItems.clear();
|
|
|
|
|
2024-02-02 21:32:42 +00:00
|
|
|
if (processing == 0)
|
2023-06-16 18:35:18 +01:00
|
|
|
{
|
2023-07-14 19:08:53 +01:00
|
|
|
// Typechecking might have been cancelled by user, don't return partial results
|
2023-08-11 15:42:37 +01:00
|
|
|
if (cancelled)
|
2023-07-14 19:08:53 +01:00
|
|
|
return {};
|
|
|
|
|
2023-06-16 18:35:18 +01:00
|
|
|
// We might have stopped because of a pending exception
|
2023-07-14 19:08:53 +01:00
|
|
|
if (itemWithException)
|
2023-06-16 18:35:18 +01:00
|
|
|
recordItemResult(buildQueueItems[*itemWithException]);
|
2023-12-15 21:29:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we aren't done, but don't have anything processing, we hit a cycle
|
|
|
|
if (remaining != 0 && processing == 0)
|
2023-05-05 22:52:49 +01:00
|
|
|
sendCycleItemTask();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ModuleName> checkedModules;
|
|
|
|
checkedModules.reserve(buildQueueItems.size());
|
|
|
|
|
|
|
|
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
|
|
|
checkedModules.push_back(std::move(buildQueueItems[i].name));
|
|
|
|
|
|
|
|
return checkedModules;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
|
|
|
|
{
|
2024-06-29 01:34:49 +01:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
|
|
forAutocomplete = false;
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
auto it = sourceNodes.find(name);
|
|
|
|
|
|
|
|
if (it == sourceNodes.end() || it->second->hasDirtyModule(forAutocomplete))
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
auto& resolver = forAutocomplete ? moduleResolverForAutocomplete : moduleResolver;
|
|
|
|
|
|
|
|
ModulePtr module = resolver.getModule(name);
|
|
|
|
|
|
|
|
if (module == nullptr)
|
|
|
|
throw InternalCompilerError("Frontend does not have module: " + name, name);
|
|
|
|
|
|
|
|
CheckResult checkResult;
|
|
|
|
|
|
|
|
if (module->timeout)
|
|
|
|
checkResult.timeoutHits.push_back(name);
|
|
|
|
|
|
|
|
if (accumulateNested)
|
|
|
|
checkResult.errors = accumulateErrors(sourceNodes, resolver, name);
|
|
|
|
else
|
|
|
|
checkResult.errors.insert(checkResult.errors.end(), module->errors.begin(), module->errors.end());
|
|
|
|
|
|
|
|
// Get lint result only for top checked module
|
|
|
|
checkResult.lintResult = module->lintResult;
|
|
|
|
|
|
|
|
return checkResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Frontend::parseGraph(
|
|
|
|
std::vector<ModuleName>& buildQueue, const ModuleName& root, bool forAutocomplete, std::function<bool(const ModuleName&)> canSkip)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-11-05 02:34:35 +00:00
|
|
|
LUAU_TIMETRACE_SCOPE("Frontend::parseGraph", "Frontend");
|
|
|
|
LUAU_TIMETRACE_ARGUMENT("root", root.c_str());
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
// https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
|
|
|
|
enum Mark
|
|
|
|
{
|
|
|
|
None,
|
|
|
|
Temporary,
|
|
|
|
Permanent
|
|
|
|
};
|
|
|
|
|
|
|
|
DenseHashMap<SourceNode*, Mark> seen(nullptr);
|
|
|
|
std::vector<SourceNode*> stack;
|
|
|
|
std::vector<SourceNode*> path;
|
|
|
|
bool cyclic = false;
|
|
|
|
|
|
|
|
{
|
2023-01-04 20:53:17 +00:00
|
|
|
auto [sourceNode, _] = getSourceNode(root);
|
2021-10-29 21:25:12 +01:00
|
|
|
if (sourceNode)
|
|
|
|
stack.push_back(sourceNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!stack.empty())
|
|
|
|
{
|
|
|
|
SourceNode* top = stack.back();
|
|
|
|
stack.pop_back();
|
|
|
|
|
|
|
|
if (top == nullptr)
|
|
|
|
{
|
|
|
|
// special marker for post-order processing
|
|
|
|
LUAU_ASSERT(!path.empty());
|
|
|
|
|
|
|
|
top = path.back();
|
|
|
|
path.pop_back();
|
|
|
|
|
|
|
|
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
|
|
|
|
Mark& topseen = seen[top];
|
|
|
|
LUAU_ASSERT(topseen == Temporary);
|
|
|
|
topseen = Permanent;
|
|
|
|
|
|
|
|
buildQueue.push_back(top->name);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
|
|
|
|
Mark& topseen = seen[top];
|
|
|
|
|
|
|
|
if (topseen != None)
|
|
|
|
{
|
|
|
|
cyclic |= topseen == Temporary;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
topseen = Temporary;
|
|
|
|
|
|
|
|
// push marker for post-order processing
|
|
|
|
stack.push_back(nullptr);
|
|
|
|
path.push_back(top);
|
|
|
|
|
|
|
|
// push children
|
2022-06-17 02:05:14 +01:00
|
|
|
for (const ModuleName& dep : top->requireSet)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
auto it = sourceNodes.find(dep);
|
|
|
|
if (it != sourceNodes.end())
|
|
|
|
{
|
|
|
|
// this is a critical optimization: we do *not* traverse non-dirty subtrees.
|
|
|
|
// this relies on the fact that markDirty marks reverse-dependencies dirty as well
|
|
|
|
// thus if a node is not dirty, all its transitive deps aren't dirty, which means that they won't ever need
|
|
|
|
// to be built, *and* can't form a cycle with any nodes we did process.
|
2023-05-05 22:52:49 +01:00
|
|
|
if (!it->second->hasDirtyModule(forAutocomplete))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// This module might already be in the outside build queue
|
|
|
|
if (canSkip && canSkip(dep))
|
2021-10-29 21:25:12 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// note: this check is technically redundant *except* that getSourceNode has somewhat broken memoization
|
|
|
|
// calling getSourceNode twice in succession will reparse the file, since getSourceNode leaves dirty flag set
|
2023-05-05 22:52:49 +01:00
|
|
|
if (seen.contains(it->second.get()))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2023-05-05 22:52:49 +01:00
|
|
|
stack.push_back(it->second.get());
|
2021-10-29 21:25:12 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-04 20:53:17 +00:00
|
|
|
auto [sourceNode, _] = getSourceNode(dep);
|
2021-10-29 21:25:12 +01:00
|
|
|
if (sourceNode)
|
|
|
|
{
|
|
|
|
stack.push_back(sourceNode);
|
|
|
|
|
|
|
|
// note: this assignment is paired with .contains() check above and effectively deduplicates getSourceNode()
|
|
|
|
seen[sourceNode] = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cyclic;
|
|
|
|
}
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
void Frontend::addBuildQueueItems(std::vector<BuildQueueItem>& items, std::vector<ModuleName>& buildQueue, bool cycleDetected,
|
2023-11-10 21:10:07 +00:00
|
|
|
DenseHashSet<Luau::ModuleName>& seen, const FrontendOptions& frontendOptions)
|
2023-05-05 22:52:49 +01:00
|
|
|
{
|
|
|
|
for (const ModuleName& moduleName : buildQueue)
|
|
|
|
{
|
2023-11-10 21:10:07 +00:00
|
|
|
if (seen.contains(moduleName))
|
2023-05-05 22:52:49 +01:00
|
|
|
continue;
|
|
|
|
seen.insert(moduleName);
|
|
|
|
|
|
|
|
LUAU_ASSERT(sourceNodes.count(moduleName));
|
|
|
|
std::shared_ptr<SourceNode>& sourceNode = sourceNodes[moduleName];
|
|
|
|
|
|
|
|
if (!sourceNode->hasDirtyModule(frontendOptions.forAutocomplete))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
LUAU_ASSERT(sourceModules.count(moduleName));
|
|
|
|
std::shared_ptr<SourceModule>& sourceModule = sourceModules[moduleName];
|
|
|
|
|
|
|
|
BuildQueueItem data{moduleName, fileResolver->getHumanReadableModuleName(moduleName), sourceNode, sourceModule};
|
|
|
|
|
|
|
|
data.config = configResolver->getConfig(moduleName);
|
|
|
|
data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete);
|
2024-01-19 18:04:46 +00:00
|
|
|
data.recordJsonLog = FFlag::DebugLuauLogSolverToJson;
|
2023-05-05 22:52:49 +01:00
|
|
|
|
|
|
|
Mode mode = sourceModule->mode.value_or(data.config.mode);
|
|
|
|
|
|
|
|
// in NoCheck mode we only need to compute the value of .cyclic for typeck
|
|
|
|
// in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself
|
|
|
|
// however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term
|
|
|
|
// all correct programs must be acyclic so this code triggers rarely
|
|
|
|
if (cycleDetected)
|
|
|
|
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), mode == Mode::NoCheck);
|
|
|
|
|
|
|
|
data.options = frontendOptions;
|
|
|
|
|
|
|
|
// This is used by the type checker to replace the resulting type of cyclic modules with any
|
|
|
|
sourceModule->cyclic = !data.requireCycles.empty();
|
|
|
|
|
|
|
|
items.push_back(std::move(data));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-18 19:15:41 +01:00
|
|
|
static void applyInternalLimitScaling(SourceNode& sourceNode, const ModulePtr module, double limit)
|
|
|
|
{
|
|
|
|
if (module->timeout)
|
|
|
|
sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0;
|
|
|
|
else if (module->checkDurationSec < limit / 2.0)
|
|
|
|
sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0);
|
|
|
|
}
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
|
|
|
{
|
|
|
|
SourceNode& sourceNode = *item.sourceNode;
|
|
|
|
const SourceModule& sourceModule = *item.sourceModule;
|
|
|
|
const Config& config = item.config;
|
2024-04-05 21:45:09 +01:00
|
|
|
Mode mode;
|
|
|
|
if (FFlag::DebugLuauForceStrictMode)
|
|
|
|
mode = Mode::Strict;
|
|
|
|
else if (FFlag::DebugLuauForceNonStrictMode)
|
|
|
|
mode = Mode::Nonstrict;
|
|
|
|
else
|
|
|
|
mode = sourceModule.mode.value_or(config.mode);
|
2024-07-08 22:57:06 +01:00
|
|
|
|
|
|
|
if (FFlag::LuauSourceModuleUpdatedWithSelectedMode)
|
|
|
|
item.sourceModule->mode = {mode};
|
2023-05-05 22:52:49 +01:00
|
|
|
ScopePtr environmentScope = item.environmentScope;
|
|
|
|
double timestamp = getTimestamp();
|
|
|
|
const std::vector<RequireCycle>& requireCycles = item.requireCycles;
|
|
|
|
|
2023-08-18 19:15:41 +01:00
|
|
|
TypeCheckLimits typeCheckLimits;
|
2023-05-05 22:52:49 +01:00
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
if (item.options.moduleTimeLimitSec)
|
|
|
|
typeCheckLimits.finishTime = TimeTrace::getClock() + *item.options.moduleTimeLimitSec;
|
|
|
|
else
|
|
|
|
typeCheckLimits.finishTime = std::nullopt;
|
|
|
|
|
|
|
|
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
|
|
|
|
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
|
|
|
|
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
|
|
|
|
if (item.options.applyInternalLimitScaling)
|
2023-08-18 19:15:41 +01:00
|
|
|
{
|
2023-12-08 21:50:16 +00:00
|
|
|
if (FInt::LuauTarjanChildLimit > 0)
|
|
|
|
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
2023-05-05 22:52:49 +01:00
|
|
|
else
|
2023-12-08 21:50:16 +00:00
|
|
|
typeCheckLimits.instantiationChildLimit = std::nullopt;
|
2023-05-05 22:52:49 +01:00
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
if (FInt::LuauTypeInferIterationLimit > 0)
|
|
|
|
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
|
|
|
else
|
|
|
|
typeCheckLimits.unifierIterationLimit = std::nullopt;
|
2023-08-18 19:15:41 +01:00
|
|
|
}
|
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
|
|
|
|
2023-08-18 19:15:41 +01:00
|
|
|
if (item.options.forAutocomplete)
|
|
|
|
{
|
|
|
|
// The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features
|
2023-05-05 22:52:49 +01:00
|
|
|
ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true,
|
|
|
|
/*recordJsonLog*/ false, typeCheckLimits);
|
|
|
|
|
|
|
|
double duration = getTimestamp() - timestamp;
|
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
moduleForAutocomplete->checkDurationSec = duration;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
|
|
|
|
applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec);
|
2023-05-05 22:52:49 +01:00
|
|
|
|
|
|
|
item.stats.timeCheck += duration;
|
|
|
|
item.stats.filesStrict += 1;
|
|
|
|
|
|
|
|
item.module = moduleForAutocomplete;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-14 19:08:53 +01:00
|
|
|
ModulePtr module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, typeCheckLimits);
|
2023-05-05 22:52:49 +01:00
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
double duration = getTimestamp() - timestamp;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
module->checkDurationSec = duration;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
|
|
|
|
applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
2023-12-08 21:50:16 +00:00
|
|
|
item.stats.timeCheck += duration;
|
2023-05-05 22:52:49 +01:00
|
|
|
item.stats.filesStrict += mode == Mode::Strict;
|
|
|
|
item.stats.filesNonstrict += mode == Mode::Nonstrict;
|
|
|
|
|
|
|
|
if (module == nullptr)
|
|
|
|
throw InternalCompilerError("Frontend::check produced a nullptr module for " + item.name, item.name);
|
|
|
|
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::NoCheck)
|
|
|
|
module->errors.clear();
|
|
|
|
|
|
|
|
if (item.options.runLintChecks)
|
|
|
|
{
|
|
|
|
LUAU_TIMETRACE_SCOPE("lint", "Frontend");
|
|
|
|
|
|
|
|
LintOptions lintOptions = item.options.enabledLintWarnings.value_or(config.enabledLint);
|
|
|
|
filterLintOptions(lintOptions, sourceModule.hotcomments, mode);
|
|
|
|
|
|
|
|
double timestamp = getTimestamp();
|
|
|
|
|
|
|
|
std::vector<LintWarning> warnings =
|
|
|
|
Luau::lint(sourceModule.root, *sourceModule.names, environmentScope, module.get(), sourceModule.hotcomments, lintOptions);
|
|
|
|
|
|
|
|
item.stats.timeLint += getTimestamp() - timestamp;
|
|
|
|
|
|
|
|
module->lintResult = classifyLints(warnings, config);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!item.options.retainFullTypeGraphs)
|
|
|
|
{
|
|
|
|
// copyErrors needs to allocate into interfaceTypes as it copies
|
|
|
|
// types out of internalTypes, so we unfreeze it here.
|
|
|
|
unfreeze(module->interfaceTypes);
|
2023-08-11 15:42:37 +01:00
|
|
|
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
|
2023-05-05 22:52:49 +01:00
|
|
|
freeze(module->interfaceTypes);
|
|
|
|
|
|
|
|
module->internalTypes.clear();
|
|
|
|
|
|
|
|
module->astTypes.clear();
|
|
|
|
module->astTypePacks.clear();
|
|
|
|
module->astExpectedTypes.clear();
|
|
|
|
module->astOriginalCallTypes.clear();
|
|
|
|
module->astOverloadResolvedTypes.clear();
|
2023-06-24 07:19:39 +01:00
|
|
|
module->astForInNextTypes.clear();
|
2023-05-05 22:52:49 +01:00
|
|
|
module->astResolvedTypes.clear();
|
|
|
|
module->astResolvedTypePacks.clear();
|
2024-06-14 21:21:20 +01:00
|
|
|
module->astCompoundAssignResultTypes.clear();
|
2023-05-05 22:52:49 +01:00
|
|
|
module->astScopes.clear();
|
2023-11-10 21:10:07 +00:00
|
|
|
module->upperBoundContributors.clear();
|
2024-06-29 01:34:49 +01:00
|
|
|
module->scopes.clear();
|
2023-05-05 22:52:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mode != Mode::NoCheck)
|
|
|
|
{
|
|
|
|
for (const RequireCycle& cyc : requireCycles)
|
|
|
|
{
|
|
|
|
TypeError te{cyc.location, item.name, ModuleHasCyclicDependency{cyc.path}};
|
|
|
|
|
|
|
|
module->errors.push_back(te);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorVec parseErrors;
|
|
|
|
|
|
|
|
for (const ParseError& pe : sourceModule.parseErrors)
|
|
|
|
parseErrors.push_back(TypeError{pe.getLocation(), item.name, SyntaxError{pe.what()}});
|
|
|
|
|
|
|
|
module->errors.insert(module->errors.begin(), parseErrors.begin(), parseErrors.end());
|
|
|
|
|
|
|
|
item.module = module;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Frontend::checkBuildQueueItems(std::vector<BuildQueueItem>& items)
|
|
|
|
{
|
|
|
|
for (BuildQueueItem& item : items)
|
|
|
|
{
|
|
|
|
checkBuildQueueItem(item);
|
2023-07-14 19:08:53 +01:00
|
|
|
|
2023-08-11 15:42:37 +01:00
|
|
|
if (item.module && item.module->cancelled)
|
2023-07-14 19:08:53 +01:00
|
|
|
break;
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
recordItemResult(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Frontend::recordItemResult(const BuildQueueItem& item)
|
|
|
|
{
|
|
|
|
if (item.exception)
|
|
|
|
std::rethrow_exception(item.exception);
|
|
|
|
|
|
|
|
if (item.options.forAutocomplete)
|
|
|
|
{
|
|
|
|
moduleResolverForAutocomplete.setModule(item.name, item.module);
|
|
|
|
item.sourceNode->dirtyModuleForAutocomplete = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
moduleResolver.setModule(item.name, item.module);
|
|
|
|
item.sourceNode->dirtyModule = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
stats.timeCheck += item.stats.timeCheck;
|
|
|
|
stats.timeLint += item.stats.timeLint;
|
|
|
|
|
|
|
|
stats.filesStrict += item.stats.filesStrict;
|
|
|
|
stats.filesNonstrict += item.stats.filesNonstrict;
|
|
|
|
}
|
|
|
|
|
2023-03-10 20:21:07 +00:00
|
|
|
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-08-23 19:53:02 +01:00
|
|
|
ScopePtr result;
|
|
|
|
if (forAutocomplete)
|
2023-03-10 20:21:07 +00:00
|
|
|
result = globalsForAutocomplete.globalScope;
|
2022-08-23 19:53:02 +01:00
|
|
|
else
|
2023-03-10 20:21:07 +00:00
|
|
|
result = globals.globalScope;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (module.environmentName)
|
|
|
|
result = getEnvironmentScope(*module.environmentName);
|
|
|
|
|
|
|
|
if (!config.globals.empty())
|
|
|
|
{
|
|
|
|
result = std::make_shared<Scope>(result);
|
|
|
|
|
|
|
|
for (const std::string& global : config.globals)
|
|
|
|
{
|
|
|
|
AstName name = module.names->get(global.c_str());
|
|
|
|
|
|
|
|
if (name.value)
|
2023-04-28 20:55:13 +01:00
|
|
|
result->bindings[name].typeId = builtinTypes->anyType;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-04-07 22:29:01 +01:00
|
|
|
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
auto it = sourceNodes.find(name);
|
2023-05-05 22:52:49 +01:00
|
|
|
return it == sourceNodes.end() || it->second->hasDirtyModule(forAutocomplete);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Mark a file as requiring rechecking before its type information can be safely used again.
|
|
|
|
*
|
|
|
|
* I am not particularly pleased with the way each dirty() operation involves a BFS on reverse dependencies.
|
|
|
|
* It would be nice for this function to be O(1)
|
|
|
|
*/
|
|
|
|
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
|
|
|
{
|
2024-01-12 22:25:27 +00:00
|
|
|
if (sourceNodes.count(name) == 0)
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
|
|
|
for (const auto& module : sourceNodes)
|
|
|
|
{
|
2023-05-05 22:52:49 +01:00
|
|
|
for (const auto& dep : module.second->requireSet)
|
2021-10-29 21:25:12 +01:00
|
|
|
reverseDeps[dep].push_back(module.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ModuleName> queue{name};
|
|
|
|
|
|
|
|
while (!queue.empty())
|
|
|
|
{
|
|
|
|
ModuleName next = std::move(queue.back());
|
|
|
|
queue.pop_back();
|
|
|
|
|
|
|
|
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
2023-05-05 22:52:49 +01:00
|
|
|
SourceNode& sourceNode = *sourceNodes[next];
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (markedDirty)
|
|
|
|
markedDirty->push_back(next);
|
|
|
|
|
2022-05-26 23:08:16 +01:00
|
|
|
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
|
|
|
continue;
|
2022-04-07 22:29:01 +01:00
|
|
|
|
2022-05-26 23:08:16 +01:00
|
|
|
sourceNode.dirtySourceModule = true;
|
|
|
|
sourceNode.dirtyModule = true;
|
|
|
|
sourceNode.dirtyModuleForAutocomplete = true;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-12-09 19:57:01 +00:00
|
|
|
if (0 == reverseDeps.count(next))
|
|
|
|
continue;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-12-09 19:57:01 +00:00
|
|
|
sourceModules.erase(next);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-12-09 19:57:01 +00:00
|
|
|
const std::vector<ModuleName>& dependents = reverseDeps[next];
|
|
|
|
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SourceModule* Frontend::getSourceModule(const ModuleName& moduleName)
|
|
|
|
{
|
|
|
|
auto it = sourceModules.find(moduleName);
|
|
|
|
if (it != sourceModules.end())
|
2023-05-05 22:52:49 +01:00
|
|
|
return it->second.get();
|
2021-10-29 21:25:12 +01:00
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const
|
|
|
|
{
|
|
|
|
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
2023-03-03 20:21:14 +00:00
|
|
|
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
2023-07-28 16:13:53 +01:00
|
|
|
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
2024-01-19 18:04:46 +00:00
|
|
|
TypeCheckLimits limits, std::function<void(const ModuleName&, std::string)> writeJsonLog)
|
2023-03-03 20:21:14 +00:00
|
|
|
{
|
|
|
|
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
|
2023-10-13 21:20:12 +01:00
|
|
|
return check(sourceModule, mode, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope,
|
2024-01-19 18:04:46 +00:00
|
|
|
std::move(prepareModuleScope), options, limits, recordJsonLog, writeJsonLog);
|
2023-03-03 20:21:14 +00:00
|
|
|
}
|
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
struct InternalTypeFinder : TypeOnceVisitor
|
|
|
|
{
|
|
|
|
bool visit(TypeId, const ClassType&) override
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId, const BlockedType&) override
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId, const FreeType&) override
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId, const PendingExpansionType&) override
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypePackId, const BlockedTypePack&) override
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypePackId, const FreeTypePack&) override
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypePackId, const TypeFamilyInstanceTypePack&) override
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
2023-03-03 20:21:14 +00:00
|
|
|
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
2023-04-28 20:55:13 +01:00
|
|
|
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
2024-01-19 18:04:46 +00:00
|
|
|
TypeCheckLimits limits, bool recordJsonLog, std::function<void(const ModuleName&, std::string)> writeJsonLog)
|
2023-03-03 20:21:14 +00:00
|
|
|
{
|
2022-06-03 23:15:45 +01:00
|
|
|
ModulePtr result = std::make_shared<Module>();
|
2023-04-21 23:14:26 +01:00
|
|
|
result->name = sourceModule.name;
|
|
|
|
result->humanReadableName = sourceModule.humanReadableName;
|
2024-07-08 22:57:06 +01:00
|
|
|
result->mode = mode;
|
2023-07-28 16:13:53 +01:00
|
|
|
result->internalTypes.owningModule = result.get();
|
|
|
|
result->interfaceTypes.owningModule = result.get();
|
|
|
|
|
2023-06-16 18:35:18 +01:00
|
|
|
iceHandler->moduleName = sourceModule.name;
|
|
|
|
|
2022-09-08 23:14:25 +01:00
|
|
|
std::unique_ptr<DcrLogger> logger;
|
2023-03-03 20:21:14 +00:00
|
|
|
if (recordJsonLog)
|
2022-09-08 23:14:25 +01:00
|
|
|
{
|
|
|
|
logger = std::make_unique<DcrLogger>();
|
2023-04-21 23:14:26 +01:00
|
|
|
std::optional<SourceCode> source = fileResolver->readSource(result->name);
|
2022-09-08 23:14:25 +01:00
|
|
|
if (source)
|
|
|
|
{
|
|
|
|
logger->captureSource(source->source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-17 23:41:51 +00:00
|
|
|
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler);
|
2022-10-21 18:54:01 +01:00
|
|
|
|
2023-03-03 20:21:14 +00:00
|
|
|
UnifierSharedState unifierState{iceHandler};
|
|
|
|
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
2023-07-28 16:13:53 +01:00
|
|
|
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
|
2023-03-03 20:21:14 +00:00
|
|
|
|
|
|
|
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
|
2022-10-07 01:23:29 +01:00
|
|
|
|
2023-11-03 23:45:04 +00:00
|
|
|
ConstraintGenerator cg{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
2023-07-28 16:13:53 +01:00
|
|
|
logger.get(), NotNull{&dfg}, requireCycles};
|
2022-10-21 18:54:01 +01:00
|
|
|
|
2023-11-03 23:45:04 +00:00
|
|
|
cg.visitModuleRoot(sourceModule.root);
|
|
|
|
result->errors = std::move(cg.errors);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cg.rootScope), borrowConstraints(cg.constraints), result->name, moduleResolver, requireCycles,
|
|
|
|
logger.get(), limits};
|
2022-10-07 01:23:29 +01:00
|
|
|
|
|
|
|
if (options.randomizeConstraintResolutionSeed)
|
|
|
|
cs.randomize(*options.randomizeConstraintResolutionSeed);
|
|
|
|
|
2023-07-28 16:13:53 +01:00
|
|
|
try
|
|
|
|
{
|
|
|
|
cs.run();
|
|
|
|
}
|
|
|
|
catch (const TimeLimitError&)
|
|
|
|
{
|
|
|
|
result->timeout = true;
|
|
|
|
}
|
|
|
|
catch (const UserCancelError&)
|
|
|
|
{
|
|
|
|
result->cancelled = true;
|
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2024-02-23 20:08:34 +00:00
|
|
|
if (recordJsonLog)
|
|
|
|
{
|
|
|
|
std::string output = logger->compileOutput();
|
|
|
|
if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
|
|
|
|
writeJsonLog(sourceModule.name, std::move(output));
|
|
|
|
else
|
|
|
|
printf("%s\n", output.c_str());
|
|
|
|
}
|
|
|
|
|
2022-08-18 22:32:08 +01:00
|
|
|
for (TypeError& e : cs.errors)
|
|
|
|
result->errors.emplace_back(std::move(e));
|
Sync to upstream/release/562 (#828)
* Fixed rare use-after-free in analysis during table unification
A lot of work these past months went into two new Luau components:
* A near full rewrite of the typechecker using a new deferred constraint
resolution system
* Native code generation for AoT/JiT compilation of VM bytecode into x64
(avx)/arm64 instructions
Both of these components are far from finished and we don't provide
documentation on building and using them at this point.
However, curious community members expressed interest in learning about
changes that go into these components each week, so we are now listing
them here in the 'sync' pull request descriptions.
---
New typechecker can be enabled by setting
DebugLuauDeferredConstraintResolution flag to 'true'.
It is considered unstable right now, so try it at your own risk.
Even though it already provides better type inference than the current
one in some cases, our main goal right now is to reach feature parity
with current typechecker.
Features which improve over the capabilities of the current typechecker
are marked as '(NEW)'.
Changes to new typechecker:
* Regular for loop index and parameters are now typechecked
* Invalid type annotations on local variables are ignored to improve
autocomplete
* Fixed missing autocomplete type suggestions for function arguments
* Type reduction is now performed to produce simpler types to be
presented to the user (error messages, custom LSPs)
* Internally, complex types like '((number | string) & ~(false?)) |
string' can be produced, which is just 'string | number' when simplified
* Fixed spots where support for unknown and never types was missing
* (NEW) Length operator '#' is now valid to use on top table type, this
type comes up when doing typeof(x) == "table" guards and isn't available
in current typechecker
---
Changes to native code generation:
* Additional math library fast calls are now lowered to x64: math.ldexp,
math.round, math.frexp, math.modf, math.sign and math.clamp
2023-02-03 19:26:13 +00:00
|
|
|
|
2023-11-03 23:45:04 +00:00
|
|
|
result->scopes = std::move(cg.scopes);
|
2022-09-02 00:14:03 +01:00
|
|
|
result->type = sourceModule.type;
|
2023-11-10 21:10:07 +00:00
|
|
|
result->upperBoundContributors = std::move(cs.upperBoundContributors);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
if (result->timeout || result->cancelled)
|
|
|
|
{
|
|
|
|
// If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending
|
|
|
|
// types
|
|
|
|
ScopePtr moduleScope = result->getModuleScope();
|
|
|
|
moduleScope->returnType = builtinTypes->errorRecoveryTypePack();
|
|
|
|
|
|
|
|
for (auto& [name, ty] : result->declaredGlobals)
|
|
|
|
ty = builtinTypes->errorRecoveryType();
|
|
|
|
|
|
|
|
for (auto& [name, tf] : result->exportedTypeBindings)
|
|
|
|
tf.type = builtinTypes->errorRecoveryType();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-07-08 22:57:06 +01:00
|
|
|
switch (mode)
|
|
|
|
{
|
|
|
|
case Mode::Nonstrict:
|
2024-05-10 19:21:45 +01:00
|
|
|
Luau::checkNonStrict(builtinTypes, iceHandler, NotNull{&unifierState}, NotNull{&dfg}, NotNull{&limits}, sourceModule, result.get());
|
2024-07-08 22:57:06 +01:00
|
|
|
break;
|
|
|
|
case Mode::Definition:
|
|
|
|
// fallthrough intentional
|
|
|
|
case Mode::Strict:
|
2024-05-10 19:21:45 +01:00
|
|
|
Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get());
|
2024-07-08 22:57:06 +01:00
|
|
|
break;
|
|
|
|
case Mode::NoCheck:
|
|
|
|
break;
|
|
|
|
};
|
2024-05-10 19:21:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
unfreeze(result->interfaceTypes);
|
|
|
|
result->clonePublicInterface(builtinTypes, *iceHandler);
|
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
if (FFlag::DebugLuauForbidInternalTypes)
|
|
|
|
{
|
|
|
|
InternalTypeFinder finder;
|
|
|
|
|
|
|
|
finder.traverse(result->returnType);
|
|
|
|
|
|
|
|
for (const auto& [_, binding] : result->exportedTypeBindings)
|
|
|
|
finder.traverse(binding.type);
|
|
|
|
|
|
|
|
for (const auto& [_, ty] : result->astTypes)
|
|
|
|
finder.traverse(ty);
|
|
|
|
|
|
|
|
for (const auto& [_, ty] : result->astExpectedTypes)
|
|
|
|
finder.traverse(ty);
|
|
|
|
|
|
|
|
for (const auto& [_, tp] : result->astTypePacks)
|
|
|
|
finder.traverse(tp);
|
|
|
|
|
|
|
|
for (const auto& [_, ty] : result->astResolvedTypes)
|
|
|
|
finder.traverse(ty);
|
|
|
|
|
|
|
|
for (const auto& [_, ty] : result->astOverloadResolvedTypes)
|
|
|
|
finder.traverse(ty);
|
|
|
|
|
|
|
|
for (const auto& [_, tp] : result->astResolvedTypePacks)
|
|
|
|
finder.traverse(tp);
|
|
|
|
}
|
2023-01-04 20:53:17 +00:00
|
|
|
|
2023-05-19 20:37:30 +01:00
|
|
|
// It would be nice if we could freeze the arenas before doing type
|
|
|
|
// checking, but we'll have to do some work to get there.
|
Sync to upstream/release/562 (#828)
* Fixed rare use-after-free in analysis during table unification
A lot of work these past months went into two new Luau components:
* A near full rewrite of the typechecker using a new deferred constraint
resolution system
* Native code generation for AoT/JiT compilation of VM bytecode into x64
(avx)/arm64 instructions
Both of these components are far from finished and we don't provide
documentation on building and using them at this point.
However, curious community members expressed interest in learning about
changes that go into these components each week, so we are now listing
them here in the 'sync' pull request descriptions.
---
New typechecker can be enabled by setting
DebugLuauDeferredConstraintResolution flag to 'true'.
It is considered unstable right now, so try it at your own risk.
Even though it already provides better type inference than the current
one in some cases, our main goal right now is to reach feature parity
with current typechecker.
Features which improve over the capabilities of the current typechecker
are marked as '(NEW)'.
Changes to new typechecker:
* Regular for loop index and parameters are now typechecked
* Invalid type annotations on local variables are ignored to improve
autocomplete
* Fixed missing autocomplete type suggestions for function arguments
* Type reduction is now performed to produce simpler types to be
presented to the user (error messages, custom LSPs)
* Internally, complex types like '((number | string) & ~(false?)) |
string' can be produced, which is just 'string | number' when simplified
* Fixed spots where support for unknown and never types was missing
* (NEW) Length operator '#' is now valid to use on top table type, this
type comes up when doing typeof(x) == "table" guards and isn't available
in current typechecker
---
Changes to native code generation:
* Additional math library fast calls are now lowered to x64: math.ldexp,
math.round, math.frexp, math.modf, math.sign and math.clamp
2023-02-03 19:26:13 +00:00
|
|
|
//
|
2023-05-19 20:37:30 +01:00
|
|
|
// TypeChecker2 sometimes needs to allocate TypePacks via extendTypePack()
|
|
|
|
// in order to do its thing. We can rework that code to instead allocate
|
|
|
|
// into a temporary arena as long as we can prove that the allocated types
|
|
|
|
// and packs can never find their way into an error.
|
|
|
|
//
|
|
|
|
// Notably, we would first need to get to a place where TypeChecker2 is
|
|
|
|
// never in the position of dealing with a FreeType. They should all be
|
|
|
|
// bound to something by the time constraints are solved.
|
2023-01-04 20:53:17 +00:00
|
|
|
freeze(result->internalTypes);
|
|
|
|
freeze(result->interfaceTypes);
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-04-14 19:06:22 +01:00
|
|
|
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles,
|
|
|
|
std::optional<ScopePtr> environmentScope, bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits)
|
2023-02-17 23:41:51 +00:00
|
|
|
{
|
2023-10-13 21:20:12 +01:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
2023-04-14 19:06:22 +01:00
|
|
|
{
|
2023-04-28 20:55:13 +01:00
|
|
|
auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) {
|
|
|
|
if (prepareModuleScope)
|
|
|
|
prepareModuleScope(name, scope, forAutocomplete);
|
|
|
|
};
|
|
|
|
|
2023-06-16 18:35:18 +01:00
|
|
|
try
|
|
|
|
{
|
2023-10-13 21:20:12 +01:00
|
|
|
return Luau::check(sourceModule, mode, requireCycles, builtinTypes, NotNull{&iceHandler},
|
2023-06-16 18:35:18 +01:00
|
|
|
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
2024-02-16 02:04:39 +00:00
|
|
|
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog,
|
|
|
|
writeJsonLog);
|
2023-06-16 18:35:18 +01:00
|
|
|
}
|
|
|
|
catch (const InternalCompilerError& err)
|
|
|
|
{
|
2024-03-30 23:14:44 +00:00
|
|
|
InternalCompilerError augmented = err.location.has_value() ? InternalCompilerError{err.message, sourceModule.name, *err.location}
|
|
|
|
: InternalCompilerError{err.message, sourceModule.name};
|
2023-06-16 18:35:18 +01:00
|
|
|
throw augmented;
|
|
|
|
}
|
2023-04-14 19:06:22 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-09 18:08:00 +01:00
|
|
|
TypeChecker typeChecker(forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope,
|
2023-05-11 12:35:56 +01:00
|
|
|
forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver, builtinTypes, &iceHandler);
|
2023-04-14 19:06:22 +01:00
|
|
|
|
|
|
|
if (prepareModuleScope)
|
|
|
|
{
|
|
|
|
typeChecker.prepareModuleScope = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) {
|
|
|
|
prepareModuleScope(name, scope, forAutocomplete);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
typeChecker.requireCycles = requireCycles;
|
|
|
|
typeChecker.finishTime = typeCheckLimits.finishTime;
|
|
|
|
typeChecker.instantiationChildLimit = typeCheckLimits.instantiationChildLimit;
|
|
|
|
typeChecker.unifierIterationLimit = typeCheckLimits.unifierIterationLimit;
|
2023-08-11 15:42:37 +01:00
|
|
|
typeChecker.cancellationToken = typeCheckLimits.cancellationToken;
|
2023-07-14 19:08:53 +01:00
|
|
|
|
2023-04-14 19:06:22 +01:00
|
|
|
return typeChecker.check(sourceModule, mode, environmentScope);
|
|
|
|
}
|
2023-02-17 23:41:51 +00:00
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
2023-01-04 20:53:17 +00:00
|
|
|
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName& name)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
auto it = sourceNodes.find(name);
|
2023-05-05 22:52:49 +01:00
|
|
|
if (it != sourceNodes.end() && !it->second->hasDirtySourceModule())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
auto moduleIt = sourceModules.find(name);
|
|
|
|
if (moduleIt != sourceModules.end())
|
2023-05-05 22:52:49 +01:00
|
|
|
return {it->second.get(), moduleIt->second.get()};
|
2021-10-29 21:25:12 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!"Everything in sourceNodes should also be in sourceModules");
|
2023-05-05 22:52:49 +01:00
|
|
|
return {it->second.get(), nullptr};
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
|
|
|
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
double timestamp = getTimestamp();
|
|
|
|
|
|
|
|
std::optional<SourceCode> source = fileResolver->readSource(name);
|
|
|
|
std::optional<std::string> environmentName = fileResolver->getEnvironmentForModule(name);
|
|
|
|
|
|
|
|
stats.timeRead += getTimestamp() - timestamp;
|
|
|
|
|
|
|
|
if (!source)
|
|
|
|
{
|
|
|
|
sourceModules.erase(name);
|
|
|
|
return {nullptr, nullptr};
|
|
|
|
}
|
|
|
|
|
|
|
|
const Config& config = configResolver->getConfig(name);
|
|
|
|
ParseOptions opts = config.parseOptions;
|
|
|
|
opts.captureComments = true;
|
|
|
|
SourceModule result = parse(name, source->source, opts);
|
|
|
|
result.type = source->type;
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
RequireTraceResult& require = requireTrace[name];
|
|
|
|
require = traceRequires(fileResolver, result.root, name);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
std::shared_ptr<SourceNode>& sourceNode = sourceNodes[name];
|
|
|
|
|
|
|
|
if (!sourceNode)
|
|
|
|
sourceNode = std::make_shared<SourceNode>();
|
|
|
|
|
|
|
|
std::shared_ptr<SourceModule>& sourceModule = sourceModules[name];
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
if (!sourceModule)
|
|
|
|
sourceModule = std::make_shared<SourceModule>();
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
*sourceModule = std::move(result);
|
|
|
|
sourceModule->environmentName = environmentName;
|
|
|
|
|
|
|
|
sourceNode->name = sourceModule->name;
|
|
|
|
sourceNode->humanReadableName = sourceModule->humanReadableName;
|
|
|
|
sourceNode->requireSet.clear();
|
|
|
|
sourceNode->requireLocations.clear();
|
|
|
|
sourceNode->dirtySourceModule = false;
|
2022-04-07 22:29:01 +01:00
|
|
|
|
2022-05-26 23:08:16 +01:00
|
|
|
if (it == sourceNodes.end())
|
2022-04-07 22:29:01 +01:00
|
|
|
{
|
2023-05-05 22:52:49 +01:00
|
|
|
sourceNode->dirtyModule = true;
|
|
|
|
sourceNode->dirtyModuleForAutocomplete = true;
|
2022-04-07 22:29:01 +01:00
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
for (const auto& [moduleName, location] : require.requireList)
|
2023-05-05 22:52:49 +01:00
|
|
|
sourceNode->requireSet.insert(moduleName);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
sourceNode->requireLocations = require.requireList;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-05-05 22:52:49 +01:00
|
|
|
return {sourceNode.get(), sourceModule.get()};
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Try to parse a source file into a SourceModule.
|
|
|
|
*
|
|
|
|
* The logic here is a little bit more complicated than we'd like it to be.
|
|
|
|
*
|
|
|
|
* If a file does not exist, we return none to prevent the Frontend from creating knowledge that this module exists.
|
|
|
|
* If the Frontend thinks that the file exists, it will not produce an "Unknown require" error.
|
|
|
|
*
|
|
|
|
* If the file has syntax errors, we report them and synthesize an empty AST if it's not available.
|
|
|
|
* This suppresses the Unknown require error and allows us to make a best effort to typecheck code that require()s
|
|
|
|
* something that has broken syntax.
|
|
|
|
* We also translate Luau::ParseError into a Luau::TypeError so that we can use a vector<TypeError> to describe the
|
|
|
|
* result of the check()
|
|
|
|
*/
|
|
|
|
SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions)
|
|
|
|
{
|
2021-11-05 02:34:35 +00:00
|
|
|
LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend");
|
|
|
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
SourceModule sourceModule;
|
|
|
|
|
|
|
|
double timestamp = getTimestamp();
|
|
|
|
|
2022-10-28 11:37:29 +01:00
|
|
|
Luau::ParseResult parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
stats.timeParse += getTimestamp() - timestamp;
|
|
|
|
stats.files++;
|
2022-10-28 11:37:29 +01:00
|
|
|
stats.lines += parseResult.lines;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (!parseResult.errors.empty())
|
|
|
|
sourceModule.parseErrors.insert(sourceModule.parseErrors.end(), parseResult.errors.begin(), parseResult.errors.end());
|
|
|
|
|
|
|
|
if (parseResult.errors.empty() || parseResult.root)
|
|
|
|
{
|
|
|
|
sourceModule.root = parseResult.root;
|
|
|
|
sourceModule.mode = parseMode(parseResult.hotcomments);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sourceModule.root = sourceModule.allocator->alloc<AstStatBlock>(Location{}, AstArray<AstStat*>{nullptr, 0});
|
|
|
|
sourceModule.mode = Mode::NoCheck;
|
|
|
|
}
|
|
|
|
|
|
|
|
sourceModule.name = name;
|
2023-04-21 23:14:26 +01:00
|
|
|
sourceModule.humanReadableName = fileResolver->getHumanReadableModuleName(name);
|
2022-02-18 01:18:01 +00:00
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
if (parseOptions.captureComments)
|
2022-02-18 01:18:01 +00:00
|
|
|
{
|
2021-10-29 21:25:12 +01:00
|
|
|
sourceModule.commentLocations = std::move(parseResult.commentLocations);
|
2022-02-18 01:18:01 +00:00
|
|
|
sourceModule.hotcomments = std::move(parseResult.hotcomments);
|
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
return sourceModule;
|
|
|
|
}
|
|
|
|
|
2023-04-21 23:14:26 +01:00
|
|
|
|
|
|
|
FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
|
|
|
|
: frontend(frontend)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
|
|
|
{
|
|
|
|
// FIXME I think this can be pushed into the FileResolver.
|
2022-06-17 02:05:14 +01:00
|
|
|
auto it = frontend->requireTrace.find(currentModuleName);
|
|
|
|
if (it == frontend->requireTrace.end())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// CLI-43699
|
|
|
|
// If we can't find the current module name, that's because we bypassed the frontend's initializer
|
2022-07-14 23:52:26 +01:00
|
|
|
// and called typeChecker.check directly.
|
2021-10-29 21:25:12 +01:00
|
|
|
// In that case, requires will always fail.
|
2021-12-03 06:41:04 +00:00
|
|
|
return std::nullopt;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const auto& exprs = it->second.exprs;
|
|
|
|
|
2021-11-05 02:34:35 +00:00
|
|
|
const ModuleInfo* info = exprs.find(&pathExpr);
|
2021-12-03 06:41:04 +00:00
|
|
|
if (!info)
|
2021-10-29 21:25:12 +01:00
|
|
|
return std::nullopt;
|
|
|
|
|
2021-11-05 02:34:35 +00:00
|
|
|
return *info;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const ModulePtr FrontendModuleResolver::getModule(const ModuleName& moduleName) const
|
|
|
|
{
|
2023-04-21 23:14:26 +01:00
|
|
|
std::scoped_lock lock(moduleMutex);
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
auto it = modules.find(moduleName);
|
|
|
|
if (it != modules.end())
|
|
|
|
return it->second;
|
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FrontendModuleResolver::moduleExists(const ModuleName& moduleName) const
|
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
return frontend->sourceNodes.count(moduleName) != 0;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const
|
|
|
|
{
|
2021-11-05 02:34:35 +00:00
|
|
|
return frontend->fileResolver->getHumanReadableModuleName(moduleName);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2023-04-21 23:14:26 +01:00
|
|
|
void FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module)
|
|
|
|
{
|
|
|
|
std::scoped_lock lock(moduleMutex);
|
|
|
|
|
|
|
|
modules[moduleName] = std::move(module);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FrontendModuleResolver::clearModules()
|
|
|
|
{
|
|
|
|
std::scoped_lock lock(moduleMutex);
|
|
|
|
|
|
|
|
modules.clear();
|
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
ScopePtr Frontend::addEnvironment(const std::string& environmentName)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(environments.count(environmentName) == 0);
|
|
|
|
|
|
|
|
if (environments.count(environmentName) == 0)
|
|
|
|
{
|
2023-03-10 20:21:07 +00:00
|
|
|
ScopePtr scope = std::make_shared<Scope>(globals.globalScope);
|
2021-10-29 21:25:12 +01:00
|
|
|
environments[environmentName] = scope;
|
|
|
|
return scope;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return environments[environmentName];
|
|
|
|
}
|
|
|
|
|
2023-03-10 20:21:07 +00:00
|
|
|
ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName) const
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2023-03-10 20:21:07 +00:00
|
|
|
if (auto it = environments.find(environmentName); it != environments.end())
|
|
|
|
return it->second;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-03-10 20:21:07 +00:00
|
|
|
LUAU_ASSERT(!"environment doesn't exist");
|
|
|
|
return {};
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(Frontend&, GlobalTypes&, ScopePtr)> applicator)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(builtinDefinitions.count(name) == 0);
|
|
|
|
|
|
|
|
if (builtinDefinitions.count(name) == 0)
|
|
|
|
builtinDefinitions[name] = applicator;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(builtinDefinitions.count(definitionName) > 0);
|
|
|
|
|
|
|
|
if (builtinDefinitions.count(definitionName) > 0)
|
2023-04-07 22:01:29 +01:00
|
|
|
builtinDefinitions[definitionName](*this, globals, getEnvironmentScope(environmentName));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
LintResult Frontend::classifyLints(const std::vector<LintWarning>& warnings, const Config& config)
|
|
|
|
{
|
|
|
|
LintResult result;
|
|
|
|
for (const auto& w : warnings)
|
|
|
|
{
|
|
|
|
if (config.lintErrors || config.fatalLint.isEnabled(w.code))
|
|
|
|
result.errors.push_back(w);
|
|
|
|
else
|
|
|
|
result.warnings.push_back(w);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Frontend::clearStats()
|
|
|
|
{
|
|
|
|
stats = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
void Frontend::clear()
|
|
|
|
{
|
|
|
|
sourceNodes.clear();
|
|
|
|
sourceModules.clear();
|
2023-04-21 23:14:26 +01:00
|
|
|
moduleResolver.clearModules();
|
|
|
|
moduleResolverForAutocomplete.clearModules();
|
2022-06-17 02:05:14 +01:00
|
|
|
requireTrace.clear();
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|