Sync to upstream/release/506 (#270)

- Fix some cases where type checking would overflow the native stack
- Improve autocomplete behavior when assigning a partially written function call (not currently exposed through command line tools)
- Improve autocomplete type inference feedback for some expressions where previously the type would not be known
- Improve quantification performance during type checking for large types
- Improve type checking for table literals when the expected type of the table is known because of a type annotation
- Fix type checking errors in cases where required module has errors in the resulting type
- Fix debug line information for multi-line chained call sequences (Add function name information for "attempt to call a nil value" #255)
- lua_newuserdata now takes 2 arguments to match Lua/LuaJIT APIs better; lua_newuserdatatagged should be used if the third argument was non-0.
- lua_ref can no longer be used with LUA_REGISTRYINDEX to prevent mistakes when migrating Lua FFI (Inconsistency with lua_ref #247)
- Fix assertions and possible crashes when executing script code indirectly via metatable dispatch from lua_equal/lua_lessthan/lua_getfield/etc. (Hitting a crash in an assert after lua_equal is called. #259)
- Fix flamegraph scripts to run under Python 2
This commit is contained in:
Arseny Kapoulkine 2021-12-02 22:41:04 -08:00 committed by GitHub
parent f5ec6df7ba
commit 32fb6d10a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2430 additions and 2343 deletions

View File

@ -51,13 +51,6 @@ struct FileResolver
{
return std::nullopt;
}
// DEPRECATED APIS
// These are going to be removed with LuauNewRequireTrace2
virtual bool moduleExists(const ModuleName& name) const = 0;
virtual std::optional<ModuleName> fromAstFragment(AstExpr* expr) const = 0;
virtual ModuleName concat(const ModuleName& lhs, std::string_view rhs) const = 0;
virtual std::optional<ModuleName> getParentModuleName(const ModuleName& name) const = 0;
};
struct NullFileResolver : FileResolver
@ -66,22 +59,6 @@ struct NullFileResolver : FileResolver
{
return std::nullopt;
}
bool moduleExists(const ModuleName& name) const override
{
return false;
}
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
{
return std::nullopt;
}
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
{
return lhs;
}
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override
{
return std::nullopt;
}
};
} // namespace Luau

View File

@ -78,9 +78,15 @@ void unfreeze(TypeArena& arena);
using SeenTypes = std::unordered_map<TypeId, TypeId>;
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
struct CloneState
{
int recursionCount = 0;
bool encounteredFreeType = false;
};
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
struct Module
{

View File

@ -0,0 +1,31 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Common.h"
#include <string>
namespace Luau
{
struct TypeVar;
using TypeId = const TypeVar*;
struct TypePackVar;
using TypePackId = const TypePackVar*;
struct ToDotOptions
{
bool showPointers = true; // Show pointer value in the node label
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
};
std::string toDot(TypeId ty, const ToDotOptions& opts);
std::string toDot(TypePackId tp, const ToDotOptions& opts);
std::string toDot(TypeId ty);
std::string toDot(TypePackId tp);
void dumpDot(TypeId ty);
void dumpDot(TypePackId tp);
} // namespace Luau

View File

@ -25,15 +25,6 @@ struct TxnLog
{
}
explicit TxnLog(const std::vector<std::pair<TypeId, TypeId>>& ownedSeen)
: originalSeenSize(ownedSeen.size())
, ownedSeen(ownedSeen)
, sharedSeen(nullptr)
{
// This is deprecated!
LUAU_ASSERT(!FFlag::LuauShareTxnSeen);
}
TxnLog(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete;

View File

@ -297,7 +297,6 @@ private:
private:
Unifier mkUnifier(const Location& location);
Unifier mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location);
// These functions are only safe to call when we are in the process of typechecking a module.

View File

@ -517,21 +517,6 @@ extern SingletonTypes singletonTypes;
void persist(TypeId ty);
void persist(TypePackId tp);
struct ToDotOptions
{
bool showPointers = true; // Show pointer value in the node label
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
};
std::string toDot(TypeId ty, const ToDotOptions& opts);
std::string toDot(TypePackId tp, const ToDotOptions& opts);
std::string toDot(TypeId ty);
std::string toDot(TypePackId tp);
void dumpDot(TypeId ty);
void dumpDot(TypePackId tp);
const TypeLevel* getLevel(TypeId ty);
TypeLevel* getMutableLevel(TypeId ty);

View File

@ -19,12 +19,6 @@ enum Variance
Invariant
};
struct UnifierCounters
{
int recursionCount = 0;
int iterationCount = 0;
};
struct Unifier
{
TypeArena* const types;
@ -37,20 +31,11 @@ struct Unifier
Variance variance = Covariant;
CountMismatch::Context ctx = CountMismatch::Arg;
UnifierCounters* counters;
UnifierCounters countersData;
std::shared_ptr<UnifierCounters> counters_DEPRECATED;
UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState);
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& ownedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED = nullptr,
UnifierCounters* counters = nullptr);
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED = nullptr,
UnifierCounters* counters = nullptr);
Variance variance, UnifierSharedState& sharedState);
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId superTy, TypeId subTy);
@ -92,9 +77,9 @@ private:
public:
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
void occursCheck(TypeId needle, TypeId haystack);
void occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
void occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
void occursCheck(TypePackId needle, TypePackId haystack);
void occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
void occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
Unifier makeChildUnifier();
@ -106,10 +91,6 @@ private:
[[noreturn]] void ice(const std::string& message, const Location& location);
[[noreturn]] void ice(const std::string& message);
// Remove with FFlagLuauCacheUnifyTableResults
DenseHashSet<TypeId> tempSeenTy_DEPRECATED{nullptr};
DenseHashSet<TypePackId> tempSeenTp_DEPRECATED{nullptr};
};
} // namespace Luau

View File

@ -24,6 +24,12 @@ struct TypeIdPairHash
}
};
struct UnifierCounters
{
int recursionCount = 0;
int iterationCount = 0;
};
struct UnifierSharedState
{
UnifierSharedState(InternalErrorReporter* iceHandler)
@ -39,6 +45,8 @@ struct UnifierSharedState
DenseHashSet<TypeId> tempSeenTy{nullptr};
DenseHashSet<TypePackId> tempSeenTp{nullptr};
UnifierCounters counters;
};
} // namespace Luau

View File

@ -5,8 +5,6 @@
#include "Luau/TypeVar.h"
#include "Luau/TypePack.h"
LUAU_FASTFLAG(LuauCacheUnifyTableResults)
namespace Luau
{
@ -101,7 +99,7 @@ void visit(TypeId ty, F& f, Set& seen)
// Some visitors want to see bound tables, that's why we visit the original type
if (apply(ty, *ttv, seen, f))
{
if (FFlag::LuauCacheUnifyTableResults && ttv->boundTo)
if (ttv->boundTo)
{
visit(*ttv->boundTo, f, seen);
}

View File

@ -12,9 +12,9 @@
#include <unordered_set>
#include <utility>
LUAU_FASTFLAGVARIABLE(ElseElseIfCompletionImprovements, false);
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -203,8 +203,9 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
{
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, nullptr);
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, nullptr);
CloneState cloneState;
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
auto errors = unifier.canUnify(expectedType, actualType);
return errors.empty();
@ -229,28 +230,51 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
TypeId expectedType = follow(*it);
if (canUnify(expectedType, ty))
return TypeCorrectKind::Correct;
// We also want to suggest functions that return compatible result
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
if (!ftv)
return TypeCorrectKind::None;
auto [retHead, retTail] = flatten(ftv->retType);
if (!retHead.empty())
return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
// We might only have a variadic tail pack, check if the element is compatible
if (retTail)
if (FFlag::LuauAutocompletePreferToCallFunctions)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)))
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
}
// We also want to suggest functions that return compatible result
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
auto [retHead, retTail] = flatten(ftv->retType);
return TypeCorrectKind::None;
if (!retHead.empty() && canUnify(expectedType, retHead.front()))
return TypeCorrectKind::CorrectFunctionResult;
// We might only have a variadic tail pack, check if the element is compatible
if (retTail)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(expectedType, vtp->ty))
return TypeCorrectKind::CorrectFunctionResult;
}
}
return canUnify(expectedType, ty) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
}
else
{
if (canUnify(expectedType, ty))
return TypeCorrectKind::Correct;
// We also want to suggest functions that return compatible result
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
if (!ftv)
return TypeCorrectKind::None;
auto [retHead, retTail] = flatten(ftv->retType);
if (!retHead.empty())
return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
// We might only have a variadic tail pack, check if the element is compatible
if (retTail)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)))
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
}
return TypeCorrectKind::None;
}
}
enum class PropIndexType
@ -1413,7 +1437,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (AstStatIf* statIf = node->as<AstStatIf>(); FFlag::ElseElseIfCompletionImprovements && statIf && !statIf->hasElse)
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->hasElse)
{
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
finder.ancestry};

View File

@ -8,8 +8,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauNewRequireTrace2)
/** FIXME: Many of these type definitions are not quite completely accurate.
*
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
@ -473,9 +471,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
if (!checkRequirePath(typechecker, expr.args.data[0]))
return std::nullopt;
const AstExpr* require = FFlag::LuauNewRequireTrace2 ? &expr : expr.args.data[0];
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require))
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr))
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
return std::nullopt;

View File

@ -7,57 +7,14 @@
#include <stdexcept>
LUAU_FASTFLAG(LuauTypeAliasPacks)
static std::string wrongNumberOfArgsString_DEPRECATED(size_t expectedCount, size_t actualCount, bool isTypeArgs = false)
{
std::string s = "expects " + std::to_string(expectedCount) + " ";
if (isTypeArgs)
s += "type ";
s += "argument";
if (expectedCount != 1)
s += "s";
s += ", but ";
if (actualCount == 0)
{
s += "none";
}
else
{
if (actualCount < expectedCount)
s += "only ";
s += std::to_string(actualCount);
}
s += (actualCount == 1) ? " is" : " are";
s += " specified";
return s;
}
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
{
std::string s;
std::string s = "expects ";
if (FFlag::LuauTypeAliasPacks)
{
s = "expects ";
if (isVariadic)
s += "at least ";
if (isVariadic)
s += "at least ";
s += std::to_string(expectedCount) + " ";
}
else
{
s = "expects " + std::to_string(expectedCount) + " ";
}
s += std::to_string(expectedCount) + " ";
if (argPrefix)
s += std::string(argPrefix) + " ";
@ -188,10 +145,7 @@ struct ErrorConverter
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
" are required here";
case CountMismatch::Arg:
if (FFlag::LuauTypeAliasPacks)
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
else
return "Argument count mismatch. Function " + wrongNumberOfArgsString_DEPRECATED(e.expected, e.actual);
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
}
LUAU_ASSERT(!"Unknown context");
@ -232,7 +186,7 @@ struct ErrorConverter
std::string operator()(const Luau::IncorrectGenericParameterCount& e) const
{
std::string name = e.name;
if (!e.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !e.typeFun.typePackParams.empty()))
if (!e.typeFun.typeParams.empty() || !e.typeFun.typePackParams.empty())
{
name += "<";
bool first = true;
@ -246,36 +200,25 @@ struct ErrorConverter
name += toString(t);
}
if (FFlag::LuauTypeAliasPacks)
for (TypePackId t : e.typeFun.typePackParams)
{
for (TypePackId t : e.typeFun.typePackParams)
{
if (first)
first = false;
else
name += ", ";
if (first)
first = false;
else
name += ", ";
name += toString(t);
}
name += toString(t);
}
name += ">";
}
if (FFlag::LuauTypeAliasPacks)
{
if (e.typeFun.typeParams.size() != e.actualParameters)
return "Generic type '" + name + "' " +
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
if (e.typeFun.typeParams.size() != e.actualParameters)
return "Generic type '" + name + "' " +
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
return "Generic type '" + name + "' " +
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
}
else
{
return "Generic type '" + name + "' " +
wrongNumberOfArgsString_DEPRECATED(e.typeFun.typeParams.size(), e.actualParameters, /*isTypeArgs*/ true);
}
return "Generic type '" + name + "' " +
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
}
std::string operator()(const Luau::SyntaxError& e) const
@ -591,11 +534,8 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size())
return false;
if (FFlag::LuauTypeAliasPacks)
{
if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
return false;
}
if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
return false;
for (size_t i = 0; i < typeFun.typeParams.size(); ++i)
{
@ -603,13 +543,10 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
return false;
}
if (FFlag::LuauTypeAliasPacks)
for (size_t i = 0; i < typeFun.typePackParams.size(); ++i)
{
for (size_t i = 0; i < typeFun.typePackParams.size(); ++i)
{
if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
return false;
}
if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
return false;
}
return true;
@ -733,14 +670,14 @@ bool containsParseErrorName(const TypeError& error)
}
template<typename T>
void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState cloneState)
{
auto clone = [&](auto&& ty) {
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks);
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks, cloneState);
};
auto visitErrorData = [&](auto&& e) {
copyError(e, destArena, seenTypes, seenTypePacks);
copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
};
if constexpr (false)
@ -864,9 +801,10 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
{
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
auto visitErrorData = [&](auto&& e) {
copyError(e, destArena, seenTypes, seenTypePacks);
copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
};
LUAU_ASSERT(!destArena.typeVars.isFrozen());

View File

@ -18,10 +18,7 @@
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauResolveModuleNameWithoutACurrentModule, false)
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
LUAU_FASTFLAGVARIABLE(LuauPersistDefinitionFileTypes, false)
LUAU_FASTFLAG(LuauNewRequireTrace2)
namespace Luau
{
@ -96,10 +93,11 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
for (const auto& [name, ty] : checkedModule->declaredGlobals)
{
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState);
std::string documentationSymbol = packageName + "/global/" + name;
generateDocumentationSymbols(globalTy, documentationSymbol);
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
@ -110,7 +108,7 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
{
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState);
std::string documentationSymbol = packageName + "/globaltype/" + name;
generateDocumentationSymbols(globalTy.type, documentationSymbol);
targetScope->exportedTypeBindings[name] = globalTy;
@ -427,15 +425,16 @@ CheckResult Frontend::check(const ModuleName& name)
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
for (const auto& [expr, strictTy] : strictModule->astTypes)
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes)
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (const auto& [expr, strictTy] : strictModule->astExpectedTypes)
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
}
stats.timeCheck += getTimestamp() - timestamp;
@ -885,16 +884,13 @@ std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const Module
// If we can't find the current module name, that's because we bypassed the frontend's initializer
// and called typeChecker.check directly. (This is done by autocompleteSource, for example).
// In that case, requires will always fail.
if (FFlag::LuauResolveModuleNameWithoutACurrentModule)
return std::nullopt;
else
throw std::runtime_error("Frontend::resolveModuleName: Unknown currentModuleName '" + currentModuleName + "'");
return std::nullopt;
}
const auto& exprs = it->second.exprs;
const ModuleInfo* info = exprs.find(&pathExpr);
if (!info || (!FFlag::LuauNewRequireTrace2 && info->name.empty()))
if (!info)
return std::nullopt;
return *info;
@ -911,10 +907,7 @@ const ModulePtr FrontendModuleResolver::getModule(const ModuleName& moduleName)
bool FrontendModuleResolver::moduleExists(const ModuleName& moduleName) const
{
if (FFlag::LuauNewRequireTrace2)
return frontend->sourceNodes.count(moduleName) != 0;
else
return frontend->fileResolver->moduleExists(moduleName);
return frontend->sourceNodes.count(moduleName) != 0;
}
std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const

View File

