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"
|
2022-06-03 23:15:45 +01:00
|
|
|
#include "Luau/ConstraintGraphBuilder.h"
|
|
|
|
#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"
|
2022-06-17 02:05:14 +01:00
|
|
|
#include "Luau/TypeChecker2.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
#include "Luau/TypeInfer.h"
|
2023-01-06 21:14:35 +00:00
|
|
|
#include "Luau/TypeReduction.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
#include "Luau/Variant.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <chrono>
|
|
|
|
#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)
|
2022-04-29 02:24:24 +01:00
|
|
|
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
2022-06-03 23:15:45 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
2023-04-14 19:06:22 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauOnDemandTypecheckers, false)
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
{
|
2022-08-04 23:35:33 +01:00
|
|
|
CloneState cloneState;
|
|
|
|
|
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
|
|
|
{
|
2023-04-14 19:06:22 +01:00
|
|
|
if (!FFlag::DebugLuauDeferredConstraintResolution && !FFlag::LuauOnDemandTypecheckers)
|
|
|
|
return Luau::loadDefinitionFileNoDCR(typeCheckForAutocomplete ? typeCheckerForAutocomplete_DEPRECATED : typeChecker_DEPRECATED,
|
2023-04-07 22:01:29 +01:00
|
|
|
typeCheckForAutocomplete ? globalsForAutocomplete : globals, targetScope, source, packageName, captureComments);
|
|
|
|
|
2023-03-10 20:21:07 +00:00
|
|
|
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
|
|
|
|
|
|
|
Luau::SourceModule sourceModule;
|
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
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
LoadDefinitionFileResult loadDefinitionFileNoDCR(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
|
|
|
|
const std::string& packageName, bool captureComments)
|
|
|
|
{
|
2023-04-14 19:06:22 +01:00
|
|
|
LUAU_ASSERT(!FFlag::LuauOnDemandTypecheckers);
|
2023-04-07 22:01:29 +01:00
|
|
|
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
2023-03-10 20:21:07 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
Luau::SourceModule sourceModule;
|
|
|
|
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
|
2023-03-10 20:21:07 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
if (parseResult.errors.size() > 0)
|
|
|
|
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
|
2023-03-10 20:21:07 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
ModulePtr checkedModule = typeChecker.check(sourceModule, Mode::Definition);
|
2023-03-10 20:21:07 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
if (checkedModule->errors.size() > 0)
|
|
|
|
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
|
|
|
|
|
|
|
|
persistCheckedTypes(checkedModule, globals, targetScope, packageName);
|
2023-03-10 20:21:07 +00:00
|
|
|
|
|
|
|
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
|
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(
|
2021-10-29 21:25:12 +01:00
|
|
|
const std::unordered_map<ModuleName, SourceNode>& sourceNodes, const std::unordered_map<ModuleName, ModulePtr>& modules, const ModuleName& name)
|
|
|
|
{
|
|
|
|
std::unordered_set<ModuleName> seen;
|
|
|
|
std::vector<ModuleName> queue{name};
|
|
|
|
|
|
|
|
ErrorVec result;
|
|
|
|
|
|
|
|
while (!queue.empty())
|
|
|
|
{
|
|
|
|
ModuleName next = std::move(queue.back());
|
|
|
|
queue.pop_back();
|
|
|
|
|
|
|
|
if (seen.count(next))
|
|
|
|
continue;
|
|
|
|
seen.insert(next);
|
|
|
|
|
|
|
|
auto it = sourceNodes.find(next);
|
|
|
|
if (it == sourceNodes.end())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
auto it2 = modules.find(next);
|
|
|
|
if (it2 == modules.end())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Module& module = *it2->second;
|
|
|
|
|
|
|
|
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)
|
|
|
|
std::vector<RequireCycle> getRequireCycles(
|
2021-11-05 15:47:21 +00:00
|
|
|
const FileResolver* resolver, const std::unordered_map<ModuleName, 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;
|
|
|
|
|
|
|
|
stack.push_back(&dit->second);
|
|
|
|
|
|
|
|
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)
|
2021-11-05 15:47:21 +00:00
|
|
|
cycle.push_back(resolver->getHumanReadableModuleName(node->name));
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-11-05 15:47:21 +00:00
|
|
|
cycle.push_back(resolver->getHumanReadableModuleName(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())
|
|
|
|
stack.push_back(&rit->second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2023-04-14 19:06:22 +01:00
|
|
|
, typeChecker_DEPRECATED(globals.globalScope, &moduleResolver, builtinTypes, &iceHandler)
|
|
|
|
, typeCheckerForAutocomplete_DEPRECATED(globalsForAutocomplete.globalScope, &moduleResolverForAutocomplete, builtinTypes, &iceHandler)
|
2021-10-29 21:25:12 +01:00
|
|
|
, configResolver(configResolver)
|
|
|
|
, options(options)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
|
|
|
|
: frontend(frontend)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-11-05 02:34:35 +00:00
|
|
|
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend");
|
|
|
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
|
|
|
|
2022-04-07 22:29:01 +01:00
|
|
|
FrontendOptions frontendOptions = optionOverride.value_or(options);
|
2021-10-29 21:25:12 +01:00
|
|
|
CheckResult checkResult;
|
|
|
|
|
|
|
|
auto it = sourceNodes.find(name);
|
2022-04-21 22:44:27 +01:00
|
|
|
if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// No recheck required.
|
2022-05-26 23:08:16 +01:00
|
|
|
if (frontendOptions.forAutocomplete)
|
2022-04-07 22:29:01 +01:00
|
|
|
{
|
2022-05-26 23:08:16 +01:00
|
|
|
auto it2 = moduleResolverForAutocomplete.modules.find(name);
|
|
|
|
if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
|
2022-12-02 18:09:59 +00:00
|
|
|
throw InternalCompilerError("Frontend::modules does not have data for " + name, name);
|
2022-04-07 22:29:01 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto it2 = moduleResolver.modules.find(name);
|
|
|
|
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
|
2022-12-02 18:09:59 +00:00
|
|
|
throw InternalCompilerError("Frontend::modules does not have data for " + name, name);
|
2022-04-07 22:29:01 +01:00
|
|
|
}
|
2022-05-26 23:08:16 +01:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
std::unordered_map<ModuleName, ModulePtr>& modules =
|
|
|
|
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
|
2023-03-17 19:20:37 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
checkResult.errors = accumulateErrors(sourceNodes, modules, name);
|
2023-03-17 19:20:37 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
// Get lint result only for top checked module
|
|
|
|
if (auto it = modules.find(name); it != modules.end())
|
|
|
|
checkResult.lintResult = it->second->lintResult;
|
2023-03-17 19:20:37 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
return checkResult;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ModuleName> buildQueue;
|
2023-01-04 20:53:17 +00:00
|
|
|
bool cycleDetected = parseGraph(buildQueue, name, frontendOptions.forAutocomplete);
|
2022-04-07 22:29:01 +01:00
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
for (const ModuleName& moduleName : buildQueue)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(sourceNodes.count(moduleName));
|
|
|
|
SourceNode& sourceNode = sourceNodes[moduleName];
|
|
|
|
|
2022-04-21 22:44:27 +01:00
|
|
|
if (!sourceNode.hasDirtyModule(frontendOptions.forAutocomplete))
|
2021-10-29 21:25:12 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
LUAU_ASSERT(sourceModules.count(moduleName));
|
|
|
|
SourceModule& sourceModule = sourceModules[moduleName];
|
|
|
|
|
|
|
|
const Config& config = configResolver->getConfig(moduleName);
|
|
|
|
|
|
|
|
Mode mode = sourceModule.mode.value_or(config.mode);
|
|
|
|
|
2022-08-23 19:53:02 +01:00
|
|
|
ScopePtr environmentScope = getModuleEnvironment(sourceModule, config, frontendOptions.forAutocomplete);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
double timestamp = getTimestamp();
|
|
|
|
|
|
|
|
std::vector<RequireCycle> requireCycles;
|
|
|
|
|
|
|
|
// 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)
|
2021-11-05 15:47:21 +00:00
|
|
|
requireCycles = getRequireCycles(fileResolver, sourceNodes, &sourceNode, mode == Mode::NoCheck);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
// This is used by the type checker to replace the resulting type of cyclic modules with any
|
|
|
|
sourceModule.cyclic = !requireCycles.empty();
|
|
|
|
|
2022-05-26 23:08:16 +01:00
|
|
|
if (frontendOptions.forAutocomplete)
|
2022-04-07 22:29:01 +01:00
|
|
|
{
|
2023-04-14 19:06:22 +01:00
|
|
|
ModulePtr moduleForAutocomplete;
|
2022-04-07 22:29:01 +01:00
|
|
|
|
2023-01-04 20:53:17 +00:00
|
|
|
double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0;
|
|
|
|
|
2023-04-14 19:06:22 +01:00
|
|
|
if (!FFlag::LuauOnDemandTypecheckers)
|
|
|
|
{
|
|
|
|
// The autocomplete typecheck is always in strict mode with DM awareness
|
|
|
|
// to provide better type information for IDE features
|
|
|
|
typeCheckerForAutocomplete_DEPRECATED.requireCycles = requireCycles;
|
|
|
|
|
|
|
|
if (autocompleteTimeLimit != 0.0)
|
|
|
|
typeCheckerForAutocomplete_DEPRECATED.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
|
|
|
|
else
|
|
|
|
typeCheckerForAutocomplete_DEPRECATED.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 (FInt::LuauTarjanChildLimit > 0)
|
|
|
|
typeCheckerForAutocomplete_DEPRECATED.instantiationChildLimit =
|
|
|
|
std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
|
|
|
else
|
|
|
|
typeCheckerForAutocomplete_DEPRECATED.instantiationChildLimit = std::nullopt;
|
|
|
|
|
|
|
|
if (FInt::LuauTypeInferIterationLimit > 0)
|
|
|
|
typeCheckerForAutocomplete_DEPRECATED.unifierIterationLimit =
|
|
|
|
std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
|
|
|
else
|
|
|
|
typeCheckerForAutocomplete_DEPRECATED.unifierIterationLimit = std::nullopt;
|
|
|
|
|
|
|
|
moduleForAutocomplete =
|
|
|
|
FFlag::DebugLuauDeferredConstraintResolution
|
|
|
|
? check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true, /*recordJsonLog*/ false, {})
|
|
|
|
: typeCheckerForAutocomplete_DEPRECATED.check(sourceModule, Mode::Strict, environmentScope);
|
|
|
|
}
|
2022-10-21 18:54:01 +01:00
|
|
|
else
|
2023-04-14 19:06:22 +01:00
|
|
|
{
|
|
|
|
// The autocomplete typecheck is always in strict mode with DM awareness
|
|
|
|
// to provide better type information for IDE features
|
|
|
|
TypeCheckLimits typeCheckLimits;
|
|
|
|
|
|
|
|
if (autocompleteTimeLimit != 0.0)
|
|
|
|
typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
|
|
|
|
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 (FInt::LuauTarjanChildLimit > 0)
|
|
|
|
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
|
|
|
else
|
|
|
|
typeCheckLimits.instantiationChildLimit = std::nullopt;
|
|
|
|
|
|
|
|
if (FInt::LuauTypeInferIterationLimit > 0)
|
|
|
|
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
|
|
|
else
|
|
|
|
typeCheckLimits.unifierIterationLimit = std::nullopt;
|
|
|
|
|
|
|
|
moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true,
|
|
|
|
/*recordJsonLog*/ false, typeCheckLimits);
|
|
|
|
}
|
2022-09-29 23:23:10 +01:00
|
|
|
|
2022-04-07 22:29:01 +01:00
|
|
|
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
|
|
|
|
2022-04-15 00:57:43 +01:00
|
|
|
double duration = getTimestamp() - timestamp;
|
|
|
|
|
2022-04-07 22:29:01 +01:00
|
|
|
if (moduleForAutocomplete->timeout)
|
2022-04-15 00:57:43 +01:00
|
|
|
{
|
2022-04-07 22:29:01 +01:00
|
|
|
checkResult.timeoutHits.push_back(moduleName);
|
|
|
|
|
2022-10-21 18:54:01 +01:00
|
|
|
sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0;
|
2022-04-15 00:57:43 +01:00
|
|
|
}
|
2022-10-21 18:54:01 +01:00
|
|
|
else if (duration < autocompleteTimeLimit / 2.0)
|
2022-04-15 00:57:43 +01:00
|
|
|
{
|
|
|
|
sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
stats.timeCheck += duration;
|
2022-04-07 22:29:01 +01:00
|
|
|
stats.filesStrict += 1;
|
|
|
|
|
2022-04-21 22:44:27 +01:00
|
|
|
sourceNode.dirtyModuleForAutocomplete = false;
|
2022-04-07 22:29:01 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-03-03 20:21:14 +00:00
|
|
|
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson && moduleName == name;
|
|
|
|
|
2023-04-14 19:06:22 +01:00
|
|
|
ModulePtr module;
|
|
|
|
|
|
|
|
if (!FFlag::LuauOnDemandTypecheckers)
|
|
|
|
{
|
|
|
|
typeChecker_DEPRECATED.requireCycles = requireCycles;
|
|
|
|
|
|
|
|
module = (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
|
|
|
|
? check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, recordJsonLog, {})
|
|
|
|
: typeChecker_DEPRECATED.check(sourceModule, mode, environmentScope);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, recordJsonLog, {});
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
stats.timeCheck += getTimestamp() - timestamp;
|
|
|
|
stats.filesStrict += mode == Mode::Strict;
|
|
|
|
stats.filesNonstrict += mode == Mode::Nonstrict;
|
|
|
|
|
|
|
|
if (module == nullptr)
|
2022-12-02 18:09:59 +00:00
|
|
|
throw InternalCompilerError("Frontend::check produced a nullptr module for " + moduleName, moduleName);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2023-03-17 19:20:37 +00:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::NoCheck)
|
|
|
|
module->errors.clear();
|
|
|
|
|
|
|
|
if (frontendOptions.runLintChecks)
|
|
|
|
{
|
|
|
|
LUAU_TIMETRACE_SCOPE("lint", "Frontend");
|
|
|
|
|
|
|
|
LintOptions lintOptions = frontendOptions.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);
|
|
|
|
|
|
|
|
stats.timeLint += getTimestamp() - timestamp;
|
|
|
|
|
|
|
|
module->lintResult = classifyLints(warnings, config);
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!frontendOptions.retainFullTypeGraphs)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// copyErrors needs to allocate into interfaceTypes as it copies
|
|
|
|
// types out of internalTypes, so we unfreeze it here.
|
|
|
|
unfreeze(module->interfaceTypes);
|
|
|
|
copyErrors(module->errors, module->interfaceTypes);
|
|
|
|
freeze(module->interfaceTypes);
|
|
|
|
|
|
|
|
module->internalTypes.clear();
|
2023-01-06 21:14:35 +00:00
|
|
|
|
2023-02-10 19:40:38 +00:00
|
|
|
module->astTypes.clear();
|
|
|
|
module->astTypePacks.clear();
|
|
|
|
module->astExpectedTypes.clear();
|
|
|
|
module->astOriginalCallTypes.clear();
|
|
|
|
module->astOverloadResolvedTypes.clear();
|
|
|
|
module->astResolvedTypes.clear();
|
|
|
|
module->astOriginalResolvedTypes.clear();
|
|
|
|
module->astResolvedTypePacks.clear();
|
|
|
|
module->astScopes.clear();
|
|
|
|
|
|
|
|
module->scopes.clear();
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mode != Mode::NoCheck)
|
|
|
|
{
|
|
|
|
for (const RequireCycle& cyc : requireCycles)
|
|
|
|
{
|
|
|
|
TypeError te{cyc.location, moduleName, ModuleHasCyclicDependency{cyc.path}};
|
|
|
|
|
|
|
|
module->errors.push_back(te);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorVec parseErrors;
|
|
|
|
|
|
|
|
for (const ParseError& pe : sourceModule.parseErrors)
|
|
|
|
parseErrors.push_back(TypeError{pe.getLocation(), moduleName, SyntaxError{pe.what()}});
|
|
|
|
|
|
|
|
module->errors.insert(module->errors.begin(), parseErrors.begin(), parseErrors.end());
|
|
|
|
|
|
|
|
checkResult.errors.insert(checkResult.errors.end(), module->errors.begin(), module->errors.end());
|
|
|
|
|
|
|
|
moduleResolver.modules[moduleName] = std::move(module);
|
2022-04-21 22:44:27 +01:00
|
|
|
sourceNode.dirtyModule = false;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
// Get lint result only for top checked module
|
|
|
|
std::unordered_map<ModuleName, ModulePtr>& modules =
|
|
|
|
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
|
2023-03-17 19:20:37 +00:00
|
|
|
|
2023-04-07 22:01:29 +01:00
|
|
|
if (auto it = modules.find(name); it != modules.end())
|
|
|
|
checkResult.lintResult = it->second->lintResult;
|
2023-03-17 19:20:37 +00:00
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
return checkResult;
|
|
|
|
}
|
|
|
|
|
2023-01-04 20:53:17 +00:00
|
|
|
bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, const ModuleName& root, bool forAutocomplete)
|
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.
|
2022-04-21 22:44:27 +01:00
|
|
|
if (!it->second.hasDirtyModule(forAutocomplete))
|
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
|
|
|
|
if (seen.contains(&it->second))
|
|
|
|
{
|
|
|
|
stack.push_back(&it->second);
|
|
|
|
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-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-14 19:06:22 +01:00
|
|
|
result->bindings[name].typeId = FFlag::LuauOnDemandTypecheckers ? builtinTypes->anyType : typeChecker_DEPRECATED.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);
|
2022-04-21 22:44:27 +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)
|
|
|
|
{
|
2022-05-26 23:08:16 +01:00
|
|
|
if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name))
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
|
|
|
for (const auto& module : sourceNodes)
|
|
|
|
{
|
2022-06-17 02:05:14 +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);
|
|
|
|
SourceNode& sourceNode = sourceNodes[next];
|
|
|
|
|
|
|
|
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())
|
|
|
|
return &it->second;
|
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const
|
|
|
|
{
|
|
|
|
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
|
|
|
}
|
|
|
|
|
2023-03-03 20:21:14 +00:00
|
|
|
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
|
|
|
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
2023-04-14 19:06:22 +01:00
|
|
|
const ScopePtr& parentScope, FrontendOptions options)
|
2023-03-03 20:21:14 +00:00
|
|
|
{
|
|
|
|
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
|
2023-04-14 19:06:22 +01:00
|
|
|
return check(sourceModule, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope, options, recordJsonLog);
|
2023-03-03 20:21:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
|
|
|
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
2023-04-14 19:06:22 +01:00
|
|
|
const ScopePtr& parentScope, FrontendOptions options, bool recordJsonLog)
|
2023-03-03 20:21:14 +00:00
|
|
|
{
|
2022-06-03 23:15:45 +01:00
|
|
|
ModulePtr result = std::make_shared<Module>();
|
2023-02-17 23:41:51 +00:00
|
|
|
result->reduction = std::make_unique<TypeReduction>(NotNull{&result->internalTypes}, builtinTypes, iceHandler);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
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>();
|
|
|
|
std::optional<SourceCode> source = fileResolver->readSource(sourceModule.name);
|
|
|
|
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;
|
|
|
|
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
|
|
|
|
|
|
|
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
|
2022-10-07 01:23:29 +01:00
|
|
|
|
2022-09-29 23:23:10 +01:00
|
|
|
ConstraintGraphBuilder cgb{
|
2022-10-21 18:54:01 +01:00
|
|
|
sourceModule.name,
|
|
|
|
result,
|
|
|
|
&result->internalTypes,
|
2023-02-17 23:41:51 +00:00
|
|
|
moduleResolver,
|
2023-01-04 20:53:17 +00:00
|
|
|
builtinTypes,
|
2023-02-17 23:41:51 +00:00
|
|
|
iceHandler,
|
2023-04-14 19:06:22 +01:00
|
|
|
parentScope,
|
2022-10-21 18:54:01 +01:00
|
|
|
logger.get(),
|
|
|
|
NotNull{&dfg},
|
|
|
|
};
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
cgb.visit(sourceModule.root);
|
2022-07-01 00:52:43 +01:00
|
|
|
result->errors = std::move(cgb.errors);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2023-03-31 19:42:49 +01:00
|
|
|
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), sourceModule.name, moduleResolver,
|
|
|
|
requireCycles, logger.get()};
|
2022-10-07 01:23:29 +01:00
|
|
|
|
|
|
|
if (options.randomizeConstraintResolutionSeed)
|
|
|
|
cs.randomize(*options.randomizeConstraintResolutionSeed);
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
cs.run();
|
|
|
|
|
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
|
|
|
|
2022-07-29 05:24:07 +01:00
|
|
|
result->scopes = std::move(cgb.scopes);
|
2022-09-02 00:14:03 +01:00
|
|
|
result->type = sourceModule.type;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2023-02-17 23:41:51 +00:00
|
|
|
result->clonePublicInterface(builtinTypes, *iceHandler);
|
2023-01-04 20:53:17 +00:00
|
|
|
|
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
|
|
|
Luau::check(builtinTypes, logger.get(), sourceModule, result.get());
|
|
|
|
|
|
|
|
// Ideally we freeze the arenas before the call into Luau::check, but TypeReduction
|
|
|
|
// needs to allocate new types while Luau::check is in progress, so here we are.
|
|
|
|
//
|
|
|
|
// It does mean that mutations to the type graph can happen after the constraints
|
|
|
|
// have been solved, which will cause hard-to-debug problems. We should revisit this.
|
2023-01-04 20:53:17 +00:00
|
|
|
freeze(result->internalTypes);
|
|
|
|
freeze(result->interfaceTypes);
|
|
|
|
|
2023-03-03 20:21:14 +00:00
|
|
|
if (recordJsonLog)
|
2022-09-08 23:14:25 +01:00
|
|
|
{
|
|
|
|
std::string output = logger->compileOutput();
|
|
|
|
printf("%s\n", output.c_str());
|
|
|
|
}
|
2022-06-17 02:05:14 +01:00
|
|
|
|
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-04-14 19:06:22 +01:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
|
|
|
|
{
|
|
|
|
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
|
|
|
|
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
|
|
|
environmentScope ? *environmentScope : globals.globalScope, options, recordJsonLog);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(FFlag::LuauOnDemandTypecheckers);
|
|
|
|
|
|
|
|
TypeChecker typeChecker(globals.globalScope, forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver, builtinTypes, &iceHandler);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2021-11-05 02:34:35 +00:00
|
|
|
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
|
|
|
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
auto it = sourceNodes.find(name);
|
2022-05-26 23:08:16 +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())
|
|
|
|
return {&it->second, &moduleIt->second};
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!"Everything in sourceNodes should also be in sourceModules");
|
|
|
|
return {&it->second, nullptr};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
SourceNode& sourceNode = sourceNodes[name];
|
|
|
|
SourceModule& sourceModule = sourceModules[name];
|
|
|
|
|
|
|
|
sourceModule = std::move(result);
|
|
|
|
sourceModule.environmentName = environmentName;
|
|
|
|
|
|
|
|
sourceNode.name = name;
|
2022-06-17 02:05:14 +01:00
|
|
|
sourceNode.requireSet.clear();
|
2021-10-29 21:25:12 +01:00
|
|
|
sourceNode.requireLocations.clear();
|
2022-05-26 23:08:16 +01:00
|
|
|
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
|
|
|
{
|
2022-04-21 22:44:27 +01:00
|
|
|
sourceNode.dirtyModule = true;
|
2022-05-26 23:08:16 +01:00
|
|
|
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)
|
|
|
|
sourceNode.requireSet.insert(moduleName);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
sourceNode.requireLocations = require.requireList;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
return {&sourceNode, &sourceModule};
|
|
|
|
}
|
|
|
|
|
|
|
|
/** 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;
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
moduleResolver.modules.clear();
|
|
|
|
moduleResolverForAutocomplete.modules.clear();
|
2022-06-17 02:05:14 +01:00
|
|
|
requireTrace.clear();
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|