luau/Analysis/src/Frontend.cpp

1243 lines
43 KiB
C++
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Frontend.h"
#include "Luau/BuiltinDefinitions.h"
2022-04-07 17:29:01 -04:00
#include "Luau/Clone.h"
2022-06-16 21:05:14 -04:00
#include "Luau/Common.h"
#include "Luau/Config.h"
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h"
2022-06-16 21:05:14 -04:00
#include "Luau/FileResolver.h"
2022-02-17 20:18:01 -05:00
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h"
2022-06-16 21:05:14 -04:00
#include "Luau/TypeChecker2.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypeReduction.h"
#include "Luau/Variant.h"
#include <algorithm>
#include <chrono>
#include <stdexcept>
#include <string>
2022-04-14 19:57:43 -04:00
LUAU_FASTINT(LuauTypeInferIterationLimit)
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
LUAU_FASTINT(LuauTypeInferRecursionLimit)
2022-04-14 19:57:43 -04:00
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
LUAU_FASTFLAGVARIABLE(LuauLintInTypecheck, false)
2022-04-28 21:24:24 -04:00
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
namespace Luau
{
2022-02-17 20:18:01 -05:00
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
{
2022-02-17 20:18:01 -05:00
for (const HotComment& hc : hotcomments)
{
2022-02-17 20:18:01 -05:00
if (!hc.header)
continue;
if (hc.content == "nocheck")
return Mode::NoCheck;
2022-02-17 20:18:01 -05:00
if (hc.content == "nonstrict")
return Mode::Nonstrict;
2022-02-17 20:18:01 -05:00
if (hc.content == "strict")
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;
if (TableType* ttv = getMutable<TableType>(ty))
{
for (auto& [name, prop] : ttv->props)
{
prop.documentationSymbol = rootName + "." + name;
}
}
else if (ClassType* ctv = getMutable<ClassType>(ty))
{
for (auto& [name, prop] : ctv->props)
{
prop.documentationSymbol = rootName + "." + name;
}
}
}
LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, const std::string& packageName, bool captureComments)
2022-08-04 18:35:33 -04:00
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return Luau::loadDefinitionFile(typeChecker, globals, globals.globalScope, source, packageName, captureComments);
2022-08-04 18:35:33 -04:00
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
Luau::SourceModule sourceModule;
2022-08-04 18:35:33 -04:00
ParseOptions options;
options.allowDeclarationSyntax = true;
options.captureComments = captureComments;
2022-08-04 18:35:33 -04:00
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
2022-08-04 18:35:33 -04:00
if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
2022-08-04 18:35:33 -04:00
sourceModule.root = parseResult.root;
sourceModule.mode = Mode::Definition;
2022-08-04 18:35:33 -04:00
ModulePtr checkedModule = check(sourceModule, Mode::Definition, {});
2022-08-04 18:35:33 -04:00
if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
2022-08-04 18:35:33 -04:00
CloneState cloneState;
std::vector<TypeId> typesToPersist;
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size());
for (const auto& [name, ty] : checkedModule->declaredGlobals)
{
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
std::string documentationSymbol = packageName + "/global/" + name;
generateDocumentationSymbols(globalTy, documentationSymbol);
globals.globalScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
typesToPersist.push_back(globalTy);
}
2022-08-04 18:35:33 -04:00
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
{
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
std::string documentationSymbol = packageName + "/globaltype/" + name;
generateDocumentationSymbols(globalTy.type, documentationSymbol);
globals.globalScope->exportedTypeBindings[name] = globalTy;
typesToPersist.push_back(globalTy.type);
}
for (TypeId ty : typesToPersist)
{
persist(ty);
2022-08-04 18:35:33 -04:00
}
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
2022-08-04 18:35:33 -04:00
}
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
const std::string& packageName, bool captureComments)
{
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
Luau::SourceModule sourceModule;
ParseOptions options;
options.allowDeclarationSyntax = true;
options.captureComments = captureComments;
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
sourceModule.root = parseResult.root;
sourceModule.mode = Mode::Definition;
ModulePtr checkedModule = typeChecker.check(sourceModule, Mode::Definition);
if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
CloneState cloneState;
std::vector<TypeId> typesToPersist;
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size());
for (const auto& [name, ty] : checkedModule->declaredGlobals)
{
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
std::string documentationSymbol = packageName + "/global/" + name;
generateDocumentationSymbols(globalTy, documentationSymbol);
targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
typesToPersist.push_back(globalTy);
}
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
{
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
std::string documentationSymbol = packageName + "/globaltype/" + name;
generateDocumentationSymbols(globalTy.type, documentationSymbol);
targetScope->exportedTypeBindings[name] = globalTy;
typesToPersist.push_back(globalTy.type);
}
for (TypeId ty : typesToPersist)
{
persist(ty);
}
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
}
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
{
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
static ErrorVec accumulateErrors(
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-16 21:05:14 -04:00
queue.insert(queue.end(), sourceNode.requireSet.begin(), sourceNode.requireSet.end());
// 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;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode)
{
LUAU_ASSERT(FFlag::LuauLintInTypecheck);
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);
}
}
// 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(
const FileResolver* resolver, const std::unordered_map<ModuleName, SourceNode>& sourceNodes, const SourceNode* start, bool stopAtFirst = false)
{
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)
cycle.push_back(resolver->getHumanReadableModuleName(node->name));
cycle.push_back(resolver->getHumanReadableModuleName(top->name));
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)
: builtinTypes(NotNull{&builtinTypes_})
, fileResolver(fileResolver)
, moduleResolver(this)
, moduleResolverForAutocomplete(this)
, globals(builtinTypes)
, globalsForAutocomplete(builtinTypes)
, typeChecker(globals, &moduleResolver, builtinTypes, &iceHandler)
, typeCheckerForAutocomplete(globalsForAutocomplete, &moduleResolverForAutocomplete, builtinTypes, &iceHandler)
, configResolver(configResolver)
, options(options)
{
}
FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
: frontend(frontend)
{
}
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
{
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
2022-04-07 17:29:01 -04:00
FrontendOptions frontendOptions = optionOverride.value_or(options);
CheckResult checkResult;
auto it = sourceNodes.find(name);
2022-04-21 17:44:27 -04:00
if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete))
{
// No recheck required.
if (frontendOptions.forAutocomplete)
2022-04-07 17:29:01 -04:00
{
auto it2 = moduleResolverForAutocomplete.modules.find(name);
if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
throw InternalCompilerError("Frontend::modules does not have data for " + name, name);
2022-04-07 17:29:01 -04:00
}
else
{
auto it2 = moduleResolver.modules.find(name);
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
throw InternalCompilerError("Frontend::modules does not have data for " + name, name);
2022-04-07 17:29:01 -04:00
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (FFlag::LuauLintInTypecheck)
{
std::unordered_map<ModuleName, ModulePtr>& modules =
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
checkResult.errors = accumulateErrors(sourceNodes, modules, name);
// Get lint result only for top checked module
if (auto it = modules.find(name); it != modules.end())
checkResult.lintResult = it->second->lintResult;
return checkResult;
}
else
{
return CheckResult{accumulateErrors(
sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
}
}
std::vector<ModuleName> buildQueue;
bool cycleDetected = parseGraph(buildQueue, name, frontendOptions.forAutocomplete);
2022-04-07 17:29:01 -04:00
for (const ModuleName& moduleName : buildQueue)
{
LUAU_ASSERT(sourceNodes.count(moduleName));
SourceNode& sourceNode = sourceNodes[moduleName];
2022-04-21 17:44:27 -04:00
if (!sourceNode.hasDirtyModule(frontendOptions.forAutocomplete))
continue;
LUAU_ASSERT(sourceModules.count(moduleName));
SourceModule& sourceModule = sourceModules[moduleName];
const Config& config = configResolver->getConfig(moduleName);
Mode mode = sourceModule.mode.value_or(config.mode);
ScopePtr environmentScope = getModuleEnvironment(sourceModule, config, frontendOptions.forAutocomplete);
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)
requireCycles = getRequireCycles(fileResolver, sourceNodes, &sourceNode, mode == Mode::NoCheck);
// This is used by the type checker to replace the resulting type of cyclic modules with any
sourceModule.cyclic = !requireCycles.empty();
if (frontendOptions.forAutocomplete)
2022-04-07 17:29:01 -04:00
{
// The autocomplete typecheck is always in strict mode with DM awareness
// to provide better type information for IDE features
2022-05-05 20:03:43 -04:00
typeCheckerForAutocomplete.requireCycles = requireCycles;
2022-04-07 17:29:01 -04:00
double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0;
2022-04-07 17:29:01 -04:00
if (autocompleteTimeLimit != 0.0)
typeCheckerForAutocomplete.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
else
typeCheckerForAutocomplete.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.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
else
typeCheckerForAutocomplete.instantiationChildLimit = std::nullopt;
if (FInt::LuauTypeInferIterationLimit > 0)
typeCheckerForAutocomplete.unifierIterationLimit =
std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
else
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
2022-04-14 19:57:43 -04:00
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ModulePtr moduleForAutocomplete = (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
? check(sourceModule, mode, requireCycles, /*forAutocomplete*/ true, /*recordJsonLog*/ false)
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
2022-04-07 17:29:01 -04:00
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
2022-04-14 19:57:43 -04:00
double duration = getTimestamp() - timestamp;
2022-04-07 17:29:01 -04:00
if (moduleForAutocomplete->timeout)
2022-04-14 19:57:43 -04:00
{
2022-04-07 17:29:01 -04:00
checkResult.timeoutHits.push_back(moduleName);
sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0;
2022-04-14 19:57:43 -04:00
}
else if (duration < autocompleteTimeLimit / 2.0)
2022-04-14 19:57:43 -04:00
{
sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0);
}
stats.timeCheck += duration;
2022-04-07 17:29:01 -04:00
stats.filesStrict += 1;
2022-04-21 17:44:27 -04:00
sourceNode.dirtyModuleForAutocomplete = false;
2022-04-07 17:29:01 -04:00
continue;
}
2022-05-05 20:03:43 -04:00
typeChecker.requireCycles = requireCycles;
2022-04-07 17:29:01 -04:00
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson && moduleName == name;
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ModulePtr module = (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
? check(sourceModule, mode, requireCycles, /*forAutocomplete*/ false, recordJsonLog)
: typeChecker.check(sourceModule, mode, environmentScope);
stats.timeCheck += getTimestamp() - timestamp;
stats.filesStrict += mode == Mode::Strict;
stats.filesNonstrict += mode == Mode::Nonstrict;
if (module == nullptr)
throw InternalCompilerError("Frontend::check produced a nullptr module for " + moduleName, moduleName);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::NoCheck)
module->errors.clear();
if (frontendOptions.runLintChecks)
{
LUAU_TIMETRACE_SCOPE("lint", "Frontend");
LUAU_ASSERT(FFlag::LuauLintInTypecheck);
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);
}
if (!frontendOptions.retainFullTypeGraphs)
{
// 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();
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();
}
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 17:44:27 -04:00
sourceNode.dirtyModule = false;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (FFlag::LuauLintInTypecheck)
{
// Get lint result only for top checked module
std::unordered_map<ModuleName, ModulePtr>& modules =
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
if (auto it = modules.find(name); it != modules.end())
checkResult.lintResult = it->second->lintResult;
}
return checkResult;
}
bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, const ModuleName& root, bool forAutocomplete)
{
LUAU_TIMETRACE_SCOPE("Frontend::parseGraph", "Frontend");
LUAU_TIMETRACE_ARGUMENT("root", root.c_str());
// 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;
{
auto [sourceNode, _] = getSourceNode(root);
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-16 21:05:14 -04:00
for (const ModuleName& dep : top->requireSet)
{
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 17:44:27 -04:00
if (!it->second.hasDirtyModule(forAutocomplete))
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;
}
}
auto [sourceNode, _] = getSourceNode(dep);
if (sourceNode)
{
stack.push_back(sourceNode);
// note: this assignment is paired with .contains() check above and effectively deduplicates getSourceNode()
seen[sourceNode] = None;
}
}
}
}
return cyclic;
}
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
{
ScopePtr result;
if (forAutocomplete)
result = globalsForAutocomplete.globalScope;
else
result = globals.globalScope;
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)
result->bindings[name].typeId = typeChecker.anyType;
}
}
return result;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
LintResult Frontend::lint_DEPRECATED(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings)
{
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
LUAU_ASSERT(!FFlag::LuauLintInTypecheck);
LUAU_TIMETRACE_SCOPE("Frontend::lint", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
auto [_sourceNode, sourceModule] = getSourceNode(name);
if (!sourceModule)
return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint.
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return lint_DEPRECATED(*sourceModule, enabledLintWarnings);
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
LintResult Frontend::lint_DEPRECATED(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings)
{
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
LUAU_ASSERT(!FFlag::LuauLintInTypecheck);
LUAU_TIMETRACE_SCOPE("Frontend::lint", "Frontend");
LUAU_TIMETRACE_ARGUMENT("module", module.name.c_str());
const Config& config = configResolver->getConfig(module.name);
2022-02-17 20:18:01 -05:00
uint64_t ignoreLints = LintWarning::parseMask(module.hotcomments);
LintOptions options = enabledLintWarnings.value_or(config.enabledLint);
2022-02-17 20:18:01 -05:00
options.warningMask &= ~ignoreLints;
Mode mode = module.mode.value_or(config.mode);
if (mode != Mode::NoCheck)
{
options.disableWarning(Luau::LintWarning::Code_UnknownGlobal);
}
if (mode == Mode::Strict)
{
options.disableWarning(Luau::LintWarning::Code_ImplicitReturn);
}
ScopePtr environmentScope = getModuleEnvironment(module, config, /*forAutocomplete*/ false);
ModulePtr modulePtr = moduleResolver.getModule(module.name);
double timestamp = getTimestamp();
2022-02-17 20:18:01 -05:00
std::vector<LintWarning> warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), module.hotcomments, options);
stats.timeLint += getTimestamp() - timestamp;
return classifyLints(warnings, config);
}
2022-04-07 17:29:01 -04:00
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
{
auto it = sourceNodes.find(name);
2022-04-21 17:44:27 -04:00
return it == sourceNodes.end() || it->second.hasDirtyModule(forAutocomplete);
}
/*
* 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)
{
if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name))
return;
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
for (const auto& module : sourceNodes)
{
2022-06-16 21:05:14 -04:00
for (const auto& dep : module.second.requireSet)
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);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue;
2022-04-07 17:29:01 -04:00
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
if (0 == reverseDeps.count(next))
continue;
sourceModules.erase(next);
const std::vector<ModuleName>& dependents = reverseDeps[next];
queue.insert(queue.end(), dependents.begin(), dependents.end());
}
}
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);
}
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, FrontendOptions options)
{
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
return check(sourceModule, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, globalScope, options, recordJsonLog);
}
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, FrontendOptions options, bool recordJsonLog)
{
ModulePtr result = std::make_shared<Module>();
result->reduction = std::make_unique<TypeReduction>(NotNull{&result->internalTypes}, builtinTypes, iceHandler);
std::unique_ptr<DcrLogger> logger;
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
if (recordJsonLog)
{
logger = std::make_unique<DcrLogger>();
std::optional<SourceCode> source = fileResolver->readSource(sourceModule.name);
if (source)
{
logger->captureSource(source->source);
}
}
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler);
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
UnifierSharedState unifierState{iceHandler};
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
ConstraintGraphBuilder cgb{
sourceModule.name,
result,
&result->internalTypes,
moduleResolver,
builtinTypes,
iceHandler,
globalScope,
logger.get(),
NotNull{&dfg},
};
cgb.visit(sourceModule.root);
2022-06-30 19:52:43 -04:00
result->errors = std::move(cgb.errors);
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), sourceModule.name,
NotNull{result->reduction.get()}, moduleResolver, requireCycles, logger.get()};
if (options.randomizeConstraintResolutionSeed)
cs.randomize(*options.randomizeConstraintResolutionSeed);
cs.run();
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 14:26:13 -05:00
2022-07-29 00:24:07 -04:00
result->scopes = std::move(cgb.scopes);
result->type = sourceModule.type;
result->clonePublicInterface(builtinTypes, *iceHandler);
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 14:26:13 -05: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.
freeze(result->internalTypes);
freeze(result->interfaceTypes);
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
if (recordJsonLog)
{
std::string output = logger->compileOutput();
printf("%s\n", output.c_str());
}
2022-06-16 21:05:14 -04:00
return result;
}
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, bool forAutocomplete, bool recordJsonLog)
{
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope, options, recordJsonLog);
}
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName& name)
{
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
auto it = sourceNodes.find(name);
if (it != sourceNodes.end() && !it->second.hasDirtySourceModule())
{
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-16 21:05:14 -04:00
RequireTraceResult& require = requireTrace[name];
require = traceRequires(fileResolver, result.root, name);
SourceNode& sourceNode = sourceNodes[name];
SourceModule& sourceModule = sourceModules[name];
sourceModule = std::move(result);
sourceModule.environmentName = environmentName;
sourceNode.name = name;
2022-06-16 21:05:14 -04:00
sourceNode.requireSet.clear();
sourceNode.requireLocations.clear();
sourceNode.dirtySourceModule = false;
2022-04-07 17:29:01 -04:00
if (it == sourceNodes.end())
2022-04-07 17:29:01 -04:00
{
2022-04-21 17:44:27 -04:00
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
2022-04-07 17:29:01 -04:00
}
2022-06-16 21:05:14 -04:00
for (const auto& [moduleName, location] : require.requireList)
sourceNode.requireSet.insert(moduleName);
2022-06-16 21:05:14 -04:00
sourceNode.requireLocations = require.requireList;
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)
{
LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
SourceModule sourceModule;
double timestamp = getTimestamp();
Luau::ParseResult parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
stats.timeParse += getTimestamp() - timestamp;
stats.files++;
stats.lines += parseResult.lines;
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-17 20:18:01 -05:00
if (parseOptions.captureComments)
2022-02-17 20:18:01 -05:00
{
sourceModule.commentLocations = std::move(parseResult.commentLocations);
2022-02-17 20:18:01 -05:00
sourceModule.hotcomments = std::move(parseResult.hotcomments);
}
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-16 21:05:14 -04:00
auto it = frontend->requireTrace.find(currentModuleName);
if (it == frontend->requireTrace.end())
{
// CLI-43699
// If we can't find the current module name, that's because we bypassed the frontend's initializer
2022-07-14 18:52:26 -04:00
// and called typeChecker.check directly.
// In that case, requires will always fail.
return std::nullopt;
}
const auto& exprs = it->second.exprs;
const ModuleInfo* info = exprs.find(&pathExpr);
if (!info)
return std::nullopt;
return *info;
}
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
{
return frontend->sourceNodes.count(moduleName) != 0;
}
std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const
{
return frontend->fileResolver->getHumanReadableModuleName(moduleName);
}
ScopePtr Frontend::addEnvironment(const std::string& environmentName)
{
LUAU_ASSERT(environments.count(environmentName) == 0);
if (environments.count(environmentName) == 0)
{
ScopePtr scope = std::make_shared<Scope>(globals.globalScope);
environments[environmentName] = scope;
return scope;
}
else
return environments[environmentName];
}
ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName) const
{
if (auto it = environments.find(environmentName); it != environments.end())
return it->second;
LUAU_ASSERT(!"environment doesn't exist");
return {};
}
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, GlobalTypes&, ScopePtr)> applicator)
{
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)
builtinDefinitions[definitionName](typeChecker, globals, getEnvironmentScope(environmentName));
}
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-16 21:05:14 -04:00
requireTrace.clear();
}
} // namespace Luau