@ -2,8 +2,6 @@
#include "Luau/IostreamHelpers.h"
#include "Luau/ToString.h"
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace Luau
{
@ -94,7 +92,7 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo
{
stream << "IncorrectGenericParameterCount { name = " << error.name;
if (!error.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !error.typeFun.typePackParams.empty()))
if (!error.typeFun.typeParams.empty() || !error.typeFun.typePackParams.empty())
{
stream << "<";
bool first = true;
@ -108,17 +106,14 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo
stream << toString(t);
}
if (FFlag::LuauTypeAliasPacks)
for (TypePackId t : error.typeFun.typePackParams)
{
for (TypePackId t : error.typeFun.typePackParams)
{
if (first)
first = false;
else
stream << ", ";
if (first)
first = false;
else
stream << ", ";
stream << toString(t);
}
stream << toString(t);
}
stream << ">";

View File

@ -5,8 +5,6 @@
#include "Luau/StringUtils.h"
#include "Luau/Common.h"
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace Luau
{
@ -615,12 +613,7 @@ struct AstJsonEncoder : public AstVisitor
writeNode(node, "AstStatTypeAlias", [&]() {
PROP(name);
PROP(generics);
if (FFlag::LuauTypeAliasPacks)
{
PROP(genericPacks);
}
PROP(genericPacks);
PROP(type);
PROP(exported);
});

View File

@ -1,20 +1,20 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Module.h"
#include "Luau/Common.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h"
#include "Luau/Common.h"
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauCloneBoundTables, false)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
namespace Luau
{
@ -120,12 +120,6 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
return allocated;
}
using SeenTypes = std::unordered_map<TypeId, TypeId>;
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
namespace
{
@ -138,11 +132,12 @@ struct TypePackCloner;
struct TypeCloner
{
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
: dest(dest)
, typeId(typeId)
, seenTypes(seenTypes)
, seenTypePacks(seenTypePacks)
, cloneState(cloneState)
{
}
@ -150,8 +145,7 @@ struct TypeCloner
TypeId typeId;
SeenTypes& seenTypes;
SeenTypePacks& seenTypePacks;
bool* encounteredFreeType = nullptr;
CloneState& cloneState;
template<typename T>
void defaultClone(const T& t);
@ -178,13 +172,14 @@ struct TypePackCloner
TypePackId typePackId;
SeenTypes& seenTypes;
SeenTypePacks& seenTypePacks;
bool* encounteredFreeType = nullptr;
CloneState& cloneState;
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
: dest(dest)
, typePackId(typePackId)
, seenTypes(seenTypes)
, seenTypePacks(seenTypePacks)
, cloneState(cloneState)
{
}
@ -197,8 +192,7 @@ struct TypePackCloner
void operator()(const Unifiable::Free& t)
{
if (encounteredFreeType)
*encounteredFreeType = true;
cloneState.encounteredFreeType = true;
TypePackId err = singletonTypes.errorRecoveryTypePack(singletonTypes.anyTypePack);
TypePackId cloned = dest.addTypePack(*err);
@ -218,13 +212,13 @@ struct TypePackCloner
// We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
void operator()(const Unifiable::Bound<TypePackId>& t)
{
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
seenTypePacks[typePackId] = cloned;
}
void operator()(const VariadicTypePack& t)
{
TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, encounteredFreeType)}});
TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, cloneState)}});
seenTypePacks[typePackId] = cloned;
}
@ -236,10 +230,10 @@ struct TypePackCloner
seenTypePacks[typePackId] = cloned;
for (TypeId ty : t.head)
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
if (t.tail)
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, encounteredFreeType);
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, cloneState);
}
};
@ -252,8 +246,7 @@ void TypeCloner::defaultClone(const T& t)
void TypeCloner::operator()(const Unifiable::Free& t)
{
if (encounteredFreeType)
*encounteredFreeType = true;
cloneState.encounteredFreeType = true;
TypeId err = singletonTypes.errorRecoveryType(singletonTypes.anyType);
TypeId cloned = dest.addType(*err);
seenTypes[typeId] = cloned;
@ -266,7 +259,7 @@ void TypeCloner::operator()(const Unifiable::Generic& t)
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
{
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
seenTypes[typeId] = boundTo;
}
@ -294,23 +287,23 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
seenTypes[typeId] = result;
for (TypeId generic : t.generics)
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, encounteredFreeType));
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, cloneState));
for (TypePackId genericPack : t.genericPacks)
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, encounteredFreeType));
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, cloneState));
ftv->tags = t.tags;
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, encounteredFreeType);
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, cloneState);
ftv->argNames = t.argNames;
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, encounteredFreeType);
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, cloneState);
}
void TypeCloner::operator()(const TableTypeVar& t)
{
// If table is now bound to another one, we ignore the content of the original
if (FFlag::LuauCloneBoundTables && t.boundTo)
if (t.boundTo)
{
TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
seenTypes[typeId] = boundTo;
return;
}
@ -326,34 +319,21 @@ void TypeCloner::operator()(const TableTypeVar& t)
ttv->level = TypeLevel{0, 0};
for (const auto& [name, prop] : t.props)
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
if (t.indexer)
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, encounteredFreeType),
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, encounteredFreeType)};
if (!FFlag::LuauCloneBoundTables)
{
if (t.boundTo)
ttv->boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
}
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, cloneState),
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, cloneState)};
for (TypeId& arg : ttv->instantiatedTypeParams)
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId& arg : ttv->instantiatedTypePackParams)
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
}
for (TypePackId& arg : ttv->instantiatedTypePackParams)
arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
if (ttv->state == TableState::Free)
{
if (FFlag::LuauCloneBoundTables || !t.boundTo)
{
if (encounteredFreeType)
*encounteredFreeType = true;
}
cloneState.encounteredFreeType = true;
ttv->state = TableState::Sealed;
}
@ -369,8 +349,8 @@ void TypeCloner::operator()(const MetatableTypeVar& t)
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result);
seenTypes[typeId] = result;
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, encounteredFreeType);
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, cloneState);
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, cloneState);
}
void TypeCloner::operator()(const ClassTypeVar& t)
@ -381,13 +361,13 @@ void TypeCloner::operator()(const ClassTypeVar& t)
seenTypes[typeId] = result;
for (const auto& [name, prop] : t.props)
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
if (t.parent)
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, encounteredFreeType);
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, cloneState);
if (t.metatable)
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, cloneState);
}
void TypeCloner::operator()(const AnyTypeVar& t)
@ -404,7 +384,7 @@ void TypeCloner::operator()(const UnionTypeVar& t)
LUAU_ASSERT(option != nullptr);
for (TypeId ty : t.options)
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
}
void TypeCloner::operator()(const IntersectionTypeVar& t)
@ -416,7 +396,7 @@ void TypeCloner::operator()(const IntersectionTypeVar& t)
LUAU_ASSERT(option != nullptr);
for (TypeId ty : t.parts)
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
}
void TypeCloner::operator()(const LazyTypeVar& t)
@ -426,17 +406,18 @@ void TypeCloner::operator()(const LazyTypeVar& t)
} // anonymous namespace
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
{
if (tp->persistent)
return tp;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypePackId& res = seenTypePacks[tp];
if (res == nullptr)
{
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks};
cloner.encounteredFreeType = encounteredFreeType;
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks, cloneState};
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
}
@ -446,17 +427,18 @@ TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypeP
return res;
}
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
{
if (typeId->persistent)
return typeId;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypeId& res = seenTypes[typeId];
if (res == nullptr)
{
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks};
cloner.encounteredFreeType = encounteredFreeType;
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
@ -467,19 +449,16 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
return res;
}
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
{
TypeFun result;
for (TypeId ty : typeFun.typeParams)
result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId tp : typeFun.typePackParams)
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, encounteredFreeType));
}
for (TypePackId tp : typeFun.typePackParams)
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, cloneState));
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, encounteredFreeType);
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, cloneState);
return result;
}
@ -519,19 +498,18 @@ bool Module::clonePublicInterface()
LUAU_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty());
bool encounteredFreeType = false;
SeenTypePacks seenTypePacks;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
ScopePtr moduleScope = getModuleScope();
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, cloneState);
if (moduleScope->varargPack)
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (auto& pair : moduleScope->exportedTypeBindings)
pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (TypeId ty : moduleScope->returnType)
if (get<GenericTypeVar>(follow(ty)))
@ -540,7 +518,7 @@ bool Module::clonePublicInterface()
freeze(internalTypes);
freeze(interfaceTypes);
return encounteredFreeType;
return cloneState.encounteredFreeType;
}
} // namespace Luau

View File

@ -4,6 +4,8 @@
#include "Luau/VisitTypeVar.h"
LUAU_FASTFLAGVARIABLE(LuauQuantifyVisitOnce, false)
namespace Luau
{
@ -79,7 +81,16 @@ struct Quantifier
void quantify(ModulePtr module, TypeId ty, TypeLevel level)
{
Quantifier q{std::move(module), level};
visitTypeVar(ty, q);
if (FFlag::LuauQuantifyVisitOnce)
{
DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(ty, q, seen);
}
else
{
visitTypeVar(ty, q);
}
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);

View File

@ -4,182 +4,9 @@
#include "Luau/Ast.h"
#include "Luau/Module.h"
LUAU_FASTFLAGVARIABLE(LuauTraceRequireLookupChild, false)
LUAU_FASTFLAGVARIABLE(LuauNewRequireTrace2, false)
namespace Luau
{
namespace
{
struct RequireTracerOld : AstVisitor
{
explicit RequireTracerOld(FileResolver* fileResolver, const ModuleName& currentModuleName)
: fileResolver(fileResolver)
, currentModuleName(currentModuleName)
{
LUAU_ASSERT(!FFlag::LuauNewRequireTrace2);
}
FileResolver* const fileResolver;
ModuleName currentModuleName;
DenseHashMap<AstLocal*, ModuleName> locals{nullptr};
RequireTraceResult result;
std::optional<ModuleName> fromAstFragment(AstExpr* expr)
{
if (auto g = expr->as<AstExprGlobal>(); g && g->name == "script")
return currentModuleName;
return fileResolver->fromAstFragment(expr);
}
bool visit(AstStatLocal* stat) override
{
for (size_t i = 0; i < stat->vars.size; ++i)
{
AstLocal* local = stat->vars.data[i];
if (local->annotation)
{
if (AstTypeTypeof* ann = local->annotation->as<AstTypeTypeof>())
ann->expr->visit(this);
}
if (i < stat->values.size)
{
AstExpr* expr = stat->values.data[i];
expr->visit(this);
const ModuleInfo* info = result.exprs.find(expr);
if (info)
locals[local] = info->name;
}
}
return false;
}
bool visit(AstExprGlobal* global) override
{
std::optional<ModuleName> name = fromAstFragment(global);
if (name)
result.exprs[global] = {*name};
return false;
}
bool visit(AstExprLocal* local) override
{
const ModuleName* name = locals.find(local->local);
if (name)
result.exprs[local] = {*name};
return false;
}
bool visit(AstExprIndexName* indexName) override
{
indexName->expr->visit(this);
const ModuleInfo* info = result.exprs.find(indexName->expr);
if (info)
{
if (indexName->index == "parent" || indexName->index == "Parent")
{
if (auto parent = fileResolver->getParentModuleName(info->name))
result.exprs[indexName] = {*parent};
}
else
result.exprs[indexName] = {fileResolver->concat(info->name, indexName->index.value)};
}
return false;
}
bool visit(AstExprIndexExpr* indexExpr) override
{
indexExpr->expr->visit(this);
const ModuleInfo* info = result.exprs.find(indexExpr->expr);
const AstExprConstantString* str = indexExpr->index->as<AstExprConstantString>();
if (info && str)
{
result.exprs[indexExpr] = {fileResolver->concat(info->name, std::string_view(str->value.data, str->value.size))};
}
indexExpr->index->visit(this);
return false;
}
bool visit(AstExprTypeAssertion* expr) override
{
return false;
}
// If we see game:GetService("StringLiteral") or Game:GetService("StringLiteral"), then rewrite to game.StringLiteral.
// Else traverse arguments and trace requires to them.
bool visit(AstExprCall* call) override
{
for (AstExpr* arg : call->args)
arg->visit(this);
call->func->visit(this);
AstExprGlobal* globalName = call->func->as<AstExprGlobal>();
if (globalName && globalName->name == "require" && call->args.size >= 1)
{
if (const ModuleInfo* moduleInfo = result.exprs.find(call->args.data[0]))
result.requires.push_back({moduleInfo->name, call->location});
return false;
}
AstExprIndexName* indexName = call->func->as<AstExprIndexName>();
if (!indexName)
return false;
std::optional<ModuleName> rootName = fromAstFragment(indexName->expr);
if (FFlag::LuauTraceRequireLookupChild && !rootName)
{
if (const ModuleInfo* moduleInfo = result.exprs.find(indexName->expr))
rootName = moduleInfo->name;
}
if (!rootName)
return false;
bool supportedLookup = indexName->index == "GetService" ||
(FFlag::LuauTraceRequireLookupChild && (indexName->index == "FindFirstChild" || indexName->index == "WaitForChild"));
if (!supportedLookup)
return false;
if (call->args.size != 1)
return false;
AstExprConstantString* name = call->args.data[0]->as<AstExprConstantString>();
if (!name)
return false;
std::string_view v{name->value.data, name->value.size};
if (v.end() != std::find(v.begin(), v.end(), '/'))
return false;
result.exprs[call] = {fileResolver->concat(*rootName, v)};
// 'WaitForChild' can be used on modules that are not available at the typecheck time, but will be available at runtime
// If we fail to find such module, we will not report an UnknownRequire error
if (FFlag::LuauTraceRequireLookupChild && indexName->index == "WaitForChild")
result.exprs[call].optional = true;
return false;
}
};
struct RequireTracer : AstVisitor
{
RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName)
@ -188,7 +15,6 @@ struct RequireTracer : AstVisitor
, currentModuleName(currentModuleName)
, locals(nullptr)
{
LUAU_ASSERT(FFlag::LuauNewRequireTrace2);
}
bool visit(AstExprTypeAssertion* expr) override
@ -328,24 +154,13 @@ struct RequireTracer : AstVisitor
std::vector<AstExprCall*> requires;
};
} // anonymous namespace
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName)
{
if (FFlag::LuauNewRequireTrace2)
{
RequireTraceResult result;
RequireTracer tracer{result, fileResolver, currentModuleName};
root->visit(&tracer);
tracer.process();
return result;
}
else
{
RequireTracerOld tracer{fileResolver, currentModuleName};
root->visit(&tracer);
return tracer.result;
}
RequireTraceResult result;
RequireTracer tracer{result, fileResolver, currentModuleName};
root->visit(&tracer);
tracer.process();
return result;
}
} // namespace Luau

View File

@ -7,8 +7,6 @@
#include <stdexcept>
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000)
LUAU_FASTFLAGVARIABLE(LuauSubstitutionDontReplaceIgnoredTypes, false)
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace Luau
{
@ -39,11 +37,8 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypeId itp : ttv->instantiatedTypeParams)
visitChild(itp);
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp);
}
for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp);
}
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
@ -339,10 +334,10 @@ std::optional<TypeId> Substitution::substitute(TypeId ty)
return std::nullopt;
for (auto [oldTy, newTy] : newTypes)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy))
if (!ignoreChildren(oldTy))
replaceChildren(newTy);
for (auto [oldTp, newTp] : newPacks)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp))
if (!ignoreChildren(oldTp))
replaceChildren(newTp);
TypeId newTy = replace(ty);
return newTy;
@ -359,10 +354,10 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
return std::nullopt;
for (auto [oldTy, newTy] : newTypes)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy))
if (!ignoreChildren(oldTy))
replaceChildren(newTy);
for (auto [oldTp, newTp] : newPacks)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp))
if (!ignoreChildren(oldTp))
replaceChildren(newTp);
TypePackId newTp = replace(tp);
return newTp;
@ -393,10 +388,7 @@ TypeId Substitution::clone(TypeId ty)
clone.name = ttv->name;
clone.syntheticName = ttv->syntheticName;
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
if (FFlag::LuauTypeAliasPacks)
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
clone.tags = ttv->tags;
result = addType(std::move(clone));
}
@ -505,11 +497,8 @@ void Substitution::replaceChildren(TypeId ty)
for (TypeId& itp : ttv->instantiatedTypeParams)
itp = replace(itp);
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId& itp : ttv->instantiatedTypePackParams)
itp = replace(itp);
}
for (TypePackId& itp : ttv->instantiatedTypePackParams)
itp = replace(itp);
}
else if (MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(ty))
{

378
Analysis/src/ToDot.cpp Normal file
View File

@ -0,0 +1,378 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ToDot.h"
#include "Luau/ToString.h"
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
#include "Luau/StringUtils.h"
#include <unordered_map>
#include <unordered_set>
namespace Luau
{
namespace
{
struct StateDot
{
StateDot(ToDotOptions opts)
: opts(opts)
{
}
ToDotOptions opts;
std::unordered_set<TypeId> seenTy;
std::unordered_set<TypePackId> seenTp;
std::unordered_map<TypeId, int> tyToIndex;
std::unordered_map<TypePackId, int> tpToIndex;
int nextIndex = 1;
std::string result;
bool canDuplicatePrimitive(TypeId ty);
void visitChildren(TypeId ty, int index);
void visitChildren(TypePackId ty, int index);
void visitChild(TypeId ty, int parentIndex, const char* linkName = nullptr);
void visitChild(TypePackId tp, int parentIndex, const char* linkName = nullptr);
void startNode(int index);
void finishNode();
void startNodeLabel();
void finishNodeLabel(TypeId ty);
void finishNodeLabel(TypePackId tp);
};
bool StateDot::canDuplicatePrimitive(TypeId ty)
{
if (get<BoundTypeVar>(ty))
return false;
return get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty);
}
void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName)
{
if (!tyToIndex.count(ty) || (opts.duplicatePrimitives && canDuplicatePrimitive(ty)))
tyToIndex[ty] = nextIndex++;
int index = tyToIndex[ty];
if (parentIndex != 0)
{
if (linkName)
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, index, linkName);
else
formatAppend(result, "n%d -> n%d;\n", parentIndex, index);
}
if (opts.duplicatePrimitives && canDuplicatePrimitive(ty))
{
if (get<PrimitiveTypeVar>(ty))
formatAppend(result, "n%d [label=\"%s\"];\n", index, toStringDetailed(ty, {}).name.c_str());
else if (get<AnyTypeVar>(ty))
formatAppend(result, "n%d [label=\"any\"];\n", index);
}
else
{
visitChildren(ty, index);
}
}
void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName)
{
if (!tpToIndex.count(tp))
tpToIndex[tp] = nextIndex++;
if (parentIndex != 0)
{
if (linkName)
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName);
else
formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]);
}
visitChildren(tp, tpToIndex[tp]);
}
void StateDot::startNode(int index)
{
formatAppend(result, "n%d [", index);
}
void StateDot::finishNode()
{
formatAppend(result, "];\n");
}
void StateDot::startNodeLabel()
{
formatAppend(result, "label=\"");
}
void StateDot::finishNodeLabel(TypeId ty)
{
if (opts.showPointers)
formatAppend(result, "\n0x%p", ty);
// additional common attributes can be added here as well
result += "\"";
}
void StateDot::finishNodeLabel(TypePackId tp)
{
if (opts.showPointers)
formatAppend(result, "\n0x%p", tp);
// additional common attributes can be added here as well
result += "\"";
}
void StateDot::visitChildren(TypeId ty, int index)
{
if (seenTy.count(ty))
return;
seenTy.insert(ty);
startNode(index);
startNodeLabel();
if (const BoundTypeVar* btv = get<BoundTypeVar>(ty))
{
formatAppend(result, "BoundTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(btv->boundTo, index);
}
else if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
formatAppend(result, "FunctionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(ftv->argTypes, index, "arg");
visitChild(ftv->retType, index, "ret");
}
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
if (ttv->name)
formatAppend(result, "TableTypeVar %s", ttv->name->c_str());
else if (ttv->syntheticName)
formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str());
else
formatAppend(result, "TableTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
if (ttv->boundTo)
return visitChild(*ttv->boundTo, index, "boundTo");
for (const auto& [name, prop] : ttv->props)
visitChild(prop.type, index, name.c_str());
if (ttv->indexer)
{
visitChild(ttv->indexer->indexType, index, "[index]");
visitChild(ttv->indexer->indexResultType, index, "[value]");
}
for (TypeId itp : ttv->instantiatedTypeParams)
visitChild(itp, index, "typeParam");
for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp, index, "typePackParam");
}
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
formatAppend(result, "MetatableTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(mtv->table, index, "table");
visitChild(mtv->metatable, index, "metatable");
}
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
formatAppend(result, "UnionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
for (TypeId opt : utv->options)
visitChild(opt, index);
}
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
formatAppend(result, "IntersectionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
for (TypeId part : itv->parts)
visitChild(part, index);
}
else if (const GenericTypeVar* gtv = get<GenericTypeVar>(ty))
{
if (gtv->explicitName)
formatAppend(result, "GenericTypeVar %s", gtv->name.c_str());
else
formatAppend(result, "GenericTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
{
formatAppend(result, "FreeTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (get<AnyTypeVar>(ty))
{
formatAppend(result, "AnyTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (get<PrimitiveTypeVar>(ty))
{
formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str());
finishNodeLabel(ty);
finishNode();
}
else if (get<ErrorTypeVar>(ty))
{
formatAppend(result, "ErrorTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty))
{
formatAppend(result, "ClassTypeVar %s", ctv->name.c_str());
finishNodeLabel(ty);
finishNode();
for (const auto& [name, prop] : ctv->props)
visitChild(prop.type, index, name.c_str());
if (ctv->parent)
visitChild(*ctv->parent, index, "[parent]");
if (ctv->metatable)
visitChild(*ctv->metatable, index, "[metatable]");
}
else
{
LUAU_ASSERT(!"unknown type kind");
finishNodeLabel(ty);
finishNode();
}
}
void StateDot::visitChildren(TypePackId tp, int index)
{
if (seenTp.count(tp))
return;
seenTp.insert(tp);
startNode(index);
startNodeLabel();
if (const BoundTypePack* btp = get<BoundTypePack>(tp))
{
formatAppend(result, "BoundTypePack %d", index);
finishNodeLabel(tp);
finishNode();
visitChild(btp->boundTo, index);
}
else if (const TypePack* tpp = get<TypePack>(tp))
{
formatAppend(result, "TypePack %d", index);
finishNodeLabel(tp);
finishNode();
for (TypeId tv : tpp->head)
visitChild(tv, index);
if (tpp->tail)
visitChild(*tpp->tail, index, "tail");
}
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
formatAppend(result, "VariadicTypePack %d", index);
finishNodeLabel(tp);
finishNode();
visitChild(vtp->ty, index);
}
else if (const FreeTypePack* ftp = get<FreeTypePack>(tp))
{
formatAppend(result, "FreeTypePack %d", index);
finishNodeLabel(tp);
finishNode();
}
else if (const GenericTypePack* gtp = get<GenericTypePack>(tp))
{
if (gtp->explicitName)
formatAppend(result, "GenericTypePack %s", gtp->name.c_str());
else
formatAppend(result, "GenericTypePack %d", index);
finishNodeLabel(tp);
finishNode();
}
else if (get<Unifiable::Error>(tp))
{
formatAppend(result, "ErrorTypePack %d", index);
finishNodeLabel(tp);
finishNode();
}
else
{
LUAU_ASSERT(!"unknown type pack kind");
finishNodeLabel(tp);
finishNode();
}
}
} // namespace
std::string toDot(TypeId ty, const ToDotOptions& opts)
{
StateDot state{opts};
state.result = "digraph graphname {\n";
state.visitChild(ty, 0);
state.result += "}";
return state.result;
}
std::string toDot(TypePackId tp, const ToDotOptions& opts)
{
StateDot state{opts};
state.result = "digraph graphname {\n";
state.visitChild(tp, 0);
state.result += "}";
return state.result;
}
std::string toDot(TypeId ty)
{
return toDot(ty, {});
}
std::string toDot(TypePackId tp)
{
return toDot(tp, {});
}
void dumpDot(TypeId ty)
{
printf("%s\n", toDot(ty).c_str());
}
void dumpDot(TypePackId tp)
{
printf("%s\n", toDot(tp).c_str());
}
} // namespace Luau

View File

@ -11,7 +11,7 @@
#include <stdexcept>
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false)
namespace Luau
{
@ -59,11 +59,8 @@ struct FindCyclicTypes
for (TypeId itp : ttv.instantiatedTypeParams)
visitTypeVar(itp, *this, seen);
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId itp : ttv.instantiatedTypePackParams)
visitTypeVar(itp, *this, seen);
}
for (TypePackId itp : ttv.instantiatedTypePackParams)
visitTypeVar(itp, *this, seen);
return exhaustive;
}
@ -248,58 +245,45 @@ struct TypeVarStringifier
void stringify(const std::vector<TypeId>& types, const std::vector<TypePackId>& typePacks)
{
if (types.size() == 0 && (!FFlag::LuauTypeAliasPacks || typePacks.size() == 0))
if (types.size() == 0 && typePacks.size() == 0)
return;
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size()))
if (types.size() || typePacks.size())
state.emit("<");
if (FFlag::LuauTypeAliasPacks)
{
bool first = true;
bool first = true;
for (TypeId ty : types)
{
if (!first)
state.emit(", ");
for (TypeId ty : types)
{
if (!first)
state.emit(", ");
first = false;
stringify(ty);
}
bool singleTp = typePacks.size() == 1;
for (TypePackId tp : typePacks)
{
if (isEmpty(tp) && singleTp)
continue;
if (!first)
state.emit(", ");
else
first = false;
stringify(ty);
}
if (!singleTp)
state.emit("(");
bool singleTp = typePacks.size() == 1;
stringify(tp);
for (TypePackId tp : typePacks)
{
if (isEmpty(tp) && singleTp)
continue;
if (!first)
state.emit(", ");
else
first = false;
if (!singleTp)
state.emit("(");
stringify(tp);
if (!singleTp)
state.emit(")");
}
}
else
{
for (size_t i = 0; i < types.size(); ++i)
{
if (i > 0)
state.emit(", ");
stringify(types[i]);
}
if (!singleTp)
state.emit(")");
}
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size()))
if (types.size() || typePacks.size())
state.emit(">");
}
@ -767,12 +751,23 @@ struct TypePackStringifier
else
state.emit(", ");
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
if (!elemNames.empty() && elemNames[elemIndex])
if (FFlag::LuauFunctionArgumentNameSize)
{
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
if (elemIndex < elemNames.size() && elemNames[elemIndex])
{
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
}
else
{
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
if (!elemNames.empty() && elemNames[elemIndex])
{
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
}
elemIndex++;
@ -929,38 +924,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
result.name += ttv->name ? *ttv->name : *ttv->syntheticName;
if (FFlag::LuauTypeAliasPacks)
{
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
}
else
{
if (ttv->instantiatedTypeParams.empty() && (!FFlag::LuauTypeAliasPacks || ttv->instantiatedTypePackParams.empty()))
return result;
result.name += "<";
bool first = true;
for (TypeId ty : ttv->instantiatedTypeParams)
{
if (!first)
result.name += ", ";
else
first = false;
tvs.stringify(ty);
}
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
{
result.truncated = true;
result.name += "... <TRUNCATED>";
}
else
{
result.name += ">";
}
}
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
return result;
}
@ -1161,17 +1125,37 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
s += ", ";
first = false;
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (!ftv.argNames.empty())
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
s += toString_(*argPackIter);
++argPackIter;
if (!ftv.argNames.empty())
if (FFlag::LuauFunctionArgumentNameSize)
{
LUAU_ASSERT(argNameIter != ftv.argNames.end());
++argNameIter;
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (argNameIter != ftv.argNames.end())
{
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
++argNameIter;
}
else
{
s += "_: ";
}
}
else
{
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (!ftv.argNames.empty())
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
}
s += toString_(*argPackIter);
++argPackIter;
if (!FFlag::LuauFunctionArgumentNameSize)
{
if (!ftv.argNames.empty())
{
LUAU_ASSERT(argNameIter != ftv.argNames.end());
++argNameIter;
}
}
}

View File

@ -10,8 +10,6 @@
#include <limits>
#include <math.h>
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace
{
bool isIdentifierStartChar(char c)
@ -787,7 +785,7 @@ struct Printer
writer.keyword("type");
writer.identifier(a->name.value);
if (a->generics.size > 0 || (FFlag::LuauTypeAliasPacks && a->genericPacks.size > 0))
if (a->generics.size > 0 || a->genericPacks.size > 0)
{
writer.symbol("<");
CommaSeparatorInserter comma(writer);
@ -798,14 +796,11 @@ struct Printer
writer.identifier(o.value);
}
if (FFlag::LuauTypeAliasPacks)
for (auto o : a->genericPacks)
{
for (auto o : a->genericPacks)
{
comma();
writer.identifier(o.value);
writer.symbol("...");
}
comma();
writer.identifier(o.value);
writer.symbol("...");
}
writer.symbol(">");

View File

@ -5,8 +5,6 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(LuauShareTxnSeen, false)
namespace Luau
{
@ -36,11 +34,8 @@ void TxnLog::rollback()
for (auto it = tableChanges.rbegin(); it != tableChanges.rend(); ++it)
std::swap(it->first->boundTo, it->second);
if (FFlag::LuauShareTxnSeen)
{
LUAU_ASSERT(originalSeenSize <= sharedSeen->size());
sharedSeen->resize(originalSeenSize);
}
LUAU_ASSERT(originalSeenSize <= sharedSeen->size());
sharedSeen->resize(originalSeenSize);
}
void TxnLog::concat(TxnLog rhs)
@ -53,45 +48,25 @@ void TxnLog::concat(TxnLog rhs)
tableChanges.insert(tableChanges.end(), rhs.tableChanges.begin(), rhs.tableChanges.end());
rhs.tableChanges.clear();
if (!FFlag::LuauShareTxnSeen)
{
ownedSeen.swap(rhs.ownedSeen);
rhs.ownedSeen.clear();
}
}
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs)
{
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (FFlag::LuauShareTxnSeen)
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
else
return (ownedSeen.end() != std::find(ownedSeen.begin(), ownedSeen.end(), sortedPair));
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
}
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
{
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (FFlag::LuauShareTxnSeen)
sharedSeen->push_back(sortedPair);
else
ownedSeen.push_back(sortedPair);
sharedSeen->push_back(sortedPair);
}
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
{
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (FFlag::LuauShareTxnSeen)
{
LUAU_ASSERT(sortedPair == sharedSeen->back());
sharedSeen->pop_back();
}
else
{
LUAU_ASSERT(sortedPair == ownedSeen.back());
ownedSeen.pop_back();
}
LUAU_ASSERT(sortedPair == sharedSeen->back());
sharedSeen->pop_back();
}
} // namespace Luau

View File

@ -13,8 +13,6 @@
#include <string>
LUAU_FASTFLAG(LuauTypeAliasPacks)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{
char* result = (char*)allocator.allocate(contents.size() + 1);
@ -131,12 +129,9 @@ public:
parameters.data[i] = {Luau::visit(*this, ttv.instantiatedTypeParams[i]->ty), {}};
}
if (FFlag::LuauTypeAliasPacks)
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
{
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
{
parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])};
}
parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])};
}
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName(ttv.name->c_str()), parameters.size != 0, parameters);
@ -250,20 +245,7 @@ public:
AstTypePack* argTailAnnotation = nullptr;
if (argTail)
{
if (FFlag::LuauTypeAliasPacks)
{
argTailAnnotation = rehydrate(*argTail);
}
else
{
TypePackId tail = *argTail;
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
{
argTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
}
}
}
argTailAnnotation = rehydrate(*argTail);
AstArray<std::optional<AstArgumentName>> argNames;
argNames.size = ftv.argNames.size();
@ -292,20 +274,7 @@ public:
AstTypePack* retTailAnnotation = nullptr;
if (retTail)
{
if (FFlag::LuauTypeAliasPacks)
{
retTailAnnotation = rehydrate(*retTail);
}
else
{
TypePackId tail = *retTail;
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
{
retTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
}
}
}
retTailAnnotation = rehydrate(*retTail);
return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation});
@ -518,18 +487,7 @@ public:
const auto& [v, tail] = flatten(ret);
if (tail)
{
if (FFlag::LuauTypeAliasPacks)
{
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
}
else
{
TypePackId tailPack = *tail;
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tailPack))
variadicAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), typeAst(vtp->ty));
}
}
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation};
}

View File

@ -23,22 +23,20 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 500)
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauClassPropertyAccessAsString, false)
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false)
LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false)
LUAU_FASTFLAGVARIABLE(LuauStrictRequire, false)
LUAU_FASTFLAG(LuauSubstitutionDontReplaceIgnoredTypes)
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
LUAU_FASTFLAG(LuauNewRequireTrace2)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauTailArgumentTypeInfo, false)
LUAU_FASTFLAGVARIABLE(LuauModuleRequireErrorPack, false)
namespace Luau
{
@ -562,12 +560,6 @@ ErrorVec TypeChecker::canUnify(TypePackId left, TypePackId right, const Location
return canUnify_(left, right, location);
}
ErrorVec TypeChecker::canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId superTy, TypeId subTy, const Location& location)
{
Unifier state = mkUnifier(seen, location);
return state.canUnify(superTy, subTy);
}
template<typename Id>
ErrorVec TypeChecker::canUnify_(Id superTy, Id subTy, const Location& location)
{
@ -1152,61 +1144,20 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
Location location = scope->typeAliasLocations[name];
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
if (FFlag::LuauTypeAliasPacks)
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
else
bindingsMap[name] = TypeFun{binding->typeParams, errorRecoveryType(anyType)};
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
}
else
{
ScopePtr aliasScope =
FFlag::LuauQuantifyInPlace2 ? childScope(scope, typealias.location, subLevel) : childScope(scope, typealias.location);
if (FFlag::LuauTypeAliasPacks)
{
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks);
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks);
TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
}
else
{
std::vector<TypeId> generics;
for (AstName generic : typealias.generics)
{
Name n = generic.value;
// These generics are the only thing that will ever be added to aliasScope, so we can be certain that
// a collision can only occur when two generic typevars have the same name.
if (aliasScope->privateTypeBindings.end() != aliasScope->privateTypeBindings.find(n))
{
// TODO(jhuelsman): report the exact span of the generic type parameter whose name is a duplicate.
reportError(TypeError{typealias.location, DuplicateGenericParameter{n}});
}
TypeId g;
if (FFlag::LuauRecursiveTypeParameterRestriction)
{
TypeId& cached = scope->typeAliasTypeParameters[n];
if (!cached)
cached = addType(GenericTypeVar{aliasScope->level, n});
g = cached;
}
else
g = addType(GenericTypeVar{aliasScope->level, n});
generics.push_back(g);
aliasScope->privateTypeBindings[n] = TypeFun{{}, g};
}
TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), ty};
}
TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
}
}
else
@ -1223,14 +1174,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, ty};
}
if (FFlag::LuauTypeAliasPacks)
for (TypePackId tp : binding->typePackParams)
{
for (TypePackId tp : binding->typePackParams)
{
auto generic = get<GenericTypePack>(tp);
LUAU_ASSERT(generic);
aliasScope->privateTypePackBindings[generic->name] = tp;
}
auto generic = get<GenericTypePack>(tp);
LUAU_ASSERT(generic);
aliasScope->privateTypePackBindings[generic->name] = tp;
}
TypeId ty = resolveType(aliasScope, *typealias.type);
@ -1241,19 +1189,16 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
{
// Copy can be skipped if this is an identical alias
if (ttv->name != name || ttv->instantiatedTypeParams != binding->typeParams ||
(FFlag::LuauTypeAliasPacks && ttv->instantiatedTypePackParams != binding->typePackParams))
ttv->instantiatedTypePackParams != binding->typePackParams)
{
// This is a shallow clone, original recursive links to self are not updated
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
clone.definitionModuleName = ttv->definitionModuleName;
clone.name = name;
clone.instantiatedTypeParams = binding->typeParams;
if (FFlag::LuauTypeAliasPacks)
clone.instantiatedTypePackParams = binding->typePackParams;
clone.instantiatedTypePackParams = binding->typePackParams;
ty = addType(std::move(clone));
}
@ -1262,9 +1207,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
{
ttv->name = name;
ttv->instantiatedTypeParams = binding->typeParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = binding->typePackParams;
ttv->instantiatedTypePackParams = binding->typePackParams;
}
}
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
@ -1289,7 +1232,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
}
// We don't have generic classes, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && (!FFlag::LuauTypeAliasPacks || lookupType->typePackParams.size() == 0));
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type;
if (!get<ClassTypeVar>(follow(*superTy)))
@ -1851,6 +1794,24 @@ TypeId TypeChecker::checkExprTable(
if (isNonstrictMode() && !getTableType(exprType) && !get<FunctionTypeVar>(exprType))
exprType = anyType;
if (FFlag::LuauPropertiesGetExpectedType && expectedTable)
{
auto it = expectedTable->props.find(key->value.data);
if (it != expectedTable->props.end())
{
Property expectedProp = it->second;
ErrorVec errors = tryUnify(expectedProp.type, exprType, k->location);
if (errors.empty())
exprType = expectedProp.type;
}
else if (expectedTable->indexer && isString(expectedTable->indexer->indexType))
{
ErrorVec errors = tryUnify(expectedTable->indexer->indexResultType, exprType, k->location);
if (errors.empty())
exprType = expectedTable->indexer->indexResultType;
}
}
props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location};
}
else
@ -3744,17 +3705,29 @@ ExprResult<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const L
for (size_t i = 0; i < exprs.size; ++i)
{
AstExpr* expr = exprs.data[i];
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
if (i == lastIndex && (expr->is<AstExprCall>() || expr->is<AstExprVarargs>()))
{
auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
insert(exprPredicates);
if (FFlag::LuauTailArgumentTypeInfo)
{
if (std::optional<TypeId> firstTy = first(typePack))
{
if (!currentModule->astTypes.find(expr))
currentModule->astTypes[expr] = follow(*firstTy);
}
if (expectedType)
currentModule->astExpectedTypes[expr] = *expectedType;
}
tp->tail = typePack;
}
else
{
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
insert(exprPredicates);
@ -3797,7 +3770,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker");
LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str());
if (FFlag::LuauNewRequireTrace2 && moduleInfo.name.empty())
if (moduleInfo.name.empty())
{
if (FFlag::LuauStrictRequire && currentModule->mode == Mode::Strict)
{
@ -3814,7 +3787,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
// There are two reasons why we might fail to find the module:
// either the file does not exist or there's a cycle. If there's a cycle
// we will already have reported the error.
if (!resolver->moduleExists(moduleInfo.name) && (FFlag::LuauTraceRequireLookupChild ? !moduleInfo.optional : true))
if (!resolver->moduleExists(moduleInfo.name) && !moduleInfo.optional)
{
std::string reportedModulePath = resolver->getHumanReadableModuleName(moduleInfo.name);
reportError(TypeError{location, UnknownRequire{reportedModulePath}});
@ -3830,7 +3803,12 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
return errorRecoveryType(scope);
}
std::optional<TypeId> moduleType = first(module->getModuleScope()->returnType);
TypePackId modulePack = module->getModuleScope()->returnType;
if (FFlag::LuauModuleRequireErrorPack && get<Unifiable::Error>(modulePack))
return errorRecoveryType(scope);
std::optional<TypeId> moduleType = first(modulePack);
if (!moduleType)
{
std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name);
@ -3840,7 +3818,8 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks);
CloneState cloneState;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState);
}
void TypeChecker::tablify(TypeId type)
@ -4326,11 +4305,6 @@ Unifier TypeChecker::mkUnifier(const Location& location)
return Unifier{&currentModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState};
}
Unifier TypeChecker::mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location)
{
return Unifier{&currentModule->internalTypes, currentModule->mode, globalScope, seen, location, Variance::Covariant, unifierState};
}
TypeId TypeChecker::freshType(const ScopePtr& scope)
{
return freshType(scope->level);
@ -4477,117 +4451,82 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
return errorRecoveryType(scope);
}
if (lit->parameters.size == 0 && tf->typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf->typePackParams.empty()))
{
if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty())
return tf->type;
}
else if (!FFlag::LuauTypeAliasPacks && lit->parameters.size != tf->typeParams.size())
if (!lit->hasParameterList && !tf->typePackParams.empty())
{
reportError(TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, lit->parameters.size, 0}});
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope);
}
if (FFlag::LuauTypeAliasPacks)
std::vector<TypeId> typeParams;
std::vector<TypeId> extraTypes;
std::vector<TypePackId> typePackParams;
for (size_t i = 0; i < lit->parameters.size; ++i)
{
if (!lit->hasParameterList && !tf->typePackParams.empty())
if (AstType* type = lit->parameters.data[i].type)
{
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope);
}
TypeId ty = resolveType(scope, *type);
std::vector<TypeId> typeParams;
std::vector<TypeId> extraTypes;
std::vector<TypePackId> typePackParams;
for (size_t i = 0; i < lit->parameters.size; ++i)
{
if (AstType* type = lit->parameters.data[i].type)
{
TypeId ty = resolveType(scope, *type);
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
typeParams.push_back(ty);
else if (typePackParams.empty())
extraTypes.push_back(ty);
else
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
}
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
{
TypePackId tp = resolveTypePack(scope, *typePack);
// If we have collected an implicit type pack, materialize it
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we need more regular types, we can use single element type packs to fill those in
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
typeParams.push_back(*first(tp));
else
typePackParams.push_back(tp);
}
}
// If we still haven't meterialized an implicit type pack, do it now
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
typePackParams.push_back(addTypePack({}));
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
{
reportError(
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
if (FFlag::LuauErrorRecoveryType)
{
// Pad the types out with error recovery types
while (typeParams.size() < tf->typeParams.size())
typeParams.push_back(errorRecoveryType(scope));
while (typePackParams.size() < tf->typePackParams.size())
typePackParams.push_back(errorRecoveryTypePack(scope));
}
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
typeParams.push_back(ty);
else if (typePackParams.empty())
extraTypes.push_back(ty);
else
return errorRecoveryType(scope);
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
}
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
{
// If the generic parameters and the type arguments are the same, we are about to
// perform an identity substitution, which we can just short-circuit.
return tf->type;
TypePackId tp = resolveTypePack(scope, *typePack);
// If we have collected an implicit type pack, materialize it
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we need more regular types, we can use single element type packs to fill those in
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
typeParams.push_back(*first(tp));
else
typePackParams.push_back(tp);
}
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
}
else
{
std::vector<TypeId> typeParams;
for (const auto& param : lit->parameters)
typeParams.push_back(resolveType(scope, *param.type));
// If we still haven't meterialized an implicit type pack, do it now
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
typePackParams.push_back(addTypePack({}));
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
{
reportError(
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
if (FFlag::LuauErrorRecoveryType)
{
// If there aren't enough type parameters, pad them out with error recovery types
// (we've already reported the error)
while (typeParams.size() < lit->parameters.size)
// Pad the types out with error recovery types
while (typeParams.size() < tf->typeParams.size())
typeParams.push_back(errorRecoveryType(scope));
while (typePackParams.size() < tf->typePackParams.size())
typePackParams.push_back(errorRecoveryTypePack(scope));
}
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams)
{
// If the generic parameters and the type arguments are the same, we are about to
// perform an identity substitution, which we can just short-circuit.
return tf->type;
}
return instantiateTypeFun(scope, *tf, typeParams, {}, annotation.location);
else
return errorRecoveryType(scope);
}
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
{
// If the generic parameters and the type arguments are the same, we are about to
// perform an identity substitution, which we can just short-circuit.
return tf->type;
}
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
}
else if (const auto& table = annotation.as<AstTypeTable>())
{
@ -4757,7 +4696,7 @@ bool ApplyTypeFunction::isDirty(TypePackId tp)
bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypeVar>(ty))
if (get<GenericTypeVar>(ty))
return true;
else
return false;
@ -4765,7 +4704,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
{
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypePack>(tp))
if (get<GenericTypePack>(tp))
return true;
else
return false;
@ -4788,36 +4727,26 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp)
// Really this should just replace the arguments,
// but for bug-compatibility with existing code, we replace
// all generics by free type variables.
if (FFlag::LuauTypeAliasPacks)
{
TypePackId& arg = typePackArguments[tp];
if (arg)
return arg;
else
return addTypePack(FreeTypePack{level});
}
TypePackId& arg = typePackArguments[tp];
if (arg)
return arg;
else
{
return addTypePack(FreeTypePack{level});
}
}
TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& typePackParams, const Location& location)
{
if (tf.typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf.typePackParams.empty()))
if (tf.typeParams.empty() && tf.typePackParams.empty())
return tf.type;
applyTypeFunction.typeArguments.clear();
for (size_t i = 0; i < tf.typeParams.size(); ++i)
applyTypeFunction.typeArguments[tf.typeParams[i]] = typeParams[i];
if (FFlag::LuauTypeAliasPacks)
{
applyTypeFunction.typePackArguments.clear();
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
}
applyTypeFunction.typePackArguments.clear();
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
applyTypeFunction.currentModule = currentModule;
applyTypeFunction.level = scope->level;
@ -4866,9 +4795,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
if (ttv)
{
ttv->instantiatedTypeParams = typeParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = typePackParams;
ttv->instantiatedTypePackParams = typePackParams;
}
}
else
@ -4884,9 +4811,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
}
ttv->instantiatedTypeParams = typeParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = typePackParams;
ttv->instantiatedTypePackParams = typePackParams;
}
}
@ -4914,7 +4839,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
}
TypeId g;
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks)
if (FFlag::LuauRecursiveTypeParameterRestriction)
{
TypeId& cached = scope->parent->typeAliasTypeParameters[n];
if (!cached)
@ -4944,7 +4869,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
}
TypePackId g;
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks)
if (FFlag::LuauRecursiveTypeParameterRestriction)
{
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
if (!cached)
@ -5245,7 +5170,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
auto typeFun = globalScope->lookupType(typeguardP.kind);
if (!typeFun || !typeFun->typeParams.empty() || (FFlag::LuauTypeAliasPacks && !typeFun->typePackParams.empty()))
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
TypeId type = follow(typeFun->type);

View File

@ -19,7 +19,6 @@
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false)
LUAU_FASTFLAG(LuauErrorRecoveryType)
@ -739,369 +738,6 @@ void persist(TypePackId tp)
}
}
namespace
{
struct StateDot
{
StateDot(ToDotOptions opts)
: opts(opts)
{
}
ToDotOptions opts;
std::unordered_set<TypeId> seenTy;
std::unordered_set<TypePackId> seenTp;
std::unordered_map<TypeId, int> tyToIndex;
std::unordered_map<TypePackId, int> tpToIndex;
int nextIndex = 1;
std::string result;
bool canDuplicatePrimitive(TypeId ty);
void visitChildren(TypeId ty, int index);
void visitChildren(TypePackId ty, int index);
void visitChild(TypeId ty, int parentIndex, const char* linkName = nullptr);
void visitChild(TypePackId tp, int parentIndex, const char* linkName = nullptr);
void startNode(int index);
void finishNode();
void startNodeLabel();
void finishNodeLabel(TypeId ty);
void finishNodeLabel(TypePackId tp);
};
bool StateDot::canDuplicatePrimitive(TypeId ty)
{
if (get<BoundTypeVar>(ty))
return false;
return get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty);
}
void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName)
{
if (!tyToIndex.count(ty) || (opts.duplicatePrimitives && canDuplicatePrimitive(ty)))
tyToIndex[ty] = nextIndex++;
int index = tyToIndex[ty];
if (parentIndex != 0)
{
if (linkName)
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, index, linkName);
else
formatAppend(result, "n%d -> n%d;\n", parentIndex, index);
}
if (opts.duplicatePrimitives && canDuplicatePrimitive(ty))
{
if (get<PrimitiveTypeVar>(ty))
formatAppend(result, "n%d [label=\"%s\"];\n", index, toStringDetailed(ty, {}).name.c_str());
else if (get<AnyTypeVar>(ty))
formatAppend(result, "n%d [label=\"any\"];\n", index);
}
else
{
visitChildren(ty, index);
}
}
void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName)
{
if (!tpToIndex.count(tp))
tpToIndex[tp] = nextIndex++;
if (linkName)
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName);
else
formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]);
visitChildren(tp, tpToIndex[tp]);
}
void StateDot::startNode(int index)
{
formatAppend(result, "n%d [", index);
}
void StateDot::finishNode()
{
formatAppend(result, "];\n");
}
void StateDot::startNodeLabel()
{
formatAppend(result, "label=\"");
}
void StateDot::finishNodeLabel(TypeId ty)
{
if (opts.showPointers)
formatAppend(result, "\n0x%p", ty);
// additional common attributes can be added here as well
result += "\"";
}
void StateDot::finishNodeLabel(TypePackId tp)
{
if (opts.showPointers)
formatAppend(result, "\n0x%p", tp);
// additional common attributes can be added here as well
result += "\"";
}
void StateDot::visitChildren(TypeId ty, int index)
{
if (seenTy.count(ty))
return;
seenTy.insert(ty);
startNode(index);
startNodeLabel();
if (const BoundTypeVar* btv = get<BoundTypeVar>(ty))
{
formatAppend(result, "BoundTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(btv->boundTo, index);
}
else if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
formatAppend(result, "FunctionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(ftv->argTypes, index, "arg");
visitChild(ftv->retType, index, "ret");
}
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
if (ttv->name)
formatAppend(result, "TableTypeVar %s", ttv->name->c_str());
else if (ttv->syntheticName)
formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str());
else
formatAppend(result, "TableTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
if (ttv->boundTo)
return visitChild(*ttv->boundTo, index, "boundTo");
for (const auto& [name, prop] : ttv->props)
visitChild(prop.type, index, name.c_str());
if (ttv->indexer)
{
visitChild(ttv->indexer->indexType, index, "[index]");
visitChild(ttv->indexer->indexResultType, index, "[value]");
}
for (TypeId itp : ttv->instantiatedTypeParams)
visitChild(itp, index, "typeParam");
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp, index, "typePackParam");
}
}
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
formatAppend(result, "MetatableTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(mtv->table, index, "table");
visitChild(mtv->metatable, index, "metatable");
}
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
formatAppend(result, "UnionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
for (TypeId opt : utv->options)
visitChild(opt, index);
}
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
formatAppend(result, "IntersectionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
for (TypeId part : itv->parts)
visitChild(part, index);
}
else if (const GenericTypeVar* gtv = get<GenericTypeVar>(ty))
{
if (gtv->explicitName)
formatAppend(result, "GenericTypeVar %s", gtv->name.c_str());
else
formatAppend(result, "GenericTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
{
formatAppend(result, "FreeTypeVar %d", ftv->index);
finishNodeLabel(ty);
finishNode();
}
else if (get<AnyTypeVar>(ty))
{
formatAppend(result, "AnyTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (get<PrimitiveTypeVar>(ty))
{
formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str());
finishNodeLabel(ty);
finishNode();
}
else if (get<ErrorTypeVar>(ty))
{
formatAppend(result, "ErrorTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty))
{
formatAppend(result, "ClassTypeVar %s", ctv->name.c_str());
finishNodeLabel(ty);
finishNode();
for (const auto& [name, prop] : ctv->props)
visitChild(prop.type, index, name.c_str());
if (ctv->parent)
visitChild(*ctv->parent, index, "[parent]");
if (ctv->metatable)
visitChild(*ctv->metatable, index, "[metatable]");
}
else
{
LUAU_ASSERT(!"unknown type kind");
finishNodeLabel(ty);
finishNode();
}
}
void StateDot::visitChildren(TypePackId tp, int index)
{
if (seenTp.count(tp))
return;
seenTp.insert(tp);
startNode(index);
startNodeLabel();
if (const BoundTypePack* btp = get<BoundTypePack>(tp))
{
formatAppend(result, "BoundTypePack %d", index);
finishNodeLabel(tp);
finishNode();
visitChild(btp->boundTo, index);
}
else if (const TypePack* tpp = get<TypePack>(tp))
{
formatAppend(result, "TypePack %d", index);
finishNodeLabel(tp);
finishNode();
for (TypeId tv : tpp->head)
visitChild(tv, index);
if (tpp->tail)
visitChild(*tpp->tail, index, "tail");
}
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
formatAppend(result, "VariadicTypePack %d", index);
finishNodeLabel(tp);
finishNode();
visitChild(vtp->ty, index);
}
else if (const FreeTypePack* ftp = get<FreeTypePack>(tp))
{
formatAppend(result, "FreeTypePack %d", ftp->index);
finishNodeLabel(tp);
finishNode();
}
else if (const GenericTypePack* gtp = get<GenericTypePack>(tp))
{
if (gtp->explicitName)
formatAppend(result, "GenericTypePack %s", gtp->name.c_str());
else
formatAppend(result, "GenericTypePack %d", gtp->index);
finishNodeLabel(tp);
finishNode();
}
else if (get<Unifiable::Error>(tp))
{
formatAppend(result, "ErrorTypePack %d", index);
finishNodeLabel(tp);
finishNode();
}
else
{
LUAU_ASSERT(!"unknown type pack kind");
finishNodeLabel(tp);
finishNode();
}
}
} // namespace
std::string toDot(TypeId ty, const ToDotOptions& opts)
{
StateDot state{opts};
state.result = "digraph graphname {\n";
state.visitChild(ty, 0);
state.result += "}";
return state.result;
}
std::string toDot(TypePackId tp, const ToDotOptions& opts)
{
StateDot state{opts};
state.result = "digraph graphname {\n";
state.visitChild(tp, 0);
state.result += "}";
return state.result;
}
std::string toDot(TypeId ty)
{
return toDot(ty, {});
}
std::string toDot(TypePackId tp)
{
return toDot(tp, {});
}
void dumpDot(TypeId ty)
{
printf("%s\n", toDot(ty).c_str());
}
void dumpDot(TypePackId tp)
{
printf("%s\n", toDot(tp).c_str());
}
const TypeLevel* getLevel(TypeId ty)
{
ty = follow(ty);

View File

@ -18,9 +18,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance, false);
LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false)
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false)
LUAU_FASTFLAGVARIABLE(LuauTypecheckOpts, false)
LUAU_FASTFLAG(LuauShareTxnSeen);
LUAU_FASTFLAGVARIABLE(LuauCacheUnifyTableResults, false)
LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false)
LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false)
@ -136,38 +133,19 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Locati
, globalScope(std::move(globalScope))
, location(location)
, variance(variance)
, counters(&countersData)
, counters_DEPRECATED(std::make_shared<UnifierCounters>())
, sharedState(sharedState)
{
LUAU_ASSERT(sharedState.iceHandler);
}
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& ownedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters)
: types(types)
, mode(mode)
, globalScope(std::move(globalScope))
, log(ownedSeen)
, location(location)
, variance(variance)
, counters(counters ? counters : &countersData)
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
, sharedState(sharedState)
{
LUAU_ASSERT(sharedState.iceHandler);
}
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters)
Variance variance, UnifierSharedState& sharedState)
: types(types)
, mode(mode)
, globalScope(std::move(globalScope))
, log(sharedSeen)
, location(location)
, variance(variance)
, counters(counters ? counters : &countersData)
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
, sharedState(sharedState)
{
LUAU_ASSERT(sharedState.iceHandler);
@ -175,26 +153,18 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<
void Unifier::tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
{
if (FFlag::LuauTypecheckOpts)
counters->iterationCount = 0;
else
counters_DEPRECATED->iterationCount = 0;
sharedState.counters.iterationCount = 0;
tryUnify_(superTy, subTy, isFunctionCall, isIntersection);
}
void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
{
RecursionLimiter _ra(
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
if (FFlag::LuauTypecheckOpts)
++counters->iterationCount;
else
++counters_DEPRECATED->iterationCount;
++sharedState.counters.iterationCount;
if (FInt::LuauTypeInferIterationLimit > 0 &&
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
{
errors.push_back(TypeError{location, UnificationTooComplex{}});
return;
@ -302,7 +272,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
return tryUnifyWithAny(subTy, superTy);
bool cacheEnabled = FFlag::LuauCacheUnifyTableResults && !isFunctionCall && !isIntersection;
bool cacheEnabled = !isFunctionCall && !isIntersection;
auto& cache = sharedState.cachedUnify;
// What if the types are immutable and we proved their relation before
@ -563,8 +533,6 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
void Unifier::cacheResult(TypeId superTy, TypeId subTy)
{
LUAU_ASSERT(FFlag::LuauCacheUnifyTableResults);
bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
if (superTyInfo && *superTyInfo)
@ -686,10 +654,7 @@ ErrorVec Unifier::canUnify(TypePackId superTy, TypePackId subTy, bool isFunction
void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
{
if (FFlag::LuauTypecheckOpts)
counters->iterationCount = 0;
else
counters_DEPRECATED->iterationCount = 0;
sharedState.counters.iterationCount = 0;
tryUnify_(superTp, subTp, isFunctionCall);
}
@ -700,16 +665,11 @@ void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall
*/
void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
{
RecursionLimiter _ra(
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
if (FFlag::LuauTypecheckOpts)
++counters->iterationCount;
else
++counters_DEPRECATED->iterationCount;
++sharedState.counters.iterationCount;
if (FInt::LuauTypeInferIterationLimit > 0 &&
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
{
errors.push_back(TypeError{location, UnificationTooComplex{}});
return;
@ -1727,39 +1687,8 @@ void Unifier::tryUnify(const TableIndexer& superIndexer, const TableIndexer& sub
tryUnify_(superIndexer.indexResultType, subIndexer.indexResultType);
}
static void queueTypePack_DEPRECATED(
std::vector<TypeId>& queue, std::unordered_set<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOpts);
while (true)
{
a = follow(a);
if (seenTypePacks.count(a))
break;
seenTypePacks.insert(a);
if (get<Unifiable::Free>(a))
{
state.log(a);
*asMutable(a) = Unifiable::Bound{anyTypePack};
}
else if (auto tp = get<TypePack>(a))
{
queue.insert(queue.end(), tp->head.begin(), tp->head.end());
if (tp->tail)
a = *tp->tail;
else
break;
}
}
}
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
while (true)
{
a = follow(a);
@ -1837,66 +1766,9 @@ void Unifier::tryUnifyVariadics(TypePackId superTp, TypePackId subTp, bool rever
}
}
static void tryUnifyWithAny_DEPRECATED(
std::vector<TypeId>& queue, Unifier& state, std::unordered_set<TypePackId>& seenTypePacks, TypeId anyType, TypePackId anyTypePack)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOpts);
std::unordered_set<TypeId> seen;
while (!queue.empty())
{
TypeId ty = follow(queue.back());
queue.pop_back();
if (seen.count(ty))
continue;
seen.insert(ty);
if (get<FreeTypeVar>(ty))
{
state.log(ty);
*asMutable(ty) = BoundTypeVar{anyType};
}
else if (auto fun = get<FunctionTypeVar>(ty))
{
queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->argTypes, anyTypePack);
queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->retType, anyTypePack);
}
else if (auto table = get<TableTypeVar>(ty))
{
for (const auto& [_name, prop] : table->props)
queue.push_back(prop.type);
if (table->indexer)
{
queue.push_back(table->indexer->indexType);
queue.push_back(table->indexer->indexResultType);
}
}
else if (auto mt = get<MetatableTypeVar>(ty))
{
queue.push_back(mt->table);
queue.push_back(mt->metatable);
}
else if (get<ClassTypeVar>(ty))
{
// ClassTypeVars never contain free typevars.
}
else if (auto union_ = get<UnionTypeVar>(ty))
queue.insert(queue.end(), union_->options.begin(), union_->options.end());
else if (auto intersection = get<IntersectionTypeVar>(ty))
queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end());
else
{
} // Primitives, any, errors, and generics are left untouched.
}
}
static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
TypeId anyType, TypePackId anyTypePack)
{
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
while (!queue.empty())
{
TypeId ty = follow(queue.back());
@ -1949,43 +1821,20 @@ void Unifier::tryUnifyWithAny(TypeId any, TypeId ty)
{
LUAU_ASSERT(get<AnyTypeVar>(any) || get<ErrorTypeVar>(any));
if (FFlag::LuauTypecheckOpts)
{
// These types are not visited in general loop below
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
return;
}
// These types are not visited in general loop below
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
return;
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes.anyType}});
const TypePackId anyTP = get<AnyTypeVar>(any) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
if (FFlag::LuauTypecheckOpts)
{
std::vector<TypeId> queue = {ty};
std::vector<TypeId> queue = {ty};
if (FFlag::LuauCacheUnifyTableResults)
{
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP);
}
else
{
tempSeenTy_DEPRECATED.clear();
tempSeenTp_DEPRECATED.clear();
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, singletonTypes.anyType, anyTP);
}
}
else
{
std::unordered_set<TypePackId> seenTypePacks;
std::vector<TypeId> queue = {ty};
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, singletonTypes.anyType, anyTP);
}
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP);
}
void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
@ -1994,38 +1843,14 @@ void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
const TypeId anyTy = singletonTypes.errorRecoveryType();
if (FFlag::LuauTypecheckOpts)
{
std::vector<TypeId> queue;
std::vector<TypeId> queue;
if (FFlag::LuauCacheUnifyTableResults)
{
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any);
queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any);
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any);
}
else
{
tempSeenTy_DEPRECATED.clear();
tempSeenTp_DEPRECATED.clear();
queueTypePack(queue, tempSeenTp_DEPRECATED, *this, ty, any);
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, anyTy, any);
}
}
else
{
std::unordered_set<TypePackId> seenTypePacks;
std::vector<TypeId> queue;
queueTypePack_DEPRECATED(queue, seenTypePacks, *this, ty, any);
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, anyTy, any);
}
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any);
}
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
@ -2035,46 +1860,22 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
void Unifier::occursCheck(TypeId needle, TypeId haystack)
{
std::unordered_set<TypeId> seen_DEPRECATED;
sharedState.tempSeenTy.clear();
if (FFlag::LuauCacheUnifyTableResults)
{
if (FFlag::LuauTypecheckOpts)
sharedState.tempSeenTy.clear();
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTy, needle, haystack);
}
else
{
if (FFlag::LuauTypecheckOpts)
tempSeenTy_DEPRECATED.clear();
return occursCheck(seen_DEPRECATED, tempSeenTy_DEPRECATED, needle, haystack);
}
return occursCheck(sharedState.tempSeenTy, needle, haystack);
}
void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
{
RecursionLimiter _ra(
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
needle = follow(needle);
haystack = follow(haystack);
if (FFlag::LuauTypecheckOpts)
{
if (seen.find(haystack))
return;
if (seen.find(haystack))
return;
seen.insert(haystack);
}
else
{
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
return;
seen_DEPRECATED.insert(haystack);
}
seen.insert(haystack);
if (get<Unifiable::Error>(needle))
return;
@ -2091,7 +1892,7 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
}
auto check = [&](TypeId tv) {
occursCheck(seen_DEPRECATED, seen, needle, tv);
occursCheck(seen, needle, tv);
};
if (get<FreeTypeVar>(haystack))
@ -2121,43 +1922,20 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
void Unifier::occursCheck(TypePackId needle, TypePackId haystack)
{
std::unordered_set<TypePackId> seen_DEPRECATED;
sharedState.tempSeenTp.clear();
if (FFlag::LuauCacheUnifyTableResults)
{
if (FFlag::LuauTypecheckOpts)
sharedState.tempSeenTp.clear();
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTp, needle, haystack);
}
else
{
if (FFlag::LuauTypecheckOpts)
tempSeenTp_DEPRECATED.clear();
return occursCheck(seen_DEPRECATED, tempSeenTp_DEPRECATED, needle, haystack);
}
return occursCheck(sharedState.tempSeenTp, needle, haystack);
}
void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
{
needle = follow(needle);
haystack = follow(haystack);
if (FFlag::LuauTypecheckOpts)
{
if (seen.find(haystack))
return;
if (seen.find(haystack))
return;
seen.insert(haystack);
}
else
{
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
return;
seen_DEPRECATED.insert(haystack);
}
seen.insert(haystack);
if (get<Unifiable::Error>(needle))
return;
@ -2165,8 +1943,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
if (!get<Unifiable::Free>(needle))
ice("Expected needle pack to be free");
RecursionLimiter _ra(
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
while (!get<ErrorTypeVar>(haystack))
{
@ -2186,8 +1963,8 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
{
if (auto f = get<FunctionTypeVar>(follow(ty)))
{
occursCheck(seen_DEPRECATED, seen, needle, f->argTypes);
occursCheck(seen_DEPRECATED, seen, needle, f->retType);
occursCheck(seen, needle, f->argTypes);
occursCheck(seen, needle, f->retType);
}
}
}
@ -2204,10 +1981,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
Unifier Unifier::makeChildUnifier()
{
if (FFlag::LuauShareTxnSeen)
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
else
return Unifier{types, mode, globalScope, log.ownedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState};
}
bool Unifier::isNonstrictMode() const

View File

@ -13,8 +13,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false)
LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAliasPacks, false)
LUAU_FASTFLAGVARIABLE(LuauParseTypePackTypeParameters, false)
LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false)
@ -782,8 +780,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
AstType* type = parseTypeAnnotation();
return allocator.alloc<AstStatTypeAlias>(
Location(start, type->location), name->name, generics, FFlag::LuauTypeAliasPacks ? genericPacks : AstArray<AstName>{}, type, exported);
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, generics, genericPacks, type, exported);
}
AstDeclaredClassProp Parser::parseDeclaredClassMethod()
@ -1602,30 +1599,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
}
if (FFlag::LuauParseTypePackTypeParameters)
bool hasParameters = false;
AstArray<AstTypeOrPack> parameters{};
if (lexer.current().type == '<')
{
bool hasParameters = false;
AstArray<AstTypeOrPack> parameters{};
if (lexer.current().type == '<')
{
hasParameters = true;
parameters = parseTypeParams();
}
Location end = lexer.previousLocation();
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
hasParameters = true;
parameters = parseTypeParams();
}
else
{
AstArray<AstTypeOrPack> generics = parseTypeParams();
Location end = lexer.previousLocation();
Location end = lexer.previousLocation();
// false in 'hasParameterList' as it is not used without FFlagLuauTypeAliasPacks
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, false, generics), {}};
}
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
}
else if (lexer.current().type == '{')
{
@ -2414,37 +2399,24 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
while (true)
{
if (FFlag::LuauParseTypePackTypeParameters)
if (shouldParseTypePackAnnotation(lexer))
{
if (shouldParseTypePackAnnotation(lexer))
{
auto typePack = parseTypePackAnnotation();
auto typePack = parseTypePackAnnotation();
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
parameters.push_back({{}, typePack});
}
else if (lexer.current().type == '(')
{
auto [type, typePack] = parseTypeOrPackAnnotation();
parameters.push_back({{}, typePack});
}
else if (lexer.current().type == '(')
{
auto [type, typePack] = parseTypeOrPackAnnotation();
if (typePack)
{
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
parameters.push_back({{}, typePack});
}
else
{
parameters.push_back({type, {}});
}
}
else if (lexer.current().type == '>' && parameters.empty())
{
break;
}
if (typePack)
parameters.push_back({{}, typePack});
else
{
parameters.push_back({parseTypeAnnotation(), {}});
}
parameters.push_back({type, {}});
}
else if (lexer.current().type == '>' && parameters.empty())
{
break;
}
else
{

View File

@ -121,7 +121,7 @@ struct CliFileResolver : Luau::FileResolver
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
{
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau";
if (!moduleExists(name))
if (!readFile(name))
{
// fall back to .lua if a module with .luau doesn't exist
name = std::string(expr->value.data, expr->value.size) + ".lua";
@ -132,27 +132,6 @@ struct CliFileResolver : Luau::FileResolver
return std::nullopt;
}
bool moduleExists(const Luau::ModuleName& name) const override
{
return !!readFile(name);
}
std::optional<Luau::ModuleName> fromAstFragment(Luau::AstExpr* expr) const override
{
return std::nullopt;
}
Luau::ModuleName concat(const Luau::ModuleName& lhs, std::string_view rhs) const override
{
return lhs + "/" + std::string(rhs);
}
std::optional<Luau::ModuleName> getParentModuleName(const Luau::ModuleName& name) const override
{
return std::nullopt;
}
};
struct CliConfigResolver : Luau::ConfigResolver

View File

@ -526,4 +526,3 @@ int main(int argc, char** argv)
return failed;
}
}

View File

@ -321,13 +321,15 @@ struct Compiler
compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i));
}
setDebugLine(expr->func);
setDebugLineEnd(expr->func);
if (expr->self)
{
AstExprIndexName* fi = expr->func->as<AstExprIndexName>();
LUAU_ASSERT(fi);
setDebugLine(fi->indexLocation);
BytecodeBuilder::StringRef iname = sref(fi->index);
int32_t cid = bytecode.addConstantString(iname);
if (cid < 0)
@ -1313,6 +1315,8 @@ struct Compiler
RegScope rs(this);
uint8_t reg = compileExprAuto(expr->expr, rs);
setDebugLine(expr->indexLocation);
BytecodeBuilder::StringRef iname = sref(expr->index);
int32_t cid = bytecode.addConstantString(iname);
if (cid < 0)
@ -2710,6 +2714,12 @@ struct Compiler
bytecode.setDebugLine(node->location.begin.line + 1);
}
void setDebugLine(const Location& location)
{
if (options.debugLevel >= 1)
bytecode.setDebugLine(location.begin.line + 1);
}
void setDebugLineEnd(AstNode* node)
{
if (options.debugLevel >= 1)
@ -3650,7 +3660,7 @@ struct Compiler
{
if (options.vectorLib)
{
if (builtin.object == options.vectorLib && builtin.method == options.vectorCtor)
if (builtin.isMethod(options.vectorLib, options.vectorCtor))
return LBF_VECTOR;
}
else

View File

@ -35,7 +35,7 @@ ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Analyze.cpp
ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze
FUZZ_SOURCES=$(wildcard fuzz/*.cpp)
FUZZ_SOURCES=$(wildcard fuzz/*.cpp) fuzz/luau.pb.cpp
FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o)
TESTS_ARGS=
@ -167,8 +167,8 @@ fuzz/luau.pb.cpp: fuzz/luau.proto build/libprotobuf-mutator
cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=.
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator
$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator
$(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp
$(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp
build/libprotobuf-mutator:
git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator

View File

@ -54,6 +54,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Scope.h
Analysis/include/Luau/Substitution.h
Analysis/include/Luau/Symbol.h
Analysis/include/Luau/ToDot.h
Analysis/include/Luau/TopoSortStatements.h
Analysis/include/Luau/ToString.h
Analysis/include/Luau/Transpiler.h
@ -86,6 +87,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/Scope.cpp
Analysis/src/Substitution.cpp
Analysis/src/Symbol.cpp
Analysis/src/ToDot.cpp
Analysis/src/TopoSortStatements.cpp
Analysis/src/ToString.cpp
Analysis/src/Transpiler.cpp
@ -118,6 +120,7 @@ target_sources(Luau.VM PRIVATE
VM/src/ldo.cpp
VM/src/lfunc.cpp
VM/src/lgc.cpp
VM/src/lgcdebug.cpp
VM/src/linit.cpp
VM/src/lmathlib.cpp
VM/src/lmem.cpp
@ -194,6 +197,7 @@ if(TARGET Luau.UnitTest)
tests/RequireTracer.test.cpp
tests/StringUtils.test.cpp
tests/Symbol.test.cpp
tests/ToDot.test.cpp
tests/TopoSort.test.cpp
tests/ToString.test.cpp
tests/Transpiler.test.cpp

View File

@ -189,7 +189,7 @@ LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);
LUA_API int lua_getreadonly(lua_State* L, int idx);
LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled);
LUA_API void* lua_newuserdata(lua_State* L, size_t sz, int tag);
LUA_API void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag);
LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
LUA_API int lua_getmetatable(lua_State* L, int objindex);
LUA_API void lua_getfenv(lua_State* L, int idx);
@ -288,6 +288,7 @@ LUA_API void lua_unref(lua_State* L, int ref);
#define lua_pop(L, n) lua_settop(L, -(n)-1)
#define lua_newtable(L) lua_createtable(L, 0, 0)
#define lua_newuserdata(L, s) lua_newuserdatatagged(L, s, 0)
#define lua_strlen(L, i) lua_objlen(L, (i))

View File

@ -13,6 +13,8 @@
#include <string.h>
LUAU_FASTFLAG(LuauActivateBeforeExec)
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
"$URL: www.lua.org $\n";
@ -937,14 +939,21 @@ void lua_call(lua_State* L, int nargs, int nresults)
checkresults(L, nargs, nresults);
func = L->top - (nargs + 1);
int wasActive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
if (FFlag::LuauActivateBeforeExec)
{
luaD_call(L, func, nresults);
}
else
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
luaD_call(L, func, nresults);
luaD_call(L, func, nresults);
if (!wasActive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
adjustresults(L, nresults);
return;
@ -985,14 +994,21 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc)
c.func = L->top - (nargs + 1); /* function to be called */
c.nresults = nresults;
int wasActive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
if (FFlag::LuauActivateBeforeExec)
{
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
}
else
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
if (!wasActive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
adjustresults(L, nresults);
return status;
@ -1166,7 +1182,7 @@ void lua_concat(lua_State* L, int n)
return;
}
void* lua_newuserdata(lua_State* L, size_t sz, int tag)
void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag)
{
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
luaC_checkGC(L);
@ -1251,6 +1267,7 @@ uintptr_t lua_encodepointer(lua_State* L, uintptr_t p)
int lua_ref(lua_State* L, int idx)
{
api_check(L, idx != LUA_REGISTRYINDEX); /* idx is a stack index for value */
int ref = LUA_REFNIL;
global_State* g = L->global;
StkId p = index2adr(L, idx);

View File

@ -401,7 +401,7 @@ static int luaB_newproxy(lua_State* L)
bool needsmt = lua_toboolean(L, 1);
lua_newuserdata(L, 0, 0);
lua_newuserdata(L, 0);
if (needsmt)
{

View File

@ -17,9 +17,9 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauExceptionMessageFix, false)
LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false)
LUAU_FASTFLAG(LuauCoroutineClose)
LUAU_FASTFLAGVARIABLE(LuauActivateBeforeExec, false)
/*
** {======================================================
@ -74,35 +74,28 @@ public:
const char* what() const throw() override
{
if (FFlag::LuauExceptionMessageFix)
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
{
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
if (const char* str = lua_tostring(L, -1))
{
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
if (const char* str = lua_tostring(L, -1))
{
return str;
}
}
switch (status)
{
case LUA_ERRRUN:
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
case LUA_ERRSYNTAX:
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
case LUA_ERRMEM:
return "lua_exception: " LUA_MEMERRMSG;
case LUA_ERRERR:
return "lua_exception: " LUA_ERRERRMSG;
default:
return "lua_exception: unexpected exception status";
return str;
}
}
else
switch (status)
{
return lua_tostring(L, -1);
case LUA_ERRRUN:
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
case LUA_ERRSYNTAX:
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
case LUA_ERRMEM:
return "lua_exception: " LUA_MEMERRMSG;
case LUA_ERRERR:
return "lua_exception: " LUA_ERRERRMSG;
default:
return "lua_exception: unexpected exception status";
}
}
@ -234,7 +227,22 @@ void luaD_call(lua_State* L, StkId func, int nResults)
if (luau_precall(L, func, nResults) == PCRLUA)
{ /* is a Lua function? */
L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */
luau_execute(L); /* call it */
if (FFlag::LuauActivateBeforeExec)
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
luau_execute(L); /* call it */
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
else
{
luau_execute(L); /* call it */
}
}
L->nCcalls--;
luaC_checkGC(L);
@ -527,10 +535,10 @@ static void restore_stack_limit(lua_State* L)
int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
{
int status;
unsigned short oldnCcalls = L->nCcalls;
ptrdiff_t old_ci = saveci(L, L->ci);
status = luaD_rawrunprotected(L, func, u);
int oldactive = luaC_threadactive(L);
int status = luaD_rawrunprotected(L, func, u);
if (status != 0)
{
// call user-defined error function (used in xpcall)
@ -541,6 +549,13 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
status = LUA_ERRERR;
}
if (FFlag::LuauActivateBeforeExec)
{
// since the call failed with an error, we might have to reset the 'active' thread state
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
if (FFlag::LuauCcallRestoreFix)
{
// Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.

View File

@ -10,9 +10,7 @@
#include "ldo.h"
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauRescanGrayAgainForwardBarrier, false)
LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false)
LUAU_FASTFLAG(LuauArrayBoundary)
@ -988,7 +986,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
GCObject* o = obj2gco(t);
// in the second propagation stage, table assignment barrier works as a forward barrier
if (FFlag::LuauRescanGrayAgainForwardBarrier && g->gcstate == GCSpropagateagain)
if (g->gcstate == GCSpropagateagain)
{
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
reallymarkobject(g, v);
@ -1044,550 +1042,6 @@ void luaC_linkupval(lua_State* L, UpVal* uv)
}
}
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{
LUAU_ASSERT(!isdead(g, t));
if (keepinvariant(g))
{
/* basic incremental invariant: black can't point to white */
LUAU_ASSERT(!(isblack(f) && iswhite(t)));
}
}
static void validateref(global_State* g, GCObject* f, TValue* v)
{
if (iscollectable(v))
{
LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt);
validateobjref(g, f, gcvalue(v));
}
}
static void validatetable(global_State* g, Table* h)
{
int sizenode = 1 << h->lsizenode;
if (FFlag::LuauArrayBoundary)
LUAU_ASSERT(h->lastfree <= sizenode);
else
LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode);
if (h->metatable)
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
for (int i = 0; i < h->sizearray; ++i)
validateref(g, obj2gco(h), &h->array[i]);
for (int i = 0; i < sizenode; ++i)
{
LuaNode* n = &h->node[i];
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode);
if (!ttisnil(gval(n)))
{
TValue k = {};
k.tt = gkey(n)->tt;
k.value = gkey(n)->value;
validateref(g, obj2gco(h), &k);
validateref(g, obj2gco(h), gval(n));
}
}
}
static void validateclosure(global_State* g, Closure* cl)
{
validateobjref(g, obj2gco(cl), obj2gco(cl->env));
if (cl->isC)
{
for (int i = 0; i < cl->nupvalues; ++i)
validateref(g, obj2gco(cl), &cl->c.upvals[i]);
}
else
{
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
validateobjref(g, obj2gco(cl), obj2gco(cl->l.p));
for (int i = 0; i < cl->nupvalues; ++i)
validateref(g, obj2gco(cl), &cl->l.uprefs[i]);
}
}
static void validatestack(global_State* g, lua_State* l)
{
validateref(g, obj2gco(l), gt(l));
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
{
LUAU_ASSERT(l->stack <= ci->base);
LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top);
LUAU_ASSERT(ci->top <= l->stack_last);
}
// note: stack refs can violate gc invariant so we only check for liveness
for (StkId o = l->stack; o < l->top; ++o)
checkliveness(g, o);
if (l->namecall)
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
for (GCObject* uv = l->openupval; uv; uv = uv->gch.next)
{
LUAU_ASSERT(uv->gch.tt == LUA_TUPVAL);
LUAU_ASSERT(gco2uv(uv)->v != &gco2uv(uv)->u.value);
}
}
static void validateproto(global_State* g, Proto* f)
{
if (f->source)
validateobjref(g, obj2gco(f), obj2gco(f->source));
if (f->debugname)
validateobjref(g, obj2gco(f), obj2gco(f->debugname));
for (int i = 0; i < f->sizek; ++i)
validateref(g, obj2gco(f), &f->k[i]);
for (int i = 0; i < f->sizeupvalues; ++i)
if (f->upvalues[i])
validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i]));
for (int i = 0; i < f->sizep; ++i)
if (f->p[i])
validateobjref(g, obj2gco(f), obj2gco(f->p[i]));
for (int i = 0; i < f->sizelocvars; i++)
if (f->locvars[i].varname)
validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname));
}
static void validateobj(global_State* g, GCObject* o)
{
/* dead objects can only occur during sweep */
if (isdead(g, o))
{
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
return;
}
switch (o->gch.tt)
{
case LUA_TSTRING:
break;
case LUA_TTABLE:
validatetable(g, gco2h(o));
break;
case LUA_TFUNCTION:
validateclosure(g, gco2cl(o));
break;
case LUA_TUSERDATA:
if (gco2u(o)->metatable)
validateobjref(g, o, obj2gco(gco2u(o)->metatable));
break;
case LUA_TTHREAD:
validatestack(g, gco2th(o));
break;
case LUA_TPROTO:
validateproto(g, gco2p(o));
break;
case LUA_TUPVAL:
validateref(g, o, gco2uv(o)->v);
break;
default:
LUAU_ASSERT(!"unexpected object type");
}
}
static void validatelist(global_State* g, GCObject* o)
{
while (o)
{
validateobj(g, o);
o = o->gch.next;
}
}
static void validategraylist(global_State* g, GCObject* o)
{
if (!keepinvariant(g))
return;
while (o)
{
LUAU_ASSERT(isgray(o));
switch (o->gch.tt)
{
case LUA_TTABLE:
o = gco2h(o)->gclist;
break;
case LUA_TFUNCTION:
o = gco2cl(o)->gclist;
break;
case LUA_TTHREAD:
o = gco2th(o)->gclist;
break;
case LUA_TPROTO:
o = gco2p(o)->gclist;
break;
default:
LUAU_ASSERT(!"unknown object in gray list");
return;
}
}
}
void luaC_validate(lua_State* L)
{
global_State* g = L->global;
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
checkliveness(g, &g->registry);
for (int i = 0; i < LUA_T_COUNT; ++i)
if (g->mt[i])
LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i])));
validategraylist(g, g->weak);
validategraylist(g, g->gray);
validategraylist(g, g->grayagain);
for (int i = 0; i < g->strt.size; ++i)
validatelist(g, g->strt.hash[i]);
validatelist(g, g->rootgc);
validatelist(g, g->strbufgc);
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
{
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
LUAU_ASSERT(uv->v != &uv->u.value);
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
}
}
inline bool safejson(char ch)
{
return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"';
}
static void dumpref(FILE* f, GCObject* o)
{
fprintf(f, "\"%p\"", o);
}
static void dumprefs(FILE* f, TValue* data, size_t size)
{
bool first = true;
for (size_t i = 0; i < size; ++i)
{
if (iscollectable(&data[i]))
{
if (!first)
fputc(',', f);
first = false;
dumpref(f, gcvalue(&data[i]));
}
}
}
static void dumpstringdata(FILE* f, const char* data, size_t len)
{
for (size_t i = 0; i < len; ++i)
fputc(safejson(data[i]) ? data[i] : '?', f);
}
static void dumpstring(FILE* f, TString* ts)
{
fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len)));
dumpstringdata(f, ts->data, ts->len);
fprintf(f, "\"}");
}
static void dumptable(FILE* f, Table* h)
{
size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size));
if (h->node != &luaH_dummynode)
{
fprintf(f, ",\"pairs\":[");
bool first = true;
for (int i = 0; i < sizenode(h); ++i)
{
const LuaNode& n = h->node[i];
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
{
if (!first)
fputc(',', f);
first = false;
if (iscollectable(&n.key))
dumpref(f, gcvalue(&n.key));
else
fprintf(f, "null");
fputc(',', f);
if (iscollectable(&n.val))
dumpref(f, gcvalue(&n.val));
else
fprintf(f, "null");
}
}
fprintf(f, "]");
}
if (h->sizearray)
{
fprintf(f, ",\"array\":[");
dumprefs(f, h->array, h->sizearray);
fprintf(f, "]");
}
if (h->metatable)
{
fprintf(f, ",\"metatable\":");
dumpref(f, obj2gco(h->metatable));
}
fprintf(f, "}");
}
static void dumpclosure(FILE* f, Closure* cl)
{
fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat,
cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues)));
fprintf(f, ",\"env\":");
dumpref(f, obj2gco(cl->env));
if (cl->isC)
{
if (cl->nupvalues)
{
fprintf(f, ",\"upvalues\":[");
dumprefs(f, cl->c.upvals, cl->nupvalues);
fprintf(f, "]");
}
}
else
{
fprintf(f, ",\"proto\":");
dumpref(f, obj2gco(cl->l.p));
if (cl->nupvalues)
{
fprintf(f, ",\"upvalues\":[");
dumprefs(f, cl->l.uprefs, cl->nupvalues);
fprintf(f, "]");
}
}
fprintf(f, "}");
}
static void dumpudata(FILE* f, Udata* u)
{
fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag);
if (u->metatable)
{
fprintf(f, ",\"metatable\":");
dumpref(f, obj2gco(u->metatable));
}
fprintf(f, "}");
}
static void dumpthread(FILE* f, lua_State* th)
{
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
if (iscollectable(&th->l_gt))
{
fprintf(f, ",\"env\":");
dumpref(f, gcvalue(&th->l_gt));
}
Closure* tcl = 0;
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
{
if (ttisfunction(ci->func))
{
tcl = clvalue(ci->func);
break;
}
}
if (tcl && !tcl->isC && tcl->l.p->source)
{
Proto* p = tcl->l.p;
fprintf(f, ",\"source\":\"");
dumpstringdata(f, p->source->data, p->source->len);
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
}
if (th->top > th->stack)
{
fprintf(f, ",\"stack\":[");
dumprefs(f, th->stack, th->top - th->stack);
fprintf(f, "]");
}
fprintf(f, "}");
}
static void dumpproto(FILE* f, Proto* p)
{
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size));
if (p->source)
{
fprintf(f, ",\"source\":\"");
dumpstringdata(f, p->source->data, p->source->len);
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
}
if (p->sizek)
{
fprintf(f, ",\"constants\":[");
dumprefs(f, p->k, p->sizek);
fprintf(f, "]");
}
if (p->sizep)
{
fprintf(f, ",\"protos\":[");
for (int i = 0; i < p->sizep; ++i)
{
if (i != 0)
fputc(',', f);
dumpref(f, obj2gco(p->p[i]));
}
fprintf(f, "]");
}
fprintf(f, "}");
}
static void dumpupval(FILE* f, UpVal* uv)
{
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal)));
if (iscollectable(uv->v))
{
fprintf(f, ",\"object\":");
dumpref(f, gcvalue(uv->v));
}
fprintf(f, "}");
}
static void dumpobj(FILE* f, GCObject* o)
{
switch (o->gch.tt)
{
case LUA_TSTRING:
return dumpstring(f, gco2ts(o));
case LUA_TTABLE:
return dumptable(f, gco2h(o));
case LUA_TFUNCTION:
return dumpclosure(f, gco2cl(o));
case LUA_TUSERDATA:
return dumpudata(f, gco2u(o));
case LUA_TTHREAD:
return dumpthread(f, gco2th(o));
case LUA_TPROTO:
return dumpproto(f, gco2p(o));
case LUA_TUPVAL:
return dumpupval(f, gco2uv(o));
default:
LUAU_ASSERT(0);
}
}
static void dumplist(FILE* f, GCObject* o)
{
while (o)
{
dumpref(f, o);
fputc(':', f);
dumpobj(f, o);
fputc(',', f);
fputc('\n', f);
// thread has additional list containing collectable objects that are not present in rootgc
if (o->gch.tt == LUA_TTHREAD)
dumplist(f, gco2th(o)->openupval);
o = o->gch.next;
}
}
void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat))
{
global_State* g = L->global;
FILE* f = static_cast<FILE*>(file);
fprintf(f, "{\"objects\":{\n");
dumplist(f, g->rootgc);
dumplist(f, g->strbufgc);
for (int i = 0; i < g->strt.size; ++i)
dumplist(f, g->strt.hash[i]);
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
fprintf(f, "},\"roots\":{\n");
fprintf(f, "\"mainthread\":");
dumpref(f, obj2gco(g->mainthread));
fprintf(f, ",\"registry\":");
dumpref(f, gcvalue(&g->registry));
fprintf(f, "},\"stats\":{\n");
fprintf(f, "\"size\":%d,\n", int(g->totalbytes));
fprintf(f, "\"categories\":{\n");
for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++)
{
if (size_t bytes = g->memcatbytes[i])
{
if (categoryName)
fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes));
else
fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes));
}
}
fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing ,
fprintf(f, "}\n");
fprintf(f, "}}\n");
}
// measure the allocation rate in bytes/sec
// returns -1 if allocation rate cannot be measured
int64_t luaC_allocationrate(lua_State* L)

558
VM/src/lgcdebug.cpp Normal file
View File

@ -0,0 +1,558 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
#include "lgc.h"
#include "lobject.h"
#include "lstate.h"
#include "ltable.h"
#include "lfunc.h"
#include "lstring.h"
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAG(LuauArrayBoundary)
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{
LUAU_ASSERT(!isdead(g, t));
if (keepinvariant(g))
{
/* basic incremental invariant: black can't point to white */
LUAU_ASSERT(!(isblack(f) && iswhite(t)));
}
}
static void validateref(global_State* g, GCObject* f, TValue* v)
{
if (iscollectable(v))
{
LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt);
validateobjref(g, f, gcvalue(v));
}
}
static void validatetable(global_State* g, Table* h)
{
int sizenode = 1 << h->lsizenode;
if (FFlag::LuauArrayBoundary)
LUAU_ASSERT(h->lastfree <= sizenode);
else
LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode);
if (h->metatable)
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
for (int i = 0; i < h->sizearray; ++i)
validateref(g, obj2gco(h), &h->array[i]);
for (int i = 0; i < sizenode; ++i)
{
LuaNode* n = &h->node[i];
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode);
if (!ttisnil(gval(n)))
{
TValue k = {};
k.tt = gkey(n)->tt;
k.value = gkey(n)->value;
validateref(g, obj2gco(h), &k);
validateref(g, obj2gco(h), gval(n));
}
}
}
static void validateclosure(global_State* g, Closure* cl)
{
validateobjref(g, obj2gco(cl), obj2gco(cl->env));
if (cl->isC)
{
for (int i = 0; i < cl->nupvalues; ++i)
validateref(g, obj2gco(cl), &cl->c.upvals[i]);
}
else
{
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
validateobjref(g, obj2gco(cl), obj2gco(cl->l.p));
for (int i = 0; i < cl->nupvalues; ++i)
validateref(g, obj2gco(cl), &cl->l.uprefs[i]);
}
}
static void validatestack(global_State* g, lua_State* l)
{
validateref(g, obj2gco(l), gt(l));
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
{
LUAU_ASSERT(l->stack <= ci->base);
LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top);
LUAU_ASSERT(ci->top <= l->stack_last);
}
// note: stack refs can violate gc invariant so we only check for liveness
for (StkId o = l->stack; o < l->top; ++o)
checkliveness(g, o);
if (l->namecall)
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
for (GCObject* uv = l->openupval; uv; uv = uv->gch.next)
{
LUAU_ASSERT(uv->gch.tt == LUA_TUPVAL);
LUAU_ASSERT(gco2uv(uv)->v != &gco2uv(uv)->u.value);
}
}
static void validateproto(global_State* g, Proto* f)
{
if (f->source)
validateobjref(g, obj2gco(f), obj2gco(f->source));
if (f->debugname)
validateobjref(g, obj2gco(f), obj2gco(f->debugname));
for (int i = 0; i < f->sizek; ++i)
validateref(g, obj2gco(f), &f->k[i]);
for (int i = 0; i < f->sizeupvalues; ++i)
if (f->upvalues[i])
validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i]));
for (int i = 0; i < f->sizep; ++i)
if (f->p[i])
validateobjref(g, obj2gco(f), obj2gco(f->p[i]));
for (int i = 0; i < f->sizelocvars; i++)
if (f->locvars[i].varname)
validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname));
}
static void validateobj(global_State* g, GCObject* o)
{
/* dead objects can only occur during sweep */
if (isdead(g, o))
{
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
return;
}
switch (o->gch.tt)
{
case LUA_TSTRING:
break;
case LUA_TTABLE:
validatetable(g, gco2h(o));
break;
case LUA_TFUNCTION:
validateclosure(g, gco2cl(o));
break;
case LUA_TUSERDATA:
if (gco2u(o)->metatable)
validateobjref(g, o, obj2gco(gco2u(o)->metatable));
break;
case LUA_TTHREAD:
validatestack(g, gco2th(o));
break;
case LUA_TPROTO:
validateproto(g, gco2p(o));
break;
case LUA_TUPVAL:
validateref(g, o, gco2uv(o)->v);
break;
default:
LUAU_ASSERT(!"unexpected object type");
}
}
static void validatelist(global_State* g, GCObject* o)
{
while (o)
{
validateobj(g, o);
o = o->gch.next;
}
}
static void validategraylist(global_State* g, GCObject* o)
{
if (!keepinvariant(g))
return;
while (o)
{
LUAU_ASSERT(isgray(o));
switch (o->gch.tt)
{
case LUA_TTABLE:
o = gco2h(o)->gclist;
break;
case LUA_TFUNCTION:
o = gco2cl(o)->gclist;
break;
case LUA_TTHREAD:
o = gco2th(o)->gclist;
break;
case LUA_TPROTO:
o = gco2p(o)->gclist;
break;
default:
LUAU_ASSERT(!"unknown object in gray list");
return;
}
}
}
void luaC_validate(lua_State* L)
{
global_State* g = L->global;
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
checkliveness(g, &g->registry);
for (int i = 0; i < LUA_T_COUNT; ++i)
if (g->mt[i])
LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i])));
validategraylist(g, g->weak);
validategraylist(g, g->gray);
validategraylist(g, g->grayagain);
for (int i = 0; i < g->strt.size; ++i)
validatelist(g, g->strt.hash[i]);
validatelist(g, g->rootgc);
validatelist(g, g->strbufgc);
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
{
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
LUAU_ASSERT(uv->v != &uv->u.value);
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
}
}
inline bool safejson(char ch)
{
return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"';
}
static void dumpref(FILE* f, GCObject* o)
{
fprintf(f, "\"%p\"", o);
}
static void dumprefs(FILE* f, TValue* data, size_t size)
{
bool first = true;
for (size_t i = 0; i < size; ++i)
{
if (iscollectable(&data[i]))
{
if (!first)
fputc(',', f);
first = false;
dumpref(f, gcvalue(&data[i]));
}
}
}
static void dumpstringdata(FILE* f, const char* data, size_t len)
{
for (size_t i = 0; i < len; ++i)
fputc(safejson(data[i]) ? data[i] : '?', f);
}
static void dumpstring(FILE* f, TString* ts)
{
fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len)));
dumpstringdata(f, ts->data, ts->len);
fprintf(f, "\"}");
}
static void dumptable(FILE* f, Table* h)
{
size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size));
if (h->node != &luaH_dummynode)
{
fprintf(f, ",\"pairs\":[");
bool first = true;
for (int i = 0; i < sizenode(h); ++i)
{
const LuaNode& n = h->node[i];
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
{
if (!first)
fputc(',', f);
first = false;
if (iscollectable(&n.key))
dumpref(f, gcvalue(&n.key));
else
fprintf(f, "null");
fputc(',', f);
if (iscollectable(&n.val))
dumpref(f, gcvalue(&n.val));
else
fprintf(f, "null");
}
}
fprintf(f, "]");
}
if (h->sizearray)
{
fprintf(f, ",\"array\":[");
dumprefs(f, h->array, h->sizearray);
fprintf(f, "]");
}
if (h->metatable)
{
fprintf(f, ",\"metatable\":");
dumpref(f, obj2gco(h->metatable));
}
fprintf(f, "}");
}
static void dumpclosure(FILE* f, Closure* cl)
{
fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat,
cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues)));
fprintf(f, ",\"env\":");
dumpref(f, obj2gco(cl->env));
if (cl->isC)
{
if (cl->nupvalues)
{
fprintf(f, ",\"upvalues\":[");
dumprefs(f, cl->c.upvals, cl->nupvalues);
fprintf(f, "]");
}
}
else
{
fprintf(f, ",\"proto\":");
dumpref(f, obj2gco(cl->l.p));
if (cl->nupvalues)
{
fprintf(f, ",\"upvalues\":[");
dumprefs(f, cl->l.uprefs, cl->nupvalues);
fprintf(f, "]");
}
}
fprintf(f, "}");
}
static void dumpudata(FILE* f, Udata* u)
{
fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag);
if (u->metatable)
{
fprintf(f, ",\"metatable\":");
dumpref(f, obj2gco(u->metatable));
}
fprintf(f, "}");
}
static void dumpthread(FILE* f, lua_State* th)
{
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
if (iscollectable(&th->l_gt))
{
fprintf(f, ",\"env\":");
dumpref(f, gcvalue(&th->l_gt));
}
Closure* tcl = 0;
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
{
if (ttisfunction(ci->func))
{
tcl = clvalue(ci->func);
break;
}
}
if (tcl && !tcl->isC && tcl->l.p->source)
{
Proto* p = tcl->l.p;
fprintf(f, ",\"source\":\"");
dumpstringdata(f, p->source->data, p->source->len);
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
}
if (th->top > th->stack)
{
fprintf(f, ",\"stack\":[");
dumprefs(f, th->stack, th->top - th->stack);
fprintf(f, "]");
}
fprintf(f, "}");
}
static void dumpproto(FILE* f, Proto* p)
{
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size));
if (p->source)
{
fprintf(f, ",\"source\":\"");
dumpstringdata(f, p->source->data, p->source->len);
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
}
if (p->sizek)
{
fprintf(f, ",\"constants\":[");
dumprefs(f, p->k, p->sizek);
fprintf(f, "]");
}
if (p->sizep)
{
fprintf(f, ",\"protos\":[");
for (int i = 0; i < p->sizep; ++i)
{
if (i != 0)
fputc(',', f);
dumpref(f, obj2gco(p->p[i]));
}
fprintf(f, "]");
}
fprintf(f, "}");
}
static void dumpupval(FILE* f, UpVal* uv)
{
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal)));
if (iscollectable(uv->v))
{
fprintf(f, ",\"object\":");
dumpref(f, gcvalue(uv->v));
}
fprintf(f, "}");
}
static void dumpobj(FILE* f, GCObject* o)
{
switch (o->gch.tt)
{
case LUA_TSTRING:
return dumpstring(f, gco2ts(o));
case LUA_TTABLE:
return dumptable(f, gco2h(o));
case LUA_TFUNCTION:
return dumpclosure(f, gco2cl(o));
case LUA_TUSERDATA:
return dumpudata(f, gco2u(o));
case LUA_TTHREAD:
return dumpthread(f, gco2th(o));
case LUA_TPROTO:
return dumpproto(f, gco2p(o));
case LUA_TUPVAL:
return dumpupval(f, gco2uv(o));
default:
LUAU_ASSERT(0);
}
}
static void dumplist(FILE* f, GCObject* o)
{
while (o)
{
dumpref(f, o);
fputc(':', f);
dumpobj(f, o);
fputc(',', f);
fputc('\n', f);
// thread has additional list containing collectable objects that are not present in rootgc
if (o->gch.tt == LUA_TTHREAD)
dumplist(f, gco2th(o)->openupval);
o = o->gch.next;
}
}
void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat))
{
global_State* g = L->global;
FILE* f = static_cast<FILE*>(file);
fprintf(f, "{\"objects\":{\n");
dumplist(f, g->rootgc);
dumplist(f, g->strbufgc);
for (int i = 0; i < g->strt.size; ++i)
dumplist(f, g->strt.hash[i]);
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
fprintf(f, "},\"roots\":{\n");
fprintf(f, "\"mainthread\":");
dumpref(f, obj2gco(g->mainthread));
fprintf(f, ",\"registry\":");
dumpref(f, gcvalue(&g->registry));
fprintf(f, "},\"stats\":{\n");
fprintf(f, "\"size\":%d,\n", int(g->totalbytes));
fprintf(f, "\"categories\":{\n");
for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++)
{
if (size_t bytes = g->memcatbytes[i])
{
if (categoryName)
fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes));
else
fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes));
}
}
fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing ,
fprintf(f, "}\n");
fprintf(f, "}}\n");
}

View File

@ -23,9 +23,11 @@ const bool kFuzzCompiler = true;
const bool kFuzzLinter = true;
const bool kFuzzTypeck = true;
const bool kFuzzVM = true;
const bool kFuzzTypes = true;
const bool kFuzzTranspile = true;
// Should we generate type annotations?
const bool kFuzzTypes = true;
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
std::string protoprint(const luau::StatBlock& stat, bool types);

View File

@ -78,3 +78,26 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_fn")
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("AstQuery");
TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type")
{
ScopedFastFlag luauTailArgumentTypeInfo{"LuauTailArgumentTypeInfo", true};
check(R"(
local function foo() return 2 end
local function bar(a: number) return -a end
bar(foo())
)");
auto oty = findTypeAtPosition(Position(3, 7));
REQUIRE(oty);
CHECK_EQ("number", toString(*oty));
auto expectedOty = findExpectedTypeAtPosition(Position(3, 7));
REQUIRE(expectedOty);
CHECK_EQ("number", toString(*expectedOty));
}
TEST_SUITE_END();

View File

@ -1935,6 +1935,39 @@ return target(b@1
CHECK(ac.entryMap["bar2"].typeCorrect == TypeCorrectKind::None);
}
TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true);
check(R"(
local function bar(a: number) return -a end
local abc = b@1
)");
auto ac = autocomplete('1');
CHECK(ac.entryMap.count("bar"));
CHECK(ac.entryMap["bar"].parens == ParenthesesRecommendation::CursorInside);
}
TEST_CASE_FIXTURE(ACFixture, "function_result_passed_to_function_has_parentheses")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true);
check(R"(
local function foo() return 1 end
local function bar(a: number) return -a end
local abc = bar(@1)
)");
auto ac = autocomplete('1');
CHECK(ac.entryMap.count("foo"));
CHECK(ac.entryMap["foo"].parens == ParenthesesRecommendation::CursorAfter);
}
TEST_CASE_FIXTURE(ACFixture, "type_correct_sealed_table")
{
check(R"(
@ -2210,8 +2243,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource")
TEST_CASE_FIXTURE(ACFixture, "autocompleteSource_require")
{
ScopedFastFlag luauResolveModuleNameWithoutACurrentModule("LuauResolveModuleNameWithoutACurrentModule", true);
std::string_view source = R"(
local a = require(w -- Line 1
-- | Column 27
@ -2287,8 +2318,6 @@ until
TEST_CASE_FIXTURE(ACFixture, "if_then_else_elseif_completions")
{
ScopedFastFlag sff{"ElseElseIfCompletionImprovements", true};
check(R"(
local elsewhere = false
@ -2585,9 +2614,6 @@ a = if temp then even elseif true then temp else e@9
TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
check(R"(
type A<T...> = () -> T...
local a: A<(number, s@1>

View File

@ -1057,6 +1057,18 @@ RETURN R0 1
CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"(
LOADN R0 20
RETURN R0 1
)");
// codegen for a true constant condition with non-constant expressions
CHECK_EQ("\n" + compileFunction0("return if true then {} else error()"), R"(
NEWTABLE R0 0 0
RETURN R0 1
)");
// codegen for a false constant condition with non-constant expressions
CHECK_EQ("\n" + compileFunction0("return if false then error() else {}"), R"(
NEWTABLE R0 0 0
RETURN R0 1
)");
// codegen for a false (in this case 'nil') constant condition
@ -2360,6 +2372,58 @@ Foo:Bar(
)");
}
TEST_CASE("DebugLineInfoCallChain")
{
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
Luau::compileOrThrow(bcb, R"(
local Foo = ...
Foo
:Bar(1)
:Baz(2)
.Qux(3)
)");
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
2: GETVARARGS R0 1
5: LOADN R4 1
5: NAMECALL R2 R0 K0
5: CALL R2 2 1
6: LOADN R4 2
6: NAMECALL R2 R2 K1
6: CALL R2 2 1
7: GETTABLEKS R1 R2 K2
7: LOADN R2 3
7: CALL R1 1 0
8: RETURN R0 0
)");
}
TEST_CASE("DebugLineInfoFastCall")
{
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
Luau::compileOrThrow(bcb, R"(
local Foo, Bar = ...
return
math.max(
Foo,
Bar)
)");
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
2: GETVARARGS R0 2
5: FASTCALL2 18 R0 R1 +5
5: MOVE R3 R0
5: MOVE R4 R1
5: GETIMPORT R2 2
5: CALL R2 2 -1
5: RETURN R2 -1
)");
}
TEST_CASE("DebugSource")
{
const char* source = R"(
@ -3742,4 +3806,108 @@ RETURN R0 0
)");
}
TEST_CASE("ConstantsNoFolding")
{
const char* source = "return nil, true, 42, 'hello'";
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
options.optimizationLevel = 0;
Luau::compileOrThrow(bcb, source, options);
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
LOADNIL R0
LOADB R1 1
LOADK R2 K0
LOADK R3 K1
RETURN R0 4
)");
}
TEST_CASE("VectorFastCall")
{
const char* source = "return Vector3.new(1, 2, 3)";
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
options.vectorLib = "Vector3";
options.vectorCtor = "new";
Luau::compileOrThrow(bcb, source, options);
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
LOADN R1 1
LOADN R2 2
LOADN R3 3
FASTCALL 54 +2
GETIMPORT R0 2
CALL R0 3 -1
RETURN R0 -1
)");
}
TEST_CASE("TypeAssertion")
{
// validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated
CHECK_EQ("\n" + compileFunction0(R"(
print(foo() :: typeof(error("compile time")))
)"),
R"(
GETIMPORT R0 1
GETIMPORT R1 3
CALL R1 0 1
CALL R0 1 0
RETURN R0 0
)");
// note that above, foo() is treated as single-arg function; removing type assertion changes the bytecode
CHECK_EQ("\n" + compileFunction0(R"(
print(foo())
)"),
R"(
GETIMPORT R0 1
GETIMPORT R1 3
CALL R1 0 -1
CALL R0 -1 0
RETURN R0 0
)");
}
TEST_CASE("Arithmetics")
{
// basic arithmetics codegen with non-constants
CHECK_EQ("\n" + compileFunction0(R"(
local a, b = ...
return a + b, a - b, a / b, a * b, a % b, a ^ b
)"),
R"(
GETVARARGS R0 2
ADD R2 R0 R1
SUB R3 R0 R1
DIV R4 R0 R1
MUL R5 R0 R1
MOD R6 R0 R1
POW R7 R0 R1
RETURN R2 6
)");
// basic arithmetics codegen with constants on the right side
// note that we don't simplify these expressions as we don't know the type of a
CHECK_EQ("\n" + compileFunction0(R"(
local a = ...
return a + 1, a - 1, a / 1, a * 1, a % 1, a ^ 1
)"),
R"(
GETVARARGS R0 1
ADDK R1 R0 K0
SUBK R2 R0 K0
DIVK R3 R0 K0
MULK R4 R0 K0
MODK R5 R0 K0
POWK R6 R0 K0
RETURN R1 6
)");
}
TEST_SUITE_END();

View File

@ -508,6 +508,9 @@ TEST_CASE("Debugger")
cb->debugbreak = [](lua_State* L, lua_Debug* ar) {
breakhits++;
// make sure we can trace the stack for every breakpoint we hit
lua_debugtrace(L);
// for every breakpoint, we break on the first invocation and continue on second
// this allows us to easily step off breakpoints
// (real implementaiton may require singlestepping)
@ -703,21 +706,52 @@ TEST_CASE("ApiFunctionCalls")
StateRef globalState = runConformance("apicalls.lua");
lua_State* L = globalState.get();
lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pushnumber(L, 40);
lua_pushnumber(L, 2);
lua_call(L, 2, 1);
CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
// lua_call
{
lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pushnumber(L, 40);
lua_pushnumber(L, 2);
lua_call(L, 2, 1);
CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
}
lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pushnumber(L, 40);
lua_pushnumber(L, 2);
lua_pcall(L, 2, 1, 0);
CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
// lua_pcall
{
lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pushnumber(L, 40);
lua_pushnumber(L, 2);
lua_pcall(L, 2, 1, 0);
CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
}
// lua_equal with a sleeping thread wake up
{
ScopedFastFlag luauActivateBeforeExec("LuauActivateBeforeExec", true);
lua_State* L2 = lua_newthread(L);
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
lua_pushnumber(L2, 42);
lua_pcall(L2, 1, 1, 0);
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
lua_pushnumber(L2, 42);
lua_pcall(L2, 1, 1, 0);
// Reset GC
lua_gc(L2, LUA_GCCOLLECT, 0);
// Try to mark 'L2' as sleeping
// Can't control GC precisely, even in tests
lua_gc(L2, LUA_GCSTEP, 8);
CHECK(lua_equal(L2, -1, -2) == 1);
lua_pop(L2, 2);
}
}
static bool endsWith(const std::string& str, const std::string& suffix)
@ -731,8 +765,6 @@ static bool endsWith(const std::string& str, const std::string& suffix)
#if !LUA_USE_LONGJMP
TEST_CASE("ExceptionObject")
{
ScopedFastFlag sff("LuauExceptionMessageFix", true);
struct ExceptionResult
{
bool exceptionGenerated;

View File

@ -19,19 +19,6 @@ static const char* mainModuleName = "MainModule";
namespace Luau
{
std::optional<ModuleName> TestFileResolver::fromAstFragment(AstExpr* expr) const
{
auto g = expr->as<AstExprGlobal>();
if (!g)
return std::nullopt;
std::string_view value = g->name.value;
if (value == "game" || value == "Game" || value == "workspace" || value == "Workspace" || value == "script" || value == "Script")
return ModuleName(value);
return std::nullopt;
}
std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* context, AstExpr* expr)
{
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
@ -81,24 +68,6 @@ std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* cont
return std::nullopt;
}
ModuleName TestFileResolver::concat(const ModuleName& lhs, std::string_view rhs) const
{
return lhs + "/" + ModuleName(rhs);
}
std::optional<ModuleName> TestFileResolver::getParentModuleName(const ModuleName& name) const
{
std::string_view view = name;
const size_t lastSeparatorIndex = view.find_last_of('/');
if (lastSeparatorIndex != std::string_view::npos)
{
return ModuleName(view.substr(0, lastSeparatorIndex));
}
return std::nullopt;
}
std::string TestFileResolver::getHumanReadableModuleName(const ModuleName& name) const
{
return name;
@ -324,6 +293,13 @@ std::optional<TypeId> Fixture::findTypeAtPosition(Position position)
return Luau::findTypeAtPosition(*module, *sourceModule, position);
}
std::optional<TypeId> Fixture::findExpectedTypeAtPosition(Position position)
{
ModulePtr module = getMainModule();
SourceModule* sourceModule = getMainSourceModule();
return Luau::findExpectedTypeAtPosition(*module, *sourceModule, position);
}
TypeId Fixture::requireTypeAtPosition(Position position)
{
auto ty = findTypeAtPosition(position);

View File

@ -64,12 +64,8 @@ struct TestFileResolver
return SourceCode{it->second, sourceType};
}
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override;
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override;
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override;
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override;
std::string getHumanReadableModuleName(const ModuleName& name) const override;
std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const override;
@ -126,6 +122,7 @@ struct Fixture
std::optional<TypeId> findTypeAtPosition(Position position);
TypeId requireTypeAtPosition(Position position);
std::optional<TypeId> findExpectedTypeAtPosition(Position position);
std::optional<TypeId> lookupType(const std::string& name);
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);

View File

@ -46,18 +46,6 @@ NaiveModuleResolver naiveModuleResolver;
struct NaiveFileResolver : NullFileResolver
{
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
{
AstExprGlobal* g = expr->as<AstExprGlobal>();
if (g && g->name == "Modules")
return "Modules";
if (g && g->name == "game")
return "game";
return std::nullopt;
}
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override
{
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
@ -86,11 +74,6 @@ struct NaiveFileResolver : NullFileResolver
return std::nullopt;
}
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
{
return lhs + "/" + ModuleName(rhs);
}
};
} // namespace

View File

@ -1469,6 +1469,22 @@ _ = true and true or false -- no warning since this is is a common pattern used
CHECK_EQ(result.warnings[6].location.begin.line + 1, 19);
}
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsExpr")
{
LintResult result = lint(R"(
local correct, opaque = ...
if correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", false)}) then
end
)");
REQUIRE_EQ(result.warnings.size(), 1);
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 4");
CHECK_EQ(result.warnings[0].location.begin.line + 1, 5);
}
TEST_CASE_FIXTURE(Fixture, "DuplicateLocal")
{
LintResult result = lint(R"(

View File

@ -44,9 +44,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive")
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
// numberType is persistent. We leave it as-is.
TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks);
TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks, cloneState);
CHECK_EQ(newNumber, typeChecker.numberType);
}
@ -56,12 +57,13 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive")
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
// Create a new number type that isn't persistent
unfreeze(typeChecker.globalTypes);
TypeId oldNumber = typeChecker.globalTypes.addType(PrimitiveTypeVar{PrimitiveTypeVar::Number});
freeze(typeChecker.globalTypes);
TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks);
TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks, cloneState);
CHECK_NE(newNumber, oldNumber);
CHECK_EQ(*oldNumber, *newNumber);
@ -89,9 +91,10 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
TypeArena dest;
TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks);
TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks, cloneState);
TableTypeVar* ttv = getMutable<TableTypeVar>(counterCopy);
REQUIRE(ttv != nullptr);
@ -142,11 +145,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union")
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
unfreeze(typeChecker.globalTypes);
TypeId oldUnion = typeChecker.globalTypes.addType(UnionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
freeze(typeChecker.globalTypes);
TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks);
TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks, cloneState);
CHECK_NE(newUnion, oldUnion);
CHECK_EQ("number | string", toString(newUnion));
@ -159,11 +163,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection")
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
unfreeze(typeChecker.globalTypes);
TypeId oldIntersection = typeChecker.globalTypes.addType(IntersectionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
freeze(typeChecker.globalTypes);
TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks);
TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks, cloneState);
CHECK_NE(newIntersection, oldIntersection);
CHECK_EQ("number & string", toString(newIntersection));
@ -188,8 +193,9 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks);
TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks, cloneState);
const ClassTypeVar* ctv = get<ClassTypeVar>(cloned);
REQUIRE(ctv != nullptr);
@ -211,16 +217,16 @@ TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types")
TypeArena dest;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
bool encounteredFreeType = false;
TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, cloneState);
CHECK_EQ("any", toString(clonedTy));
CHECK(encounteredFreeType);
CHECK(cloneState.encounteredFreeType);
encounteredFreeType = false;
TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, &encounteredFreeType);
cloneState = {};
TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, cloneState);
CHECK_EQ("...any", toString(clonedTp));
CHECK(encounteredFreeType);
CHECK(cloneState.encounteredFreeType);
}
TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
@ -232,12 +238,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
TypeArena dest;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
bool encounteredFreeType = false;
TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, cloneState);
const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned);
CHECK_EQ(clonedTtv->state, TableState::Sealed);
CHECK(encounteredFreeType);
CHECK(cloneState.encounteredFreeType);
}
TEST_CASE_FIXTURE(Fixture, "clone_self_property")
@ -267,4 +273,34 @@ TEST_CASE_FIXTURE(Fixture, "clone_self_property")
"dot or pass 1 extra nil to suppress this warning");
}
TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
{
#if defined(_DEBUG) || defined(_NOOPT)
int limit = 250;
#else
int limit = 400;
#endif
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
TypeArena src;
TypeId table = src.addType(TableTypeVar{});
TypeId nested = table;
for (int i = 0; i < limit + 100; i++)
{
TableTypeVar* ttv = getMutable<TableTypeVar>(nested);
ttv->props["a"].type = src.addType(TableTypeVar{});
nested = ttv->props["a"].type;
}
TypeArena dest;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
CHECK_THROWS_AS(clone(table, dest, seenTypes, seenTypePacks, cloneState), std::runtime_error);
}
TEST_SUITE_END();

View File

@ -2518,8 +2518,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_if_else_expression")
TEST_CASE_FIXTURE(Fixture, "parse_type_pack_type_parameters")
{
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
AstStat* stat = parse(R"(
type Packed<T...> = () -> T...

366
tests/ToDot.test.cpp Normal file
View File

@ -0,0 +1,366 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Scope.h"
#include "Luau/ToDot.h"
#include "Fixture.h"
#include "doctest.h"
using namespace Luau;
struct ToDotClassFixture : Fixture
{
ToDotClassFixture()
{
TypeArena& arena = typeChecker.globalTypes;
unfreeze(arena);
TypeId baseClassMetaType = arena.addType(TableTypeVar{});
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}});
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
{"BaseField", {typeChecker.numberType}},
};
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}});
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
{"ChildField", {typeChecker.stringType}},
};
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
freeze(arena);
}
};
TEST_SUITE_BEGIN("ToDot");
TEST_CASE_FIXTURE(Fixture, "primitive")
{
CheckResult result = check(R"(
local a: nil
local b: number
local c: any
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_NE("nil", toDot(requireType("a")));
CHECK_EQ(R"(digraph graphname {
n1 [label="number"];
})",
toDot(requireType("b")));
CHECK_EQ(R"(digraph graphname {
n1 [label="any"];
})",
toDot(requireType("c")));
ToDotOptions opts;
opts.showPointers = false;
opts.duplicatePrimitives = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="PrimitiveTypeVar number"];
})",
toDot(requireType("b"), opts));
CHECK_EQ(R"(digraph graphname {
n1 [label="AnyTypeVar 1"];
})",
toDot(requireType("c"), opts));
}
TEST_CASE_FIXTURE(Fixture, "bound")
{
CheckResult result = check(R"(
local a = 444
local b = a
)");
LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> ty = getType("b");
REQUIRE(bool(ty));
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="BoundTypeVar 1"];
n1 -> n2;
n2 [label="number"];
})",
toDot(*ty, opts));
}
TEST_CASE_FIXTURE(Fixture, "function")
{
ScopedFastFlag luauQuantifyInPlace2{"LuauQuantifyInPlace2", true};
CheckResult result = check(R"(
local function f(a, ...: string) return a end
)");
LUAU_REQUIRE_NO_ERRORS(result);
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="FunctionTypeVar 1"];
n1 -> n2 [label="arg"];
n2 [label="TypePack 2"];
n2 -> n3;
n3 [label="GenericTypeVar 3"];
n2 -> n4 [label="tail"];
n4 [label="VariadicTypePack 4"];
n4 -> n5;
n5 [label="string"];
n1 -> n6 [label="ret"];
n6 [label="BoundTypePack 6"];
n6 -> n7;
n7 [label="TypePack 7"];
n7 -> n3;
})",
toDot(requireType("f"), opts));
}
TEST_CASE_FIXTURE(Fixture, "union")
{
CheckResult result = check(R"(
local a: string | number
)");
LUAU_REQUIRE_NO_ERRORS(result);
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="UnionTypeVar 1"];
n1 -> n2;
n2 [label="string"];
n1 -> n3;
n3 [label="number"];
})",
toDot(requireType("a"), opts));
}
TEST_CASE_FIXTURE(Fixture, "intersection")
{
CheckResult result = check(R"(
local a: string & number -- uninhabited
)");
LUAU_REQUIRE_NO_ERRORS(result);
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="IntersectionTypeVar 1"];
n1 -> n2;
n2 [label="string"];
n1 -> n3;
n3 [label="number"];
})",
toDot(requireType("a"), opts));
}
TEST_CASE_FIXTURE(Fixture, "table")
{
CheckResult result = check(R"(
type A<T, U...> = { x: T, y: (U...) -> (), [string]: any }
local a: A<number, ...string>
)");
LUAU_REQUIRE_NO_ERRORS(result);
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="TableTypeVar A"];
n1 -> n2 [label="x"];
n2 [label="number"];
n1 -> n3 [label="y"];
n3 [label="FunctionTypeVar 3"];
n3 -> n4 [label="arg"];
n4 [label="VariadicTypePack 4"];
n4 -> n5;
n5 [label="string"];
n3 -> n6 [label="ret"];
n6 [label="TypePack 6"];
n1 -> n7 [label="[index]"];
n7 [label="string"];
n1 -> n8 [label="[value]"];
n8 [label="any"];
n1 -> n9 [label="typeParam"];
n9 [label="number"];
n1 -> n4 [label="typePackParam"];
})",
toDot(requireType("a"), opts));
// Extra coverage with pointers (unstable values)
(void)toDot(requireType("a"));
}
TEST_CASE_FIXTURE(Fixture, "metatable")
{
CheckResult result = check(R"(
local a: typeof(setmetatable({}, {}))
)");
LUAU_REQUIRE_NO_ERRORS(result);
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="MetatableTypeVar 1"];
n1 -> n2 [label="table"];
n2 [label="TableTypeVar 2"];
n1 -> n3 [label="metatable"];
n3 [label="TableTypeVar 3"];
})",
toDot(requireType("a"), opts));
}
TEST_CASE_FIXTURE(Fixture, "free")
{
TypeVar type{TypeVariant{FreeTypeVar{TypeLevel{0, 0}}}};
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="FreeTypeVar 1"];
})",
toDot(&type, opts));
}
TEST_CASE_FIXTURE(Fixture, "error")
{
TypeVar type{TypeVariant{ErrorTypeVar{}}};
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="ErrorTypeVar 1"];
})",
toDot(&type, opts));
}
TEST_CASE_FIXTURE(Fixture, "generic")
{
TypeVar type{TypeVariant{GenericTypeVar{"T"}}};
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="GenericTypeVar T"];
})",
toDot(&type, opts));
}
TEST_CASE_FIXTURE(ToDotClassFixture, "class")
{
CheckResult result = check(R"(
local a: ChildClass
)");
LUAU_REQUIRE_NO_ERRORS(result);
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="ClassTypeVar ChildClass"];
n1 -> n2 [label="ChildField"];
n2 [label="string"];
n1 -> n3 [label="[parent]"];
n3 [label="ClassTypeVar BaseClass"];
n3 -> n4 [label="BaseField"];
n4 [label="number"];
n3 -> n5 [label="[metatable]"];
n5 [label="TableTypeVar 5"];
})",
toDot(requireType("a"), opts));
}
TEST_CASE_FIXTURE(Fixture, "free_pack")
{
TypePackVar pack{TypePackVariant{FreeTypePack{TypeLevel{0, 0}}}};
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="FreeTypePack 1"];
})",
toDot(&pack, opts));
}
TEST_CASE_FIXTURE(Fixture, "error_pack")
{
TypePackVar pack{TypePackVariant{Unifiable::Error{}}};
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="ErrorTypePack 1"];
})",
toDot(&pack, opts));
// Extra coverage with pointers (unstable values)
(void)toDot(&pack);
}
TEST_CASE_FIXTURE(Fixture, "generic_pack")
{
TypePackVar pack1{TypePackVariant{GenericTypePack{}}};
TypePackVar pack2{TypePackVariant{GenericTypePack{"T"}}};
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="GenericTypePack 1"];
})",
toDot(&pack1, opts));
CHECK_EQ(R"(digraph graphname {
n1 [label="GenericTypePack T"];
})",
toDot(&pack2, opts));
}
TEST_CASE_FIXTURE(Fixture, "bound_pack")
{
TypePackVar pack{TypePackVariant{TypePack{{typeChecker.numberType}, {}}}};
TypePackVar bound{TypePackVariant{BoundTypePack{&pack}}};
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="BoundTypePack 1"];
n1 -> n2;
n2 [label="TypePack 2"];
n2 -> n3;
n3 [label="number"];
})",
toDot(&bound, opts));
}
TEST_CASE_FIXTURE(Fixture, "bound_table")
{
CheckResult result = check(R"(
local a = {x=2}
local b
b.x = 2
b = a
)");
LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> ty = getType("b");
REQUIRE(bool(ty));
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
n1 [label="TableTypeVar 1"];
n1 -> n2 [label="boundTo"];
n2 [label="TableTypeVar a"];
n2 -> n3 [label="x"];
n3 [label="number"];
})",
toDot(*ty, opts));
}
TEST_SUITE_END();

View File

@ -445,9 +445,6 @@ local a: Import.Type
TEST_CASE_FIXTURE(Fixture, "transpile_type_packs")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
std::string code = R"(
type Packed<T...> = (T...)->(T...)
local a: Packed<>

View File

@ -537,8 +537,6 @@ TEST_CASE_FIXTURE(Fixture, "free_variables_from_typeof_in_aliases")
TEST_CASE_FIXTURE(Fixture, "non_recursive_aliases_that_reuse_a_generic_name")
{
ScopedFastFlag sff1{"LuauSubstitutionDontReplaceIgnoredTypes", true};
CheckResult result = check(R"(
type Array<T> = { [number]: T }
type Tuple<T, V> = Array<T | V>

View File

@ -609,8 +609,6 @@ TEST_CASE_FIXTURE(Fixture, "typefuns_sharing_types")
TEST_CASE_FIXTURE(Fixture, "bound_tables_do_not_clone_original_fields")
{
ScopedFastFlag luauCloneBoundTables{"LuauCloneBoundTables", true};
CheckResult result = check(R"(
local exports = {}
local nested = {}
@ -627,4 +625,23 @@ return exports
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names")
{
ScopedFastFlag luauFunctionArgumentNameSize{"LuauFunctionArgumentNameSize", true};
CheckResult result = check(R"(
local function f<T, U...>(a: T, ...: U...) end
f(1, 2, 3)
)");
LUAU_REQUIRE_NO_ERRORS(result);
auto ty = findTypeAtPosition(Position(3, 0));
REQUIRE(ty);
ToStringOptions opts;
opts.functionTypeArguments = true;
CHECK_EQ(toString(*ty, opts), "(a: number, number, number) -> ()");
}
TEST_SUITE_END();

View File

@ -31,8 +31,6 @@ TEST_SUITE_BEGIN("ProvisionalTests");
*/
TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
const std::string code = R"(
function f(a)
if type(a) == "boolean" then

View File

@ -2022,4 +2022,74 @@ caused by:
Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '<a>(a) -> ()')");
}
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table")
{
ScopedFastFlag sffs[] {
{"LuauPropertiesGetExpectedType", true},
{"LuauExpectedTypesOfProperties", true},
{"LuauTableSubtypingVariance", true},
};
CheckResult result = check(R"(
--!strict
type Super = { x : number }
type Sub = { x : number, y: number }
type HasSuper = { p : Super }
type HasSub = { p : Sub }
local a: HasSuper = { p = { x = 5, y = 7 }}
a.p = { x = 9 }
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error")
{
ScopedFastFlag sffs[] {
{"LuauPropertiesGetExpectedType", true},
{"LuauExpectedTypesOfProperties", true},
{"LuauTableSubtypingVariance", true},
{"LuauExtendedTypeMismatchError", true},
};
CheckResult result = check(R"(
--!strict
type Super = { x : number }
type Sub = { x : number, y: number }
type HasSuper = { p : Super }
type HasSub = { p : Sub }
local tmp = { p = { x = 5, y = 7 }}
local a: HasSuper = tmp
a.p = { x = 9 }
-- needs to be an error because
local y: number = tmp.p.y
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'tmp' could not be converted into 'HasSuper'
caused by:
Property 'p' is not compatible. Table type '{| x: number, y: number |}' not compatible with type 'Super' because the former has extra field 'y')");
}
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer")
{
ScopedFastFlag sffs[] {
{"LuauPropertiesGetExpectedType", true},
{"LuauExpectedTypesOfProperties", true},
{"LuauTableSubtypingVariance", true},
};
CheckResult result = check(R"(
--!strict
type Super = { x : number }
type Sub = { x : number, y: number }
type HasSuper = { [string] : Super }
type HasSub = { [string] : Sub }
local a: HasSuper = { p = { x = 5, y = 7 }}
a.p = { x = 9 }
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View File

@ -4779,4 +4779,24 @@ local bar = foo.nutrition + 100
// CHECK_EQ("Unknown type used in + operation; consider adding a type annotation to 'foo'", toString(result.errors[1]));
}
TEST_CASE_FIXTURE(Fixture, "require_failed_module")
{
ScopedFastFlag luauModuleRequireErrorPack{"LuauModuleRequireErrorPack", true};
fileResolver.source["game/A"] = R"(
return unfortunately()
)";
CheckResult aResult = frontend.check("game/A");
LUAU_REQUIRE_ERRORS(aResult);
CheckResult result = check(R"(
local ModuleA = require(game.A)
)");
LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> oty = requireType("ModuleA");
CHECK_EQ("*unknown*", toString(*oty));
}
TEST_SUITE_END();

View File

@ -296,9 +296,6 @@ end
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type Packed<T...> = (T...) -> T...
local a: Packed<>
@ -360,9 +357,6 @@ local c: Packed<string, number, boolean>
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_import")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
fileResolver.source["game/A"] = R"(
export type Packed<T, U...> = { a: T, b: (U...) -> () }
return {}
@ -393,9 +387,6 @@ local d: { a: typeof(c) }
TEST_CASE_FIXTURE(Fixture, "type_pack_type_parameters")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
fileResolver.source["game/A"] = R"(
export type Packed<T, U...> = { a: T, b: (U...) -> () }
return {}
@ -431,9 +422,6 @@ type C<X...> = Import.Packed<string, (number, X...)>
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_nested")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type Packed1<T...> = (T...) -> (T...)
type Packed2<T...> = (Packed1<T...>, T...) -> (Packed1<T...>, T...)
@ -452,9 +440,6 @@ type Packed4<T...> = (Packed3<T...>, T...) -> (Packed3<T...>, T...)
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_variadic")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type X<T...> = (T...) -> (string, T...)
@ -470,9 +455,6 @@ type E = X<(number, ...string)>
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_multi")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type Y<T..., U...> = (T...) -> (U...)
type A<S...> = Y<S..., S...>
@ -501,9 +483,6 @@ type I<S..., R...> = W<number, (string, S...), R...>
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type X<T...> = (T...) -> (T...)
@ -527,9 +506,6 @@ type F = X<(string, ...number)>
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type Y<T..., U...> = (T...) -> (U...)
@ -549,9 +525,6 @@ type D<X...> = Y<X..., (number, string, X...)>
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi_tostring")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type Y<T..., U...> = { f: (T...) -> (U...) }
@ -567,9 +540,6 @@ local b: Y<(), ()>
TEST_CASE_FIXTURE(Fixture, "type_alias_backwards_compatible")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type X<T> = () -> T
type Y<T, U> = (T) -> U
@ -588,9 +558,6 @@ type C = Y<(number), boolean>
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_errors")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
CheckResult result = check(R"(
type Packed<T, U, V...> = (T, U) -> (V...)
local b: Packed<number>

View File

@ -3,6 +3,7 @@
#include "Luau/Scope.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h"
#include "Fixture.h"
#include "ScopedFlags.h"
@ -323,4 +324,48 @@ TEST_CASE("tagging_props")
CHECK(Luau::hasTag(prop, "foo"));
}
struct VisitCountTracker
{
std::unordered_map<TypeId, unsigned> tyVisits;
std::unordered_map<TypePackId, unsigned> tpVisits;
void cycle(TypeId) {}
void cycle(TypePackId) {}
template<typename T>
bool operator()(TypeId ty, const T& t)
{
tyVisits[ty]++;
return true;
}
template<typename T>
bool operator()(TypePackId tp, const T&)
{
tpVisits[tp]++;
return true;
}
};
TEST_CASE_FIXTURE(Fixture, "visit_once")
{
CheckResult result = check(R"(
type T = { a: number, b: () -> () }
local b: (T, T, T) -> T
)");
LUAU_REQUIRE_NO_ERRORS(result);
TypeId bType = requireType("b");
VisitCountTracker tester;
DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(bType, tester, seen);
for (auto [_, count] : tester.tyVisits)
CHECK_EQ(count, 1);
for (auto [_, count] : tester.tpVisits)
CHECK_EQ(count, 1);
}
TEST_SUITE_END();

View File

@ -2,7 +2,13 @@
print('testing function calls through API')
function add(a, b)
return a + b
return a + b
end
local m = { __eq = function(a, b) return a.a == b.a end }
function create_with_tm(x)
return setmetatable({ a = x }, m)
end
return('OK')

View File

@ -441,7 +441,8 @@ assert((function() a = {} b = {} mt = { __eq = function(l, r) return #l == #r en
assert((function() a = {} b = {} function eq(l, r) return #l == #r end setmetatable(a, {__eq = eq}) setmetatable(b, {__eq = eq}) return concat(a == b, a ~= b) end)() == "true,false")
assert((function() a = {} b = {} setmetatable(a, {__eq = function(l, r) return #l == #r end}) setmetatable(b, {__eq = function(l, r) return #l == #r end}) return concat(a == b, a ~= b) end)() == "false,true")
-- userdata, reference equality (no mt)
-- userdata, reference equality (no mt or mt.__eq)
assert((function() a = newproxy() return concat(a == newproxy(),a ~= newproxy()) end)() == "false,true")
assert((function() a = newproxy(true) return concat(a == newproxy(true),a ~= newproxy(true)) end)() == "false,true")
-- rawequal

View File

@ -458,13 +458,16 @@ def display(root, title, colors, flip = False):
framewidth = 1200 - 20
def pixels(x):
return float(x) / root.width * framewidth if root.width > 0 else 0
for n in root.subtree():
if n.width / root.width * framewidth < 0.1:
if pixels(n.width) < 0.1:
continue
x = 10 + n.offset / root.width * framewidth
x = 10 + pixels(n.offset)
y = (maxdepth - 1 - n.depth if flip else n.depth) * 16 + 3 * 16
width = n.width / root.width * framewidth
width = pixels(n.width)
height = 15
if colors == "cold":