Sync to upstream/release/506

This commit is contained in:
Arseny Kapoulkine 2021-12-02 15:20:08 -08:00
parent 60e6e86adb
commit eed18acec8
99 changed files with 2905 additions and 2568 deletions

View File

@ -51,13 +51,6 @@ struct FileResolver
{ {
return std::nullopt; 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 struct NullFileResolver : FileResolver
@ -66,22 +59,6 @@ struct NullFileResolver : FileResolver
{ {
return std::nullopt; 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 } // namespace Luau

View File

@ -78,9 +78,15 @@ void unfreeze(TypeArena& arena);
using SeenTypes = std::unordered_map<TypeId, TypeId>; using SeenTypes = std::unordered_map<TypeId, TypeId>;
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>; using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr); struct CloneState
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); 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 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(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete; TxnLog& operator=(const TxnLog&) = delete;

View File

@ -297,7 +297,6 @@ private:
private: private:
Unifier mkUnifier(const Location& location); 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. // 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(TypeId ty);
void persist(TypePackId tp); 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); const TypeLevel* getLevel(TypeId ty);
TypeLevel* getMutableLevel(TypeId ty); TypeLevel* getMutableLevel(TypeId ty);

View File

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

View File

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

View File

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

View File

@ -12,9 +12,9 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
LUAU_FASTFLAGVARIABLE(ElseElseIfCompletionImprovements, false);
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport) LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = { static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; "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; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, nullptr); CloneState cloneState;
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, nullptr); expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
auto errors = unifier.canUnify(expectedType, actualType); auto errors = unifier.canUnify(expectedType, actualType);
return errors.empty(); return errors.empty();
@ -229,28 +230,51 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
TypeId expectedType = follow(*it); TypeId expectedType = follow(*it);
if (canUnify(expectedType, ty)) if (FFlag::LuauAutocompletePreferToCallFunctions)
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))) // We also want to suggest functions that return compatible result
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None; 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 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) else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; 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}}}, return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
finder.ancestry}; finder.ancestry};

View File

@ -8,8 +8,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauNewRequireTrace2)
/** FIXME: Many of these type definitions are not quite completely accurate. /** 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 * 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])) if (!checkRequirePath(typechecker, expr.args.data[0]))
return std::nullopt; return std::nullopt;
const AstExpr* require = FFlag::LuauNewRequireTrace2 ? &expr : expr.args.data[0]; if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr))
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require))
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})}; return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
return std::nullopt; return std::nullopt;

View File

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

View File

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

View File

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

View File

@ -5,8 +5,6 @@
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace Luau namespace Luau
{ {
@ -615,12 +613,7 @@ struct AstJsonEncoder : public AstVisitor
writeNode(node, "AstStatTypeAlias", [&]() { writeNode(node, "AstStatTypeAlias", [&]() {
PROP(name); PROP(name);
PROP(generics); PROP(generics);
PROP(genericPacks);
if (FFlag::LuauTypeAliasPacks)
{
PROP(genericPacks);
}
PROP(type); PROP(type);
PROP(exported); 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 // 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/Module.h"
#include "Luau/Common.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h" #include "Luau/VisitTypeVar.h"
#include "Luau/Common.h"
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans) LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans)
LUAU_FASTFLAG(LuauTypeAliasPacks) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
LUAU_FASTFLAGVARIABLE(LuauCloneBoundTables, false)
namespace Luau namespace Luau
{ {
@ -120,12 +120,6 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
return allocated; 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 namespace
{ {
@ -138,11 +132,12 @@ struct TypePackCloner;
struct TypeCloner struct TypeCloner
{ {
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks) TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
: dest(dest) : dest(dest)
, typeId(typeId) , typeId(typeId)
, seenTypes(seenTypes) , seenTypes(seenTypes)
, seenTypePacks(seenTypePacks) , seenTypePacks(seenTypePacks)
, cloneState(cloneState)
{ {
} }
@ -150,8 +145,7 @@ struct TypeCloner
TypeId typeId; TypeId typeId;
SeenTypes& seenTypes; SeenTypes& seenTypes;
SeenTypePacks& seenTypePacks; SeenTypePacks& seenTypePacks;
CloneState& cloneState;
bool* encounteredFreeType = nullptr;
template<typename T> template<typename T>
void defaultClone(const T& t); void defaultClone(const T& t);
@ -178,13 +172,14 @@ struct TypePackCloner
TypePackId typePackId; TypePackId typePackId;
SeenTypes& seenTypes; SeenTypes& seenTypes;
SeenTypePacks& seenTypePacks; 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) : dest(dest)
, typePackId(typePackId) , typePackId(typePackId)
, seenTypes(seenTypes) , seenTypes(seenTypes)
, seenTypePacks(seenTypePacks) , seenTypePacks(seenTypePacks)
, cloneState(cloneState)
{ {
} }
@ -197,8 +192,7 @@ struct TypePackCloner
void operator()(const Unifiable::Free& t) void operator()(const Unifiable::Free& t)
{ {
if (encounteredFreeType) cloneState.encounteredFreeType = true;
*encounteredFreeType = true;
TypePackId err = singletonTypes.errorRecoveryTypePack(singletonTypes.anyTypePack); TypePackId err = singletonTypes.errorRecoveryTypePack(singletonTypes.anyTypePack);
TypePackId cloned = dest.addTypePack(*err); 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. // 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) 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; seenTypePacks[typePackId] = cloned;
} }
void operator()(const VariadicTypePack& t) 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; seenTypePacks[typePackId] = cloned;
} }
@ -236,10 +230,10 @@ struct TypePackCloner
seenTypePacks[typePackId] = cloned; seenTypePacks[typePackId] = cloned;
for (TypeId ty : t.head) 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) 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) void TypeCloner::operator()(const Unifiable::Free& t)
{ {
if (encounteredFreeType) cloneState.encounteredFreeType = true;
*encounteredFreeType = true;
TypeId err = singletonTypes.errorRecoveryType(singletonTypes.anyType); TypeId err = singletonTypes.errorRecoveryType(singletonTypes.anyType);
TypeId cloned = dest.addType(*err); TypeId cloned = dest.addType(*err);
seenTypes[typeId] = cloned; seenTypes[typeId] = cloned;
@ -266,7 +259,7 @@ void TypeCloner::operator()(const Unifiable::Generic& t)
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& 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; seenTypes[typeId] = boundTo;
} }
@ -294,23 +287,23 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
seenTypes[typeId] = result; seenTypes[typeId] = result;
for (TypeId generic : t.generics) 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) 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->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->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) void TypeCloner::operator()(const TableTypeVar& t)
{ {
// If table is now bound to another one, we ignore the content of the original // 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; seenTypes[typeId] = boundTo;
return; return;
} }
@ -326,34 +319,21 @@ void TypeCloner::operator()(const TableTypeVar& t)
ttv->level = TypeLevel{0, 0}; ttv->level = TypeLevel{0, 0};
for (const auto& [name, prop] : t.props) 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) if (t.indexer)
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, encounteredFreeType), ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, cloneState),
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, encounteredFreeType)}; clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, cloneState)};
if (!FFlag::LuauCloneBoundTables)
{
if (t.boundTo)
ttv->boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
}
for (TypeId& arg : ttv->instantiatedTypeParams) 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, cloneState);
for (TypePackId& arg : ttv->instantiatedTypePackParams)
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
}
if (ttv->state == TableState::Free) if (ttv->state == TableState::Free)
{ {
if (FFlag::LuauCloneBoundTables || !t.boundTo) cloneState.encounteredFreeType = true;
{
if (encounteredFreeType)
*encounteredFreeType = true;
}
ttv->state = TableState::Sealed; ttv->state = TableState::Sealed;
} }
@ -369,8 +349,8 @@ void TypeCloner::operator()(const MetatableTypeVar& t)
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result); MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result);
seenTypes[typeId] = result; seenTypes[typeId] = result;
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, encounteredFreeType); mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, cloneState);
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType); mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, cloneState);
} }
void TypeCloner::operator()(const ClassTypeVar& t) void TypeCloner::operator()(const ClassTypeVar& t)
@ -381,13 +361,13 @@ void TypeCloner::operator()(const ClassTypeVar& t)
seenTypes[typeId] = result; seenTypes[typeId] = result;
for (const auto& [name, prop] : t.props) 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) if (t.parent)
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, encounteredFreeType); ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, cloneState);
if (t.metatable) 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) void TypeCloner::operator()(const AnyTypeVar& t)
@ -404,7 +384,7 @@ void TypeCloner::operator()(const UnionTypeVar& t)
LUAU_ASSERT(option != nullptr); LUAU_ASSERT(option != nullptr);
for (TypeId ty : t.options) 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) void TypeCloner::operator()(const IntersectionTypeVar& t)
@ -416,7 +396,7 @@ void TypeCloner::operator()(const IntersectionTypeVar& t)
LUAU_ASSERT(option != nullptr); LUAU_ASSERT(option != nullptr);
for (TypeId ty : t.parts) 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) void TypeCloner::operator()(const LazyTypeVar& t)
@ -426,17 +406,18 @@ void TypeCloner::operator()(const LazyTypeVar& t)
} // anonymous namespace } // 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) if (tp->persistent)
return tp; return tp;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypePackId& res = seenTypePacks[tp]; TypePackId& res = seenTypePacks[tp];
if (res == nullptr) if (res == nullptr)
{ {
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks}; TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks, cloneState};
cloner.encounteredFreeType = encounteredFreeType;
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. 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; 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) if (typeId->persistent)
return typeId; return typeId;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypeId& res = seenTypes[typeId]; TypeId& res = seenTypes[typeId];
if (res == nullptr) if (res == nullptr)
{ {
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks}; TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
cloner.encounteredFreeType = encounteredFreeType;
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
asMutable(res)->documentationSymbol = typeId->documentationSymbol; asMutable(res)->documentationSymbol = typeId->documentationSymbol;
} }
@ -467,19 +449,16 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
return res; 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; TypeFun result;
for (TypeId ty : typeFun.typeParams) 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, cloneState));
for (TypePackId tp : typeFun.typePackParams)
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, encounteredFreeType));
}
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, encounteredFreeType); result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, cloneState);
return result; return result;
} }
@ -519,19 +498,18 @@ bool Module::clonePublicInterface()
LUAU_ASSERT(interfaceTypes.typeVars.empty()); LUAU_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty()); LUAU_ASSERT(interfaceTypes.typePacks.empty());
bool encounteredFreeType = false;
SeenTypePacks seenTypePacks;
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
ScopePtr moduleScope = getModuleScope(); ScopePtr moduleScope = getModuleScope();
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType); moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, cloneState);
if (moduleScope->varargPack) 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) 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) for (TypeId ty : moduleScope->returnType)
if (get<GenericTypeVar>(follow(ty))) if (get<GenericTypeVar>(follow(ty)))
@ -540,7 +518,7 @@ bool Module::clonePublicInterface()
freeze(internalTypes); freeze(internalTypes);
freeze(interfaceTypes); freeze(interfaceTypes);
return encounteredFreeType; return cloneState.encounteredFreeType;
} }
} // namespace Luau } // namespace Luau

View File

@ -4,6 +4,8 @@
#include "Luau/VisitTypeVar.h" #include "Luau/VisitTypeVar.h"
LUAU_FASTFLAGVARIABLE(LuauQuantifyVisitOnce, false)
namespace Luau namespace Luau
{ {
@ -79,7 +81,16 @@ struct Quantifier
void quantify(ModulePtr module, TypeId ty, TypeLevel level) void quantify(ModulePtr module, TypeId ty, TypeLevel level)
{ {
Quantifier q{std::move(module), 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); FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);

View File

@ -4,182 +4,9 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
LUAU_FASTFLAGVARIABLE(LuauTraceRequireLookupChild, false)
LUAU_FASTFLAGVARIABLE(LuauNewRequireTrace2, false)
namespace Luau 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 struct RequireTracer : AstVisitor
{ {
RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName) RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName)
@ -188,7 +15,6 @@ struct RequireTracer : AstVisitor
, currentModuleName(currentModuleName) , currentModuleName(currentModuleName)
, locals(nullptr) , locals(nullptr)
{ {
LUAU_ASSERT(FFlag::LuauNewRequireTrace2);
} }
bool visit(AstExprTypeAssertion* expr) override bool visit(AstExprTypeAssertion* expr) override
@ -328,24 +154,13 @@ struct RequireTracer : AstVisitor
std::vector<AstExprCall*> requires; std::vector<AstExprCall*> requires;
}; };
} // anonymous namespace
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName) RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName)
{ {
if (FFlag::LuauNewRequireTrace2) RequireTraceResult result;
{ RequireTracer tracer{result, fileResolver, currentModuleName};
RequireTraceResult result; root->visit(&tracer);
RequireTracer tracer{result, fileResolver, currentModuleName}; tracer.process();
root->visit(&tracer); return result;
tracer.process();
return result;
}
else
{
RequireTracerOld tracer{fileResolver, currentModuleName};
root->visit(&tracer);
return tracer.result;
}
} }
} // namespace Luau } // namespace Luau

View File

@ -7,8 +7,6 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000)
LUAU_FASTFLAGVARIABLE(LuauSubstitutionDontReplaceIgnoredTypes, false)
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace Luau namespace Luau
{ {
@ -39,11 +37,8 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypeId itp : ttv->instantiatedTypeParams) for (TypeId itp : ttv->instantiatedTypeParams)
visitChild(itp); 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)) else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{ {
@ -339,10 +334,10 @@ std::optional<TypeId> Substitution::substitute(TypeId ty)
return std::nullopt; return std::nullopt;
for (auto [oldTy, newTy] : newTypes) for (auto [oldTy, newTy] : newTypes)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy)) if (!ignoreChildren(oldTy))
replaceChildren(newTy); replaceChildren(newTy);
for (auto [oldTp, newTp] : newPacks) for (auto [oldTp, newTp] : newPacks)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp)) if (!ignoreChildren(oldTp))
replaceChildren(newTp); replaceChildren(newTp);
TypeId newTy = replace(ty); TypeId newTy = replace(ty);
return newTy; return newTy;
@ -359,10 +354,10 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
return std::nullopt; return std::nullopt;
for (auto [oldTy, newTy] : newTypes) for (auto [oldTy, newTy] : newTypes)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy)) if (!ignoreChildren(oldTy))
replaceChildren(newTy); replaceChildren(newTy);
for (auto [oldTp, newTp] : newPacks) for (auto [oldTp, newTp] : newPacks)
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp)) if (!ignoreChildren(oldTp))
replaceChildren(newTp); replaceChildren(newTp);
TypePackId newTp = replace(tp); TypePackId newTp = replace(tp);
return newTp; return newTp;
@ -393,10 +388,7 @@ TypeId Substitution::clone(TypeId ty)
clone.name = ttv->name; clone.name = ttv->name;
clone.syntheticName = ttv->syntheticName; clone.syntheticName = ttv->syntheticName;
clone.instantiatedTypeParams = ttv->instantiatedTypeParams; clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
if (FFlag::LuauTypeAliasPacks)
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
clone.tags = ttv->tags; clone.tags = ttv->tags;
result = addType(std::move(clone)); result = addType(std::move(clone));
} }
@ -505,11 +497,8 @@ void Substitution::replaceChildren(TypeId ty)
for (TypeId& itp : ttv->instantiatedTypeParams) for (TypeId& itp : ttv->instantiatedTypeParams)
itp = replace(itp); 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)) 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> #include <stdexcept>
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions) LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions)
LUAU_FASTFLAG(LuauTypeAliasPacks) LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false)
namespace Luau namespace Luau
{ {
@ -59,11 +59,8 @@ struct FindCyclicTypes
for (TypeId itp : ttv.instantiatedTypeParams) for (TypeId itp : ttv.instantiatedTypeParams)
visitTypeVar(itp, *this, seen); 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; return exhaustive;
} }
@ -248,58 +245,45 @@ struct TypeVarStringifier
void stringify(const std::vector<TypeId>& types, const std::vector<TypePackId>& typePacks) 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; return;
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size())) if (types.size() || typePacks.size())
state.emit("<"); state.emit("<");
if (FFlag::LuauTypeAliasPacks) bool first = true;
{
bool first = true;
for (TypeId ty : types) for (TypeId ty : types)
{ {
if (!first) if (!first)
state.emit(", "); 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; first = false;
stringify(ty); if (!singleTp)
} state.emit("(");
bool singleTp = typePacks.size() == 1; stringify(tp);
for (TypePackId tp : typePacks) if (!singleTp)
{ state.emit(")");
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 (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size())) if (types.size() || typePacks.size())
state.emit(">"); state.emit(">");
} }
@ -767,12 +751,23 @@ struct TypePackStringifier
else else
state.emit(", "); state.emit(", ");
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size()); if (FFlag::LuauFunctionArgumentNameSize)
if (!elemNames.empty() && elemNames[elemIndex])
{ {
state.emit(elemNames[elemIndex]->name); if (elemIndex < elemNames.size() && elemNames[elemIndex])
state.emit(": "); {
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++; elemIndex++;
@ -929,38 +924,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
result.name += ttv->name ? *ttv->name : *ttv->syntheticName; result.name += ttv->name ? *ttv->name : *ttv->syntheticName;
if (FFlag::LuauTypeAliasPacks) tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
{
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 += ">";
}
}
return result; return result;
} }
@ -1161,17 +1125,37 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
s += ", "; s += ", ";
first = false; first = false;
// argNames is guaranteed to be equal to argTypes iff argNames is not empty. if (FFlag::LuauFunctionArgumentNameSize)
// 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())
{ {
LUAU_ASSERT(argNameIter != ftv.argNames.end()); // We don't currently respect opts.functionTypeArguments. I don't think this function should.
++argNameIter; 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 <limits>
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace namespace
{ {
bool isIdentifierStartChar(char c) bool isIdentifierStartChar(char c)
@ -787,7 +785,7 @@ struct Printer
writer.keyword("type"); writer.keyword("type");
writer.identifier(a->name.value); 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("<"); writer.symbol("<");
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer);
@ -798,14 +796,11 @@ struct Printer
writer.identifier(o.value); writer.identifier(o.value);
} }
if (FFlag::LuauTypeAliasPacks) for (auto o : a->genericPacks)
{ {
for (auto o : a->genericPacks) comma();
{ writer.identifier(o.value);
comma(); writer.symbol("...");
writer.identifier(o.value);
writer.symbol("...");
}
} }
writer.symbol(">"); writer.symbol(">");

View File

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

View File

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

View File

@ -23,22 +23,20 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 500)
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000) LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauClassPropertyAccessAsString, false)
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false) LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false) LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false)
LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false) LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false) LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false)
LUAU_FASTFLAGVARIABLE(LuauStrictRequire, false) LUAU_FASTFLAGVARIABLE(LuauStrictRequire, false)
LUAU_FASTFLAG(LuauSubstitutionDontReplaceIgnoredTypes)
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
LUAU_FASTFLAG(LuauNewRequireTrace2)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauTailArgumentTypeInfo, false)
LUAU_FASTFLAGVARIABLE(LuauModuleRequireErrorPack, false)
namespace Luau namespace Luau
{ {
@ -562,12 +560,6 @@ ErrorVec TypeChecker::canUnify(TypePackId left, TypePackId right, const Location
return canUnify_(left, right, 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> template<typename Id>
ErrorVec TypeChecker::canUnify_(Id superTy, Id subTy, const Location& location) 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]; Location location = scope->typeAliasLocations[name];
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}}); reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
if (FFlag::LuauTypeAliasPacks) bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
else
bindingsMap[name] = TypeFun{binding->typeParams, errorRecoveryType(anyType)};
} }
else else
{ {
ScopePtr aliasScope = ScopePtr aliasScope =
FFlag::LuauQuantifyInPlace2 ? childScope(scope, typealias.location, subLevel) : childScope(scope, typealias.location); 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); TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty); FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true; ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; 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};
}
} }
} }
else else
@ -1223,14 +1174,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, ty}; 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);
auto generic = get<GenericTypePack>(tp); aliasScope->privateTypePackBindings[generic->name] = tp;
LUAU_ASSERT(generic);
aliasScope->privateTypePackBindings[generic->name] = tp;
}
} }
TypeId ty = resolveType(aliasScope, *typealias.type); 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 // Copy can be skipped if this is an identical alias
if (ttv->name != name || ttv->instantiatedTypeParams != binding->typeParams || 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 // This is a shallow clone, original recursive links to self are not updated
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
clone.methodDefinitionLocations = ttv->methodDefinitionLocations; clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
clone.definitionModuleName = ttv->definitionModuleName; clone.definitionModuleName = ttv->definitionModuleName;
clone.name = name; clone.name = name;
clone.instantiatedTypeParams = binding->typeParams; clone.instantiatedTypeParams = binding->typeParams;
clone.instantiatedTypePackParams = binding->typePackParams;
if (FFlag::LuauTypeAliasPacks)
clone.instantiatedTypePackParams = binding->typePackParams;
ty = addType(std::move(clone)); ty = addType(std::move(clone));
} }
@ -1262,9 +1207,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
{ {
ttv->name = name; ttv->name = name;
ttv->instantiatedTypeParams = binding->typeParams; ttv->instantiatedTypeParams = binding->typeParams;
ttv->instantiatedTypePackParams = binding->typePackParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = binding->typePackParams;
} }
} }
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty))) 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. // 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; superTy = lookupType->type;
if (!get<ClassTypeVar>(follow(*superTy))) if (!get<ClassTypeVar>(follow(*superTy)))
@ -1851,6 +1794,24 @@ TypeId TypeChecker::checkExprTable(
if (isNonstrictMode() && !getTableType(exprType) && !get<FunctionTypeVar>(exprType)) if (isNonstrictMode() && !getTableType(exprType) && !get<FunctionTypeVar>(exprType))
exprType = anyType; 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}; props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location};
} }
else else
@ -3744,17 +3705,29 @@ ExprResult<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const L
for (size_t i = 0; i < exprs.size; ++i) for (size_t i = 0; i < exprs.size; ++i)
{ {
AstExpr* expr = exprs.data[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>())) if (i == lastIndex && (expr->is<AstExprCall>() || expr->is<AstExprVarargs>()))
{ {
auto [typePack, exprPredicates] = checkExprPack(scope, *expr); auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
insert(exprPredicates); 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; tp->tail = typePack;
} }
else else
{ {
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType); auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
insert(exprPredicates); insert(exprPredicates);
@ -3797,7 +3770,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker"); LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker");
LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str()); 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) 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: // 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 // either the file does not exist or there's a cycle. If there's a cycle
// we will already have reported the error. // 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); std::string reportedModulePath = resolver->getHumanReadableModuleName(moduleInfo.name);
reportError(TypeError{location, UnknownRequire{reportedModulePath}}); reportError(TypeError{location, UnknownRequire{reportedModulePath}});
@ -3830,7 +3803,12 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
return errorRecoveryType(scope); 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) if (!moduleType)
{ {
std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name); std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name);
@ -3840,7 +3818,8 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks); CloneState cloneState;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState);
} }
void TypeChecker::tablify(TypeId type) 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}; 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) TypeId TypeChecker::freshType(const ScopePtr& scope)
{ {
return freshType(scope->level); return freshType(scope->level);
@ -4477,117 +4451,82 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
return errorRecoveryType(scope); 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; 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) if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope); 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"}}); TypeId ty = resolveType(scope, *type);
if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope);
}
std::vector<TypeId> typeParams; if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
std::vector<TypeId> extraTypes; typeParams.push_back(ty);
std::vector<TypePackId> typePackParams; else if (typePackParams.empty())
extraTypes.push_back(ty);
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));
}
else else
return errorRecoveryType(scope); reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
} }
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
{ {
// If the generic parameters and the type arguments are the same, we are about to TypePackId tp = resolveTypePack(scope, *typePack);
// perform an identity substitution, which we can just short-circuit.
return tf->type; // 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) // If we still haven't meterialized an implicit type pack, do it now
typeParams.push_back(resolveType(scope, *param.type)); 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 (FFlag::LuauErrorRecoveryType)
{ {
// If there aren't enough type parameters, pad them out with error recovery types // Pad the types out with error recovery types
// (we've already reported the error) while (typeParams.size() < tf->typeParams.size())
while (typeParams.size() < lit->parameters.size)
typeParams.push_back(errorRecoveryType(scope)); typeParams.push_back(errorRecoveryType(scope));
while (typePackParams.size() < tf->typePackParams.size())
typePackParams.push_back(errorRecoveryTypePack(scope));
} }
else
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams) return errorRecoveryType(scope);
{
// 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);
} }
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>()) else if (const auto& table = annotation.as<AstTypeTable>())
{ {
@ -4757,7 +4696,7 @@ bool ApplyTypeFunction::isDirty(TypePackId tp)
bool ApplyTypeFunction::ignoreChildren(TypeId ty) bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{ {
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypeVar>(ty)) if (get<GenericTypeVar>(ty))
return true; return true;
else else
return false; return false;
@ -4765,7 +4704,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
bool ApplyTypeFunction::ignoreChildren(TypePackId tp) bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
{ {
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypePack>(tp)) if (get<GenericTypePack>(tp))
return true; return true;
else else
return false; return false;
@ -4788,36 +4727,26 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp)
// Really this should just replace the arguments, // Really this should just replace the arguments,
// but for bug-compatibility with existing code, we replace // but for bug-compatibility with existing code, we replace
// all generics by free type variables. // all generics by free type variables.
if (FFlag::LuauTypeAliasPacks) TypePackId& arg = typePackArguments[tp];
{ if (arg)
TypePackId& arg = typePackArguments[tp]; return arg;
if (arg)
return arg;
else
return addTypePack(FreeTypePack{level});
}
else else
{
return addTypePack(FreeTypePack{level}); return addTypePack(FreeTypePack{level});
}
} }
TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams, TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& typePackParams, const Location& location) 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; return tf.type;
applyTypeFunction.typeArguments.clear(); applyTypeFunction.typeArguments.clear();
for (size_t i = 0; i < tf.typeParams.size(); ++i) for (size_t i = 0; i < tf.typeParams.size(); ++i)
applyTypeFunction.typeArguments[tf.typeParams[i]] = typeParams[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.clear(); applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
}
applyTypeFunction.currentModule = currentModule; applyTypeFunction.currentModule = currentModule;
applyTypeFunction.level = scope->level; applyTypeFunction.level = scope->level;
@ -4866,9 +4795,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
if (ttv) if (ttv)
{ {
ttv->instantiatedTypeParams = typeParams; ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = typePackParams;
} }
} }
else else
@ -4884,9 +4811,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
} }
ttv->instantiatedTypeParams = typeParams; ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = typePackParams;
} }
} }
@ -4914,7 +4839,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
} }
TypeId g; TypeId g;
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks) if (FFlag::LuauRecursiveTypeParameterRestriction)
{ {
TypeId& cached = scope->parent->typeAliasTypeParameters[n]; TypeId& cached = scope->parent->typeAliasTypeParameters[n];
if (!cached) if (!cached)
@ -4944,7 +4869,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
} }
TypePackId g; TypePackId g;
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks) if (FFlag::LuauRecursiveTypeParameterRestriction)
{ {
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n]; TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
if (!cached) if (!cached)
@ -5245,7 +5170,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
auto typeFun = globalScope->lookupType(typeguardP.kind); 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}); return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
TypeId type = follow(typeFun->type); TypeId type = follow(typeFun->type);

View File

@ -19,7 +19,6 @@
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false) LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false)
LUAU_FASTFLAG(LuauErrorRecoveryType) 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) const TypeLevel* getLevel(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);

View File

@ -18,9 +18,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance, false);
LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false) LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false)
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false) LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false) LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false)
LUAU_FASTFLAGVARIABLE(LuauTypecheckOpts, false)
LUAU_FASTFLAG(LuauShareTxnSeen);
LUAU_FASTFLAGVARIABLE(LuauCacheUnifyTableResults, false)
LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false) LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false)
LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false) LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false)
@ -136,38 +133,19 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Locati
, globalScope(std::move(globalScope)) , globalScope(std::move(globalScope))
, location(location) , location(location)
, variance(variance) , 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) , sharedState(sharedState)
{ {
LUAU_ASSERT(sharedState.iceHandler); LUAU_ASSERT(sharedState.iceHandler);
} }
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location, 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) : types(types)
, mode(mode) , mode(mode)
, globalScope(std::move(globalScope)) , globalScope(std::move(globalScope))
, log(sharedSeen) , log(sharedSeen)
, location(location) , location(location)
, variance(variance) , variance(variance)
, counters(counters ? counters : &countersData)
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
, sharedState(sharedState) , sharedState(sharedState)
{ {
LUAU_ASSERT(sharedState.iceHandler); 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) void Unifier::tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
{ {
if (FFlag::LuauTypecheckOpts) sharedState.counters.iterationCount = 0;
counters->iterationCount = 0;
else
counters_DEPRECATED->iterationCount = 0;
tryUnify_(superTy, subTy, isFunctionCall, isIntersection); tryUnify_(superTy, subTy, isFunctionCall, isIntersection);
} }
void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection) void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
{ {
RecursionLimiter _ra( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
if (FFlag::LuauTypecheckOpts) ++sharedState.counters.iterationCount;
++counters->iterationCount;
else
++counters_DEPRECATED->iterationCount;
if (FInt::LuauTypeInferIterationLimit > 0 && if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
{ {
errors.push_back(TypeError{location, UnificationTooComplex{}}); errors.push_back(TypeError{location, UnificationTooComplex{}});
return; return;
@ -302,7 +272,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy)) if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
return tryUnifyWithAny(subTy, superTy); return tryUnifyWithAny(subTy, superTy);
bool cacheEnabled = FFlag::LuauCacheUnifyTableResults && !isFunctionCall && !isIntersection; bool cacheEnabled = !isFunctionCall && !isIntersection;
auto& cache = sharedState.cachedUnify; auto& cache = sharedState.cachedUnify;
// What if the types are immutable and we proved their relation before // 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) void Unifier::cacheResult(TypeId superTy, TypeId subTy)
{ {
LUAU_ASSERT(FFlag::LuauCacheUnifyTableResults);
bool* superTyInfo = sharedState.skipCacheForType.find(superTy); bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
if (superTyInfo && *superTyInfo) 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) void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
{ {
if (FFlag::LuauTypecheckOpts) sharedState.counters.iterationCount = 0;
counters->iterationCount = 0;
else
counters_DEPRECATED->iterationCount = 0;
tryUnify_(superTp, subTp, isFunctionCall); 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) void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
{ {
RecursionLimiter _ra( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
if (FFlag::LuauTypecheckOpts) ++sharedState.counters.iterationCount;
++counters->iterationCount;
else
++counters_DEPRECATED->iterationCount;
if (FInt::LuauTypeInferIterationLimit > 0 && if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
{ {
errors.push_back(TypeError{location, UnificationTooComplex{}}); errors.push_back(TypeError{location, UnificationTooComplex{}});
return; return;
@ -1727,39 +1687,8 @@ void Unifier::tryUnify(const TableIndexer& superIndexer, const TableIndexer& sub
tryUnify_(superIndexer.indexResultType, subIndexer.indexResultType); 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) static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{ {
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
while (true) while (true)
{ {
a = follow(a); 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, static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
TypeId anyType, TypePackId anyTypePack) TypeId anyType, TypePackId anyTypePack)
{ {
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
while (!queue.empty()) while (!queue.empty())
{ {
TypeId ty = follow(queue.back()); TypeId ty = follow(queue.back());
@ -1949,43 +1821,20 @@ void Unifier::tryUnifyWithAny(TypeId any, TypeId ty)
{ {
LUAU_ASSERT(get<AnyTypeVar>(any) || get<ErrorTypeVar>(any)); 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))
// These types are not visited in general loop below return;
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
return;
}
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes.anyType}}); const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes.anyType}});
const TypePackId anyTP = get<AnyTypeVar>(any) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}}); 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); 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);
}
} }
void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty) void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
@ -1994,38 +1843,14 @@ void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
const TypeId anyTy = singletonTypes.errorRecoveryType(); 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); 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);
}
} }
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name) 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) void Unifier::occursCheck(TypeId needle, TypeId haystack)
{ {
std::unordered_set<TypeId> seen_DEPRECATED; sharedState.tempSeenTy.clear();
if (FFlag::LuauCacheUnifyTableResults) return occursCheck(sharedState.tempSeenTy, needle, haystack);
{
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);
}
} }
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( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
needle = follow(needle); needle = follow(needle);
haystack = follow(haystack); haystack = follow(haystack);
if (FFlag::LuauTypecheckOpts) if (seen.find(haystack))
{ return;
if (seen.find(haystack))
return;
seen.insert(haystack); seen.insert(haystack);
}
else
{
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
return;
seen_DEPRECATED.insert(haystack);
}
if (get<Unifiable::Error>(needle)) if (get<Unifiable::Error>(needle))
return; return;
@ -2091,7 +1892,7 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
} }
auto check = [&](TypeId tv) { auto check = [&](TypeId tv) {
occursCheck(seen_DEPRECATED, seen, needle, tv); occursCheck(seen, needle, tv);
}; };
if (get<FreeTypeVar>(haystack)) 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) void Unifier::occursCheck(TypePackId needle, TypePackId haystack)
{ {
std::unordered_set<TypePackId> seen_DEPRECATED; sharedState.tempSeenTp.clear();
if (FFlag::LuauCacheUnifyTableResults) return occursCheck(sharedState.tempSeenTp, needle, haystack);
{
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);
}
} }
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); needle = follow(needle);
haystack = follow(haystack); haystack = follow(haystack);
if (FFlag::LuauTypecheckOpts) if (seen.find(haystack))
{ return;
if (seen.find(haystack))
return;
seen.insert(haystack); seen.insert(haystack);
}
else
{
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
return;
seen_DEPRECATED.insert(haystack);
}
if (get<Unifiable::Error>(needle)) if (get<Unifiable::Error>(needle))
return; return;
@ -2165,8 +1943,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
if (!get<Unifiable::Free>(needle)) if (!get<Unifiable::Free>(needle))
ice("Expected needle pack to be free"); ice("Expected needle pack to be free");
RecursionLimiter _ra( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
while (!get<ErrorTypeVar>(haystack)) 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))) if (auto f = get<FunctionTypeVar>(follow(ty)))
{ {
occursCheck(seen_DEPRECATED, seen, needle, f->argTypes); occursCheck(seen, needle, f->argTypes);
occursCheck(seen_DEPRECATED, seen, needle, f->retType); occursCheck(seen, needle, f->retType);
} }
} }
} }
@ -2204,10 +1981,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
Unifier Unifier::makeChildUnifier() Unifier Unifier::makeChildUnifier()
{ {
if (FFlag::LuauShareTxnSeen) return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState};
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};
} }
bool Unifier::isNonstrictMode() const bool Unifier::isNonstrictMode() const

View File

@ -13,8 +13,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false) LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false) LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false)
LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false) LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAliasPacks, false)
LUAU_FASTFLAGVARIABLE(LuauParseTypePackTypeParameters, false)
LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false) LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false) LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false)
@ -782,8 +780,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
AstType* type = parseTypeAnnotation(); AstType* type = parseTypeAnnotation();
return allocator.alloc<AstStatTypeAlias>( return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, generics, genericPacks, type, exported);
Location(start, type->location), name->name, generics, FFlag::LuauTypeAliasPacks ? genericPacks : AstArray<AstName>{}, type, exported);
} }
AstDeclaredClassProp Parser::parseDeclaredClassMethod() AstDeclaredClassProp Parser::parseDeclaredClassMethod()
@ -1602,30 +1599,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}}; return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
} }
if (FFlag::LuauParseTypePackTypeParameters) bool hasParameters = false;
AstArray<AstTypeOrPack> parameters{};
if (lexer.current().type == '<')
{ {
bool hasParameters = false; hasParameters = true;
AstArray<AstTypeOrPack> parameters{}; parameters = parseTypeParams();
if (lexer.current().type == '<')
{
hasParameters = true;
parameters = parseTypeParams();
}
Location end = lexer.previousLocation();
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
} }
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, hasParameters, parameters), {}};
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, false, generics), {}};
}
} }
else if (lexer.current().type == '{') else if (lexer.current().type == '{')
{ {
@ -2414,37 +2399,24 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
while (true) 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});
parameters.push_back({{}, typePack}); }
} else if (lexer.current().type == '(')
else if (lexer.current().type == '(') {
{ auto [type, typePack] = parseTypeOrPackAnnotation();
auto [type, typePack] = parseTypeOrPackAnnotation();
if (typePack) if (typePack)
{ parameters.push_back({{}, 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;
}
else else
{ parameters.push_back({type, {}});
parameters.push_back({parseTypeAnnotation(), {}}); }
} else if (lexer.current().type == '>' && parameters.empty())
{
break;
} }
else else
{ {

View File

@ -121,7 +121,7 @@ struct CliFileResolver : Luau::FileResolver
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>()) if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
{ {
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau"; 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 // fall back to .lua if a module with .luau doesn't exist
name = std::string(expr->value.data, expr->value.size) + ".lua"; name = std::string(expr->value.data, expr->value.size) + ".lua";
@ -132,27 +132,6 @@ struct CliFileResolver : Luau::FileResolver
return std::nullopt; 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 struct CliConfigResolver : Luau::ConfigResolver

View File

@ -198,11 +198,6 @@ static std::string runCode(lua_State* L, const std::string& source)
error += "\nstack backtrace:\n"; error += "\nstack backtrace:\n";
error += lua_debugtrace(T); error += lua_debugtrace(T);
#ifdef __EMSCRIPTEN__
// nicer formatting for errors in web repl
error = "Error:" + error;
#endif
fprintf(stdout, "%s", error.c_str()); fprintf(stdout, "%s", error.c_str());
} }
@ -210,39 +205,6 @@ static std::string runCode(lua_State* L, const std::string& source)
return std::string(); return std::string();
} }
#ifdef __EMSCRIPTEN__
extern "C"
{
const char* executeScript(const char* source)
{
// setup flags
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
if (strncmp(flag->name, "Luau", 4) == 0)
flag->value = true;
// create new state
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
// setup state
setupState(L);
// sandbox thread
luaL_sandboxthread(L);
// static string for caching result (prevents dangling ptr on function exit)
static std::string result;
// run code + collect error
result = runCode(L, source);
return result.empty() ? NULL : result.c_str();
}
}
#endif
// Excluded from emscripten compilation to avoid -Wunused-function errors.
#ifndef __EMSCRIPTEN__
static void completeIndexer(lua_State* L, const char* editBuffer, size_t start, std::vector<std::string>& completions) static void completeIndexer(lua_State* L, const char* editBuffer, size_t start, std::vector<std::string>& completions)
{ {
std::string_view lookup = editBuffer + start; std::string_view lookup = editBuffer + start;
@ -564,6 +526,5 @@ int main(int argc, char** argv)
return failed; return failed;
} }
} }
#endif

106
CLI/Web.cpp Normal file
View File

@ -0,0 +1,106 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "lua.h"
#include "lualib.h"
#include "luacode.h"
#include "Luau/Common.h"
#include <string>
#include <string.h>
static void setupState(lua_State* L)
{
luaL_openlibs(L);
luaL_sandbox(L);
}
static std::string runCode(lua_State* L, const std::string& source)
{
size_t bytecodeSize = 0;
char* bytecode = luau_compile(source.data(), source.length(), nullptr, &bytecodeSize);
int result = luau_load(L, "=stdin", bytecode, bytecodeSize, 0);
free(bytecode);
if (result != 0)
{
size_t len;
const char* msg = lua_tolstring(L, -1, &len);
std::string error(msg, len);
lua_pop(L, 1);
return error;
}
lua_State* T = lua_newthread(L);
lua_pushvalue(L, -2);
lua_remove(L, -3);
lua_xmove(L, T, 1);
int status = lua_resume(T, NULL, 0);
if (status == 0)
{
int n = lua_gettop(T);
if (n)
{
luaL_checkstack(T, LUA_MINSTACK, "too many results to print");
lua_getglobal(T, "print");
lua_insert(T, 1);
lua_pcall(T, n, 0, 0);
}
}
else
{
std::string error;
if (status == LUA_YIELD)
{
error = "thread yielded unexpectedly";
}
else if (const char* str = lua_tostring(T, -1))
{
error = str;
}
error += "\nstack backtrace:\n";
error += lua_debugtrace(T);
error = "Error:" + error;
fprintf(stdout, "%s", error.c_str());
}
lua_pop(L, 1);
return std::string();
}
extern "C" const char* executeScript(const char* source)
{
// setup flags
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
if (strncmp(flag->name, "Luau", 4) == 0)
flag->value = true;
// create new state
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
// setup state
setupState(L);
// sandbox thread
luaL_sandboxthread(L);
// static string for caching result (prevents dangling ptr on function exit)
static std::string result;
// run code + collect error
result = runCode(L, source);
return result.empty() ? NULL : result.c_str();
}

View File

@ -9,6 +9,7 @@ project(Luau LANGUAGES CXX)
option(LUAU_BUILD_CLI "Build CLI" ON) option(LUAU_BUILD_CLI "Build CLI" ON)
option(LUAU_BUILD_TESTS "Build tests" ON) option(LUAU_BUILD_TESTS "Build tests" ON)
option(LUAU_BUILD_WEB "Build Web module" OFF)
option(LUAU_WERROR "Warnings as errors" OFF) option(LUAU_WERROR "Warnings as errors" OFF)
add_library(Luau.Ast STATIC) add_library(Luau.Ast STATIC)
@ -18,26 +19,22 @@ add_library(Luau.VM STATIC)
if(LUAU_BUILD_CLI) if(LUAU_BUILD_CLI)
add_executable(Luau.Repl.CLI) add_executable(Luau.Repl.CLI)
if(NOT EMSCRIPTEN) add_executable(Luau.Analyze.CLI)
add_executable(Luau.Analyze.CLI)
else()
# add -fexceptions for emscripten to allow exceptions to be caught in C++
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
endif()
# This also adds target `name` on Linux/macOS and `name.exe` on Windows # This also adds target `name` on Linux/macOS and `name.exe` on Windows
set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau) set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau)
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
if(NOT EMSCRIPTEN)
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
endif()
endif() endif()
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) if(LUAU_BUILD_TESTS)
add_executable(Luau.UnitTest) add_executable(Luau.UnitTest)
add_executable(Luau.Conformance) add_executable(Luau.Conformance)
endif() endif()
if(LUAU_BUILD_WEB)
add_executable(Luau.Web)
endif()
include(Sources.cmake) include(Sources.cmake)
target_compile_features(Luau.Ast PUBLIC cxx_std_17) target_compile_features(Luau.Ast PUBLIC cxx_std_17)
@ -72,16 +69,18 @@ if(LUAU_WERROR)
endif() endif()
endif() endif()
if(LUAU_BUILD_WEB)
# add -fexceptions for emscripten to allow exceptions to be caught in C++
list(APPEND LUAU_OPTIONS -fexceptions)
endif()
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
if(LUAU_BUILD_CLI) if(LUAU_BUILD_CLI)
target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS})
if(NOT EMSCRIPTEN)
target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS})
endif()
target_include_directories(Luau.Repl.CLI PRIVATE extern) target_include_directories(Luau.Repl.CLI PRIVATE extern)
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM) target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM)
@ -93,20 +92,10 @@ if(LUAU_BUILD_CLI)
endif() endif()
endif() endif()
if(NOT EMSCRIPTEN) target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis)
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis)
endif()
if(EMSCRIPTEN)
# declare exported functions to emscripten
target_link_options(Luau.Repl.CLI PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -fexceptions)
# custom output directory for wasm + js file
set_target_properties(Luau.Repl.CLI PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/docs/assets/luau)
endif()
endif() endif()
if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) if(LUAU_BUILD_TESTS)
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.UnitTest PRIVATE extern) target_include_directories(Luau.UnitTest PRIVATE extern)
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler) target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler)
@ -115,3 +104,17 @@ if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN)
target_include_directories(Luau.Conformance PRIVATE extern) target_include_directories(Luau.Conformance PRIVATE extern)
target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.VM) target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.VM)
endif() endif()
if(LUAU_BUILD_WEB)
target_compile_options(Luau.Web PRIVATE ${LUAU_OPTIONS})
target_link_libraries(Luau.Web PRIVATE Luau.Compiler Luau.VM)
# declare exported functions to emscripten
target_link_options(Luau.Web PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap'])
# add -fexceptions for emscripten to allow exceptions to be caught in C++
target_link_options(Luau.Web PRIVATE -fexceptions)
# the output is a single .js file with an embedded wasm blob
target_link_options(Luau.Web PRIVATE -sSINGLE_FILE=1)
endif()

View File

@ -321,13 +321,15 @@ struct Compiler
compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i)); compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i));
} }
setDebugLine(expr->func); setDebugLineEnd(expr->func);
if (expr->self) if (expr->self)
{ {
AstExprIndexName* fi = expr->func->as<AstExprIndexName>(); AstExprIndexName* fi = expr->func->as<AstExprIndexName>();
LUAU_ASSERT(fi); LUAU_ASSERT(fi);
setDebugLine(fi->indexLocation);
BytecodeBuilder::StringRef iname = sref(fi->index); BytecodeBuilder::StringRef iname = sref(fi->index);
int32_t cid = bytecode.addConstantString(iname); int32_t cid = bytecode.addConstantString(iname);
if (cid < 0) if (cid < 0)
@ -1313,6 +1315,8 @@ struct Compiler
RegScope rs(this); RegScope rs(this);
uint8_t reg = compileExprAuto(expr->expr, rs); uint8_t reg = compileExprAuto(expr->expr, rs);
setDebugLine(expr->indexLocation);
BytecodeBuilder::StringRef iname = sref(expr->index); BytecodeBuilder::StringRef iname = sref(expr->index);
int32_t cid = bytecode.addConstantString(iname); int32_t cid = bytecode.addConstantString(iname);
if (cid < 0) if (cid < 0)
@ -2710,6 +2714,12 @@ struct Compiler
bytecode.setDebugLine(node->location.begin.line + 1); 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) void setDebugLineEnd(AstNode* node)
{ {
if (options.debugLevel >= 1) if (options.debugLevel >= 1)
@ -3650,7 +3660,7 @@ struct Compiler
{ {
if (options.vectorLib) if (options.vectorLib)
{ {
if (builtin.object == options.vectorLib && builtin.method == options.vectorCtor) if (builtin.isMethod(options.vectorLib, options.vectorCtor))
return LBF_VECTOR; return LBF_VECTOR;
} }
else 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_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze 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) FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o)
TESTS_ARGS= 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=. cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=.
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator $(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp
$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator $(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp
build/libprotobuf-mutator: build/libprotobuf-mutator:
git clone https://github.com/google/libprotobuf-mutator 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/Scope.h
Analysis/include/Luau/Substitution.h Analysis/include/Luau/Substitution.h
Analysis/include/Luau/Symbol.h Analysis/include/Luau/Symbol.h
Analysis/include/Luau/ToDot.h
Analysis/include/Luau/TopoSortStatements.h Analysis/include/Luau/TopoSortStatements.h
Analysis/include/Luau/ToString.h Analysis/include/Luau/ToString.h
Analysis/include/Luau/Transpiler.h Analysis/include/Luau/Transpiler.h
@ -86,6 +87,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/Scope.cpp Analysis/src/Scope.cpp
Analysis/src/Substitution.cpp Analysis/src/Substitution.cpp
Analysis/src/Symbol.cpp Analysis/src/Symbol.cpp
Analysis/src/ToDot.cpp
Analysis/src/TopoSortStatements.cpp Analysis/src/TopoSortStatements.cpp
Analysis/src/ToString.cpp Analysis/src/ToString.cpp
Analysis/src/Transpiler.cpp Analysis/src/Transpiler.cpp
@ -118,6 +120,7 @@ target_sources(Luau.VM PRIVATE
VM/src/ldo.cpp VM/src/ldo.cpp
VM/src/lfunc.cpp VM/src/lfunc.cpp
VM/src/lgc.cpp VM/src/lgc.cpp
VM/src/lgcdebug.cpp
VM/src/linit.cpp VM/src/linit.cpp
VM/src/lmathlib.cpp VM/src/lmathlib.cpp
VM/src/lmem.cpp VM/src/lmem.cpp
@ -194,6 +197,7 @@ if(TARGET Luau.UnitTest)
tests/RequireTracer.test.cpp tests/RequireTracer.test.cpp
tests/StringUtils.test.cpp tests/StringUtils.test.cpp
tests/Symbol.test.cpp tests/Symbol.test.cpp
tests/ToDot.test.cpp
tests/TopoSort.test.cpp tests/TopoSort.test.cpp
tests/ToString.test.cpp tests/ToString.test.cpp
tests/Transpiler.test.cpp tests/Transpiler.test.cpp
@ -224,3 +228,9 @@ if(TARGET Luau.Conformance)
tests/Conformance.test.cpp tests/Conformance.test.cpp
tests/main.cpp) tests/main.cpp)
endif() endif()
if(TARGET Luau.Web)
# Luau.Web Sources
target_sources(Luau.Web PRIVATE
CLI/Web.cpp)
endif()

View File

@ -21,6 +21,7 @@
#define LUA_ENVIRONINDEX (-10001) #define LUA_ENVIRONINDEX (-10001)
#define LUA_GLOBALSINDEX (-10002) #define LUA_GLOBALSINDEX (-10002)
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i)) #define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i))
#define lua_ispseudo(i) ((i) <= LUA_REGISTRYINDEX)
/* thread status; 0 is OK */ /* thread status; 0 is OK */
enum lua_Status enum lua_Status
@ -108,6 +109,7 @@ LUA_API int lua_isthreadreset(lua_State* L);
/* /*
** basic stack manipulation ** basic stack manipulation
*/ */
LUA_API int lua_absindex(lua_State* L, int idx);
LUA_API int lua_gettop(lua_State* L); LUA_API int lua_gettop(lua_State* L);
LUA_API void lua_settop(lua_State* L, int idx); LUA_API void lua_settop(lua_State* L, int idx);
LUA_API void lua_pushvalue(lua_State* L, int idx); LUA_API void lua_pushvalue(lua_State* L, int idx);
@ -159,7 +161,11 @@ LUA_API void lua_pushnil(lua_State* L);
LUA_API void lua_pushnumber(lua_State* L, double n); LUA_API void lua_pushnumber(lua_State* L, double n);
LUA_API void lua_pushinteger(lua_State* L, int n); LUA_API void lua_pushinteger(lua_State* L, int n);
LUA_API void lua_pushunsigned(lua_State* L, unsigned n); LUA_API void lua_pushunsigned(lua_State* L, unsigned n);
#if LUA_VECTOR_SIZE == 4
LUA_API void lua_pushvector(lua_State* L, float x, float y, float z, float w);
#else
LUA_API void lua_pushvector(lua_State* L, float x, float y, float z); LUA_API void lua_pushvector(lua_State* L, float x, float y, float z);
#endif
LUA_API void lua_pushlstring(lua_State* L, const char* s, size_t l); LUA_API void lua_pushlstring(lua_State* L, const char* s, size_t l);
LUA_API void lua_pushstring(lua_State* L, const char* s); LUA_API void lua_pushstring(lua_State* L, const char* s);
LUA_API const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp); LUA_API const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp);
@ -183,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 int lua_getreadonly(lua_State* L, int idx);
LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled); 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 void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
LUA_API int lua_getmetatable(lua_State* L, int objindex); LUA_API int lua_getmetatable(lua_State* L, int objindex);
LUA_API void lua_getfenv(lua_State* L, int idx); LUA_API void lua_getfenv(lua_State* L, int idx);
@ -227,6 +233,7 @@ enum lua_GCOp
LUA_GCRESTART, LUA_GCRESTART,
LUA_GCCOLLECT, LUA_GCCOLLECT,
LUA_GCCOUNT, LUA_GCCOUNT,
LUA_GCCOUNTB,
LUA_GCISRUNNING, LUA_GCISRUNNING,
// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation // garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
@ -281,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_pop(L, n) lua_settop(L, -(n)-1)
#define lua_newtable(L) lua_createtable(L, 0, 0) #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)) #define lua_strlen(L, i) lua_objlen(L, (i))
@ -289,6 +297,7 @@ LUA_API void lua_unref(lua_State* L, int ref);
#define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) #define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL) #define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN) #define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isvector(L, n) (lua_type(L, (n)) == LUA_TVECTOR)
#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD) #define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL)

View File

@ -34,7 +34,10 @@
#endif #endif
/* Can be used to reconfigure visibility/exports for public APIs */ /* Can be used to reconfigure visibility/exports for public APIs */
#ifndef LUA_API
#define LUA_API extern #define LUA_API extern
#endif
#define LUALIB_API LUA_API #define LUALIB_API LUA_API
/* Can be used to reconfigure visibility for internal APIs */ /* Can be used to reconfigure visibility for internal APIs */
@ -47,10 +50,14 @@
#endif #endif
/* Can be used to reconfigure internal error handling to use longjmp instead of C++ EH */ /* Can be used to reconfigure internal error handling to use longjmp instead of C++ EH */
#ifndef LUA_USE_LONGJMP
#define LUA_USE_LONGJMP 0 #define LUA_USE_LONGJMP 0
#endif
/* LUA_IDSIZE gives the maximum size for the description of the source */ /* LUA_IDSIZE gives the maximum size for the description of the source */
#ifndef LUA_IDSIZE
#define LUA_IDSIZE 256 #define LUA_IDSIZE 256
#endif
/* /*
@@ LUAI_GCGOAL defines the desired top heap size in relation to the live heap @@ LUAI_GCGOAL defines the desired top heap size in relation to the live heap
@ -59,7 +66,9 @@
** mean larger GC pauses which mean slower collection.) You can also change ** mean larger GC pauses which mean slower collection.) You can also change
** this value dynamically. ** this value dynamically.
*/ */
#ifndef LUAI_GCGOAL
#define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */ #define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */
#endif
/* /*
@@ LUAI_GCSTEPMUL / LUAI_GCSTEPSIZE define the default speed of garbage collection @@ LUAI_GCSTEPMUL / LUAI_GCSTEPSIZE define the default speed of garbage collection
@ -69,38 +78,63 @@
** CHANGE it if you want to change the granularity of the garbage ** CHANGE it if you want to change the granularity of the garbage
** collection. ** collection.
*/ */
#ifndef LUAI_GCSTEPMUL
#define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */ #define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */
#endif
#ifndef LUAI_GCSTEPSIZE
#define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */ #define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */
#endif
/* LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function */ /* LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function */
#ifndef LUA_MINSTACK
#define LUA_MINSTACK 20 #define LUA_MINSTACK 20
#endif
/* LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use */ /* LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use */
#ifndef LUAI_MAXCSTACK
#define LUAI_MAXCSTACK 8000 #define LUAI_MAXCSTACK 8000
#endif
/* LUAI_MAXCALLS limits the number of nested calls */ /* LUAI_MAXCALLS limits the number of nested calls */
#ifndef LUAI_MAXCALLS
#define LUAI_MAXCALLS 20000 #define LUAI_MAXCALLS 20000
#endif
/* LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size */ /* LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size */
#ifndef LUAI_MAXCCALLS
#define LUAI_MAXCCALLS 200 #define LUAI_MAXCCALLS 200
#endif
/* buffer size used for on-stack string operations; this limit depends on native stack size */ /* buffer size used for on-stack string operations; this limit depends on native stack size */
#ifndef LUA_BUFFERSIZE
#define LUA_BUFFERSIZE 512 #define LUA_BUFFERSIZE 512
#endif
/* number of valid Lua userdata tags */ /* number of valid Lua userdata tags */
#ifndef LUA_UTAG_LIMIT
#define LUA_UTAG_LIMIT 128 #define LUA_UTAG_LIMIT 128
#endif
/* upper bound for number of size classes used by page allocator */ /* upper bound for number of size classes used by page allocator */
#ifndef LUA_SIZECLASSES
#define LUA_SIZECLASSES 32 #define LUA_SIZECLASSES 32
#endif
/* available number of separate memory categories */ /* available number of separate memory categories */
#ifndef LUA_MEMORY_CATEGORIES
#define LUA_MEMORY_CATEGORIES 256 #define LUA_MEMORY_CATEGORIES 256
#endif
/* minimum size for the string table (must be power of 2) */ /* minimum size for the string table (must be power of 2) */
#ifndef LUA_MINSTRTABSIZE
#define LUA_MINSTRTABSIZE 32 #define LUA_MINSTRTABSIZE 32
#endif
/* maximum number of captures supported by pattern matching */ /* maximum number of captures supported by pattern matching */
#ifndef LUA_MAXCAPTURES
#define LUA_MAXCAPTURES 32 #define LUA_MAXCAPTURES 32
#endif
/* }================================================================== */ /* }================================================================== */
@ -122,3 +156,7 @@
void* s; \ void* s; \
long l; \ long l; \
} }
#define LUA_VECTOR_SIZE 3 /* must be 3 or 4 */
#define LUA_EXTRA_SIZE LUA_VECTOR_SIZE - 2

View File

@ -25,11 +25,17 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int numArg, const char* def
LUALIB_API double luaL_checknumber(lua_State* L, int numArg); LUALIB_API double luaL_checknumber(lua_State* L, int numArg);
LUALIB_API double luaL_optnumber(lua_State* L, int nArg, double def); LUALIB_API double luaL_optnumber(lua_State* L, int nArg, double def);
LUALIB_API int luaL_checkboolean(lua_State* L, int narg);
LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def);
LUALIB_API int luaL_checkinteger(lua_State* L, int numArg); LUALIB_API int luaL_checkinteger(lua_State* L, int numArg);
LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def); LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def);
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg); LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg);
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def); LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def);
LUALIB_API const float* luaL_checkvector(lua_State* L, int narg);
LUALIB_API const float* luaL_optvector(lua_State* L, int narg, const float* def);
LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg); LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg);
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t); LUALIB_API void luaL_checktype(lua_State* L, int narg, int t);
LUALIB_API void luaL_checkany(lua_State* L, int narg); LUALIB_API void luaL_checkany(lua_State* L, int narg);

View File

@ -13,6 +13,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAG(LuauActivateBeforeExec)
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n" 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" "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
"$URL: www.lua.org $\n"; "$URL: www.lua.org $\n";
@ -170,6 +172,12 @@ lua_State* lua_mainthread(lua_State* L)
** basic stack manipulation ** basic stack manipulation
*/ */
int lua_absindex(lua_State* L, int idx)
{
api_check(L, (idx > 0 && idx <= L->top - L->base) || (idx < 0 && -idx <= L->top - L->base) || lua_ispseudo(idx));
return idx > 0 || lua_ispseudo(idx) ? idx : cast_int(L->top - L->base) + idx + 1;
}
int lua_gettop(lua_State* L) int lua_gettop(lua_State* L)
{ {
return cast_int(L->top - L->base); return cast_int(L->top - L->base);
@ -550,12 +558,21 @@ void lua_pushunsigned(lua_State* L, unsigned u)
return; return;
} }
void lua_pushvector(lua_State* L, float x, float y, float z) #if LUA_VECTOR_SIZE == 4
void lua_pushvector(lua_State* L, float x, float y, float z, float w)
{ {
setvvalue(L->top, x, y, z); setvvalue(L->top, x, y, z, w);
api_incr_top(L); api_incr_top(L);
return; return;
} }
#else
void lua_pushvector(lua_State* L, float x, float y, float z)
{
setvvalue(L->top, x, y, z, 0.0f);
api_incr_top(L);
return;
}
#endif
void lua_pushlstring(lua_State* L, const char* s, size_t len) void lua_pushlstring(lua_State* L, const char* s, size_t len)
{ {
@ -922,14 +939,21 @@ void lua_call(lua_State* L, int nargs, int nresults)
checkresults(L, nargs, nresults); checkresults(L, nargs, nresults);
func = L->top - (nargs + 1); func = L->top - (nargs + 1);
int wasActive = luaC_threadactive(L); if (FFlag::LuauActivateBeforeExec)
l_setbit(L->stackstate, THREAD_ACTIVEBIT); {
luaC_checkthreadsleep(L); 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) if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT); resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
adjustresults(L, nresults); adjustresults(L, nresults);
return; return;
@ -970,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.func = L->top - (nargs + 1); /* function to be called */
c.nresults = nresults; c.nresults = nresults;
int wasActive = luaC_threadactive(L); if (FFlag::LuauActivateBeforeExec)
l_setbit(L->stackstate, THREAD_ACTIVEBIT); {
luaC_checkthreadsleep(L); 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) if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT); resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
adjustresults(L, nresults); adjustresults(L, nresults);
return status; return status;
@ -1030,6 +1061,11 @@ int lua_gc(lua_State* L, int what, int data)
res = cast_int(g->totalbytes >> 10); res = cast_int(g->totalbytes >> 10);
break; break;
} }
case LUA_GCCOUNTB:
{
res = cast_int(g->totalbytes & 1023);
break;
}
case LUA_GCISRUNNING: case LUA_GCISRUNNING:
{ {
res = (g->GCthreshold != SIZE_MAX); res = (g->GCthreshold != SIZE_MAX);
@ -1146,7 +1182,7 @@ void lua_concat(lua_State* L, int n)
return; 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); api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
luaC_checkGC(L); luaC_checkGC(L);
@ -1231,6 +1267,7 @@ uintptr_t lua_encodepointer(lua_State* L, uintptr_t p)
int lua_ref(lua_State* L, int idx) 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; int ref = LUA_REFNIL;
global_State* g = L->global; global_State* g = L->global;
StkId p = index2adr(L, idx); StkId p = index2adr(L, idx);

View File

@ -30,7 +30,7 @@ static const char* currfuncname(lua_State* L)
return debugname; return debugname;
} }
LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg) l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
{ {
const char* fname = currfuncname(L); const char* fname = currfuncname(L);
@ -40,7 +40,7 @@ LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
luaL_error(L, "invalid argument #%d (%s)", narg, extramsg); luaL_error(L, "invalid argument #%d (%s)", narg, extramsg);
} }
LUALIB_API l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname) l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname)
{ {
const char* fname = currfuncname(L); const char* fname = currfuncname(L);
const TValue* obj = luaA_toobject(L, narg); const TValue* obj = luaA_toobject(L, narg);
@ -66,7 +66,7 @@ static l_noret tag_error(lua_State* L, int narg, int tag)
luaL_typeerrorL(L, narg, lua_typename(L, tag)); luaL_typeerrorL(L, narg, lua_typename(L, tag));
} }
LUALIB_API void luaL_where(lua_State* L, int level) void luaL_where(lua_State* L, int level)
{ {
lua_Debug ar; lua_Debug ar;
if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0) if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0)
@ -77,7 +77,7 @@ LUALIB_API void luaL_where(lua_State* L, int level)
lua_pushliteral(L, ""); /* else, no information available... */ lua_pushliteral(L, ""); /* else, no information available... */
} }
LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...) l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
{ {
va_list argp; va_list argp;
va_start(argp, fmt); va_start(argp, fmt);
@ -90,7 +90,7 @@ LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
/* }====================================================== */ /* }====================================================== */
LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[]) int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[])
{ {
const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg); const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
int i; int i;
@ -101,7 +101,7 @@ LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const c
luaL_argerrorL(L, narg, msg); luaL_argerrorL(L, narg, msg);
} }
LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname) int luaL_newmetatable(lua_State* L, const char* tname)
{ {
lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */ lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */
if (!lua_isnil(L, -1)) /* name already in use? */ if (!lua_isnil(L, -1)) /* name already in use? */
@ -113,7 +113,7 @@ LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname)
return 1; return 1;
} }
LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname) void* luaL_checkudata(lua_State* L, int ud, const char* tname)
{ {
void* p = lua_touserdata(L, ud); void* p = lua_touserdata(L, ud);
if (p != NULL) if (p != NULL)
@ -131,25 +131,25 @@ LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname)
luaL_typeerrorL(L, ud, tname); /* else error */ luaL_typeerrorL(L, ud, tname); /* else error */
} }
LUALIB_API void luaL_checkstack(lua_State* L, int space, const char* mes) void luaL_checkstack(lua_State* L, int space, const char* mes)
{ {
if (!lua_checkstack(L, space)) if (!lua_checkstack(L, space))
luaL_error(L, "stack overflow (%s)", mes); luaL_error(L, "stack overflow (%s)", mes);
} }
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t) void luaL_checktype(lua_State* L, int narg, int t)
{ {
if (lua_type(L, narg) != t) if (lua_type(L, narg) != t)
tag_error(L, narg, t); tag_error(L, narg, t);
} }
LUALIB_API void luaL_checkany(lua_State* L, int narg) void luaL_checkany(lua_State* L, int narg)
{ {
if (lua_type(L, narg) == LUA_TNONE) if (lua_type(L, narg) == LUA_TNONE)
luaL_error(L, "missing argument #%d", narg); luaL_error(L, "missing argument #%d", narg);
} }
LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len) const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
{ {
const char* s = lua_tolstring(L, narg, len); const char* s = lua_tolstring(L, narg, len);
if (!s) if (!s)
@ -157,7 +157,7 @@ LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
return s; return s;
} }
LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len) const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len)
{ {
if (lua_isnoneornil(L, narg)) if (lua_isnoneornil(L, narg))
{ {
@ -169,7 +169,7 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def,
return luaL_checklstring(L, narg, len); return luaL_checklstring(L, narg, len);
} }
LUALIB_API double luaL_checknumber(lua_State* L, int narg) double luaL_checknumber(lua_State* L, int narg)
{ {
int isnum; int isnum;
double d = lua_tonumberx(L, narg, &isnum); double d = lua_tonumberx(L, narg, &isnum);
@ -178,12 +178,28 @@ LUALIB_API double luaL_checknumber(lua_State* L, int narg)
return d; return d;
} }
LUALIB_API double luaL_optnumber(lua_State* L, int narg, double def) double luaL_optnumber(lua_State* L, int narg, double def)
{ {
return luaL_opt(L, luaL_checknumber, narg, def); return luaL_opt(L, luaL_checknumber, narg, def);
} }
LUALIB_API int luaL_checkinteger(lua_State* L, int narg) int luaL_checkboolean(lua_State* L, int narg)
{
// This checks specifically for boolean values, ignoring
// all other truthy/falsy values. If the desired result
// is true if value is present then lua_toboolean should
// directly be used instead.
if (!lua_isboolean(L, narg))
tag_error(L, narg, LUA_TBOOLEAN);
return lua_toboolean(L, narg);
}
int luaL_optboolean(lua_State* L, int narg, int def)
{
return luaL_opt(L, luaL_checkboolean, narg, def);
}
int luaL_checkinteger(lua_State* L, int narg)
{ {
int isnum; int isnum;
int d = lua_tointegerx(L, narg, &isnum); int d = lua_tointegerx(L, narg, &isnum);
@ -192,12 +208,12 @@ LUALIB_API int luaL_checkinteger(lua_State* L, int narg)
return d; return d;
} }
LUALIB_API int luaL_optinteger(lua_State* L, int narg, int def) int luaL_optinteger(lua_State* L, int narg, int def)
{ {
return luaL_opt(L, luaL_checkinteger, narg, def); return luaL_opt(L, luaL_checkinteger, narg, def);
} }
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg) unsigned luaL_checkunsigned(lua_State* L, int narg)
{ {
int isnum; int isnum;
unsigned d = lua_tounsignedx(L, narg, &isnum); unsigned d = lua_tounsignedx(L, narg, &isnum);
@ -206,12 +222,25 @@ LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg)
return d; return d;
} }
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def) unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def)
{ {
return luaL_opt(L, luaL_checkunsigned, narg, def); return luaL_opt(L, luaL_checkunsigned, narg, def);
} }
LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event) const float* luaL_checkvector(lua_State* L, int narg)
{
const float* v = lua_tovector(L, narg);
if (!v)
tag_error(L, narg, LUA_TVECTOR);
return v;
}
const float* luaL_optvector(lua_State* L, int narg, const float* def)
{
return luaL_opt(L, luaL_checkvector, narg, def);
}
int luaL_getmetafield(lua_State* L, int obj, const char* event)
{ {
if (!lua_getmetatable(L, obj)) /* no metatable? */ if (!lua_getmetatable(L, obj)) /* no metatable? */
return 0; return 0;
@ -229,7 +258,7 @@ LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event)
} }
} }
LUALIB_API int luaL_callmeta(lua_State* L, int obj, const char* event) int luaL_callmeta(lua_State* L, int obj, const char* event)
{ {
obj = abs_index(L, obj); obj = abs_index(L, obj);
if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ if (!luaL_getmetafield(L, obj, event)) /* no metafield? */
@ -247,7 +276,7 @@ static int libsize(const luaL_Reg* l)
return size; return size;
} }
LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l) void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l)
{ {
if (libname) if (libname)
{ {
@ -273,7 +302,7 @@ LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg*
} }
} }
LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint) const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
{ {
const char* e; const char* e;
lua_pushvalue(L, idx); lua_pushvalue(L, idx);
@ -324,7 +353,7 @@ static size_t getnextbuffersize(lua_State* L, size_t currentsize, size_t desired
return newsize; return newsize;
} }
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B) void luaL_buffinit(lua_State* L, luaL_Buffer* B)
{ {
// start with an internal buffer // start with an internal buffer
B->p = B->buffer; B->p = B->buffer;
@ -334,14 +363,14 @@ LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B)
B->storage = nullptr; B->storage = nullptr;
} }
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size) char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size)
{ {
luaL_buffinit(L, B); luaL_buffinit(L, B);
luaL_reservebuffer(B, size, -1); luaL_reservebuffer(B, size, -1);
return B->p; return B->p;
} }
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc) char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
{ {
lua_State* L = B->L; lua_State* L = B->L;
@ -372,13 +401,13 @@ LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int bo
return B->p; return B->p;
} }
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc) void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
{ {
if (size_t(B->end - B->p) < size) if (size_t(B->end - B->p) < size)
luaL_extendbuffer(B, size - (B->end - B->p), boxloc); luaL_extendbuffer(B, size - (B->end - B->p), boxloc);
} }
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len) void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
{ {
if (size_t(B->end - B->p) < len) if (size_t(B->end - B->p) < len)
luaL_extendbuffer(B, len - (B->end - B->p), -1); luaL_extendbuffer(B, len - (B->end - B->p), -1);
@ -387,7 +416,7 @@ LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
B->p += len; B->p += len;
} }
LUALIB_API void luaL_addvalue(luaL_Buffer* B) void luaL_addvalue(luaL_Buffer* B)
{ {
lua_State* L = B->L; lua_State* L = B->L;
@ -404,7 +433,7 @@ LUALIB_API void luaL_addvalue(luaL_Buffer* B)
} }
} }
LUALIB_API void luaL_pushresult(luaL_Buffer* B) void luaL_pushresult(luaL_Buffer* B)
{ {
lua_State* L = B->L; lua_State* L = B->L;
@ -428,7 +457,7 @@ LUALIB_API void luaL_pushresult(luaL_Buffer* B)
} }
} }
LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size) void luaL_pushresultsize(luaL_Buffer* B, size_t size)
{ {
B->p += size; B->p += size;
luaL_pushresult(B); luaL_pushresult(B);
@ -436,7 +465,7 @@ LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size)
/* }====================================================== */ /* }====================================================== */
LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len) const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
{ {
if (luaL_callmeta(L, idx, "__tostring")) /* is there a metafield? */ if (luaL_callmeta(L, idx, "__tostring")) /* is there a metafield? */
{ {
@ -462,7 +491,11 @@ LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
case LUA_TVECTOR: case LUA_TVECTOR:
{ {
const float* v = lua_tovector(L, idx); const float* v = lua_tovector(L, idx);
#if LUA_VECTOR_SIZE == 4
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]);
#else
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]); lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
#endif
break; break;
} }
default: default:

View File

@ -401,7 +401,7 @@ static int luaB_newproxy(lua_State* L)
bool needsmt = lua_toboolean(L, 1); bool needsmt = lua_toboolean(L, 1);
lua_newuserdata(L, 0, 0); lua_newuserdata(L, 0);
if (needsmt) if (needsmt)
{ {
@ -441,7 +441,7 @@ static void auxopen(lua_State* L, const char* name, lua_CFunction f, lua_CFuncti
lua_setfield(L, -2, name); lua_setfield(L, -2, name);
} }
LUALIB_API int luaopen_base(lua_State* L) int luaopen_base(lua_State* L)
{ {
/* set global _G */ /* set global _G */
lua_pushvalue(L, LUA_GLOBALSINDEX); lua_pushvalue(L, LUA_GLOBALSINDEX);

View File

@ -236,7 +236,7 @@ static const luaL_Reg bitlib[] = {
{NULL, NULL}, {NULL, NULL},
}; };
LUALIB_API int luaopen_bit32(lua_State* L) int luaopen_bit32(lua_State* L)
{ {
luaL_register(L, LUA_BITLIBNAME, bitlib); luaL_register(L, LUA_BITLIBNAME, bitlib);

View File

@ -1018,13 +1018,23 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{ {
#if LUA_VECTOR_SIZE == 4
if (nparams >= 4 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1) && ttisnumber(args + 2))
#else
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
#endif
{ {
double x = nvalue(arg0); double x = nvalue(arg0);
double y = nvalue(args); double y = nvalue(args);
double z = nvalue(args + 1); double z = nvalue(args + 1);
setvvalue(res, float(x), float(y), float(z)); #if LUA_VECTOR_SIZE == 4
double w = nvalue(args + 2);
setvvalue(res, float(x), float(y), float(z), float(w));
#else
setvvalue(res, float(x), float(y), float(z), 0.0f);
#endif
return 1; return 1;
} }

View File

@ -272,7 +272,7 @@ static const luaL_Reg co_funcs[] = {
{NULL, NULL}, {NULL, NULL},
}; };
LUALIB_API int luaopen_coroutine(lua_State* L) int luaopen_coroutine(lua_State* L)
{ {
luaL_register(L, LUA_COLIBNAME, co_funcs); luaL_register(L, LUA_COLIBNAME, co_funcs);

View File

@ -160,7 +160,7 @@ static const luaL_Reg dblib[] = {
{NULL, NULL}, {NULL, NULL},
}; };
LUALIB_API int luaopen_debug(lua_State* L) int luaopen_debug(lua_State* L)
{ {
luaL_register(L, LUA_DBLIBNAME, dblib); luaL_register(L, LUA_DBLIBNAME, dblib);
return 1; return 1;

View File

@ -17,9 +17,9 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauExceptionMessageFix, false)
LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false) LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false)
LUAU_FASTFLAG(LuauCoroutineClose) LUAU_FASTFLAG(LuauCoroutineClose)
LUAU_FASTFLAGVARIABLE(LuauActivateBeforeExec, false)
/* /*
** {====================================================== ** {======================================================
@ -74,35 +74,28 @@ public:
const char* what() const throw() override 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. // Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX) 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()`. return str;
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";
} }
} }
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) if (luau_precall(L, func, nResults) == PCRLUA)
{ /* is a Lua function? */ { /* is a Lua function? */
L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */ 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--; L->nCcalls--;
luaC_checkGC(L); 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 luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
{ {
int status;
unsigned short oldnCcalls = L->nCcalls; unsigned short oldnCcalls = L->nCcalls;
ptrdiff_t old_ci = saveci(L, L->ci); 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) if (status != 0)
{ {
// call user-defined error function (used in xpcall) // 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; 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) if (FFlag::LuauCcallRestoreFix)
{ {
// Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. // 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 "ldo.h"
#include <string.h> #include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauRescanGrayAgainForwardBarrier, false)
LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false) LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false)
LUAU_FASTFLAG(LuauArrayBoundary) LUAU_FASTFLAG(LuauArrayBoundary)
@ -988,7 +986,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
GCObject* o = obj2gco(t); GCObject* o = obj2gco(t);
// in the second propagation stage, table assignment barrier works as a forward barrier // 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)); LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
reallymarkobject(g, v); 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 // measure the allocation rate in bytes/sec
// returns -1 if allocation rate cannot be measured // returns -1 if allocation rate cannot be measured
int64_t luaC_allocationrate(lua_State* L) 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

@ -17,7 +17,7 @@ static const luaL_Reg lualibs[] = {
{NULL, NULL}, {NULL, NULL},
}; };
LUALIB_API void luaL_openlibs(lua_State* L) void luaL_openlibs(lua_State* L)
{ {
const luaL_Reg* lib = lualibs; const luaL_Reg* lib = lualibs;
for (; lib->func; lib++) for (; lib->func; lib++)
@ -28,7 +28,7 @@ LUALIB_API void luaL_openlibs(lua_State* L)
} }
} }
LUALIB_API void luaL_sandbox(lua_State* L) void luaL_sandbox(lua_State* L)
{ {
// set all libraries to read-only // set all libraries to read-only
lua_pushnil(L); lua_pushnil(L);
@ -44,14 +44,14 @@ LUALIB_API void luaL_sandbox(lua_State* L)
lua_pushliteral(L, ""); lua_pushliteral(L, "");
lua_getmetatable(L, -1); lua_getmetatable(L, -1);
lua_setreadonly(L, -1, true); lua_setreadonly(L, -1, true);
lua_pop(L, 1); lua_pop(L, 2);
// set globals to readonly and activate safeenv since the env is immutable // set globals to readonly and activate safeenv since the env is immutable
lua_setreadonly(L, LUA_GLOBALSINDEX, true); lua_setreadonly(L, LUA_GLOBALSINDEX, true);
lua_setsafeenv(L, LUA_GLOBALSINDEX, true); lua_setsafeenv(L, LUA_GLOBALSINDEX, true);
} }
LUALIB_API void luaL_sandboxthread(lua_State* L) void luaL_sandboxthread(lua_State* L)
{ {
// create new global table that proxies reads to original table // create new global table that proxies reads to original table
lua_newtable(L); lua_newtable(L);
@ -81,7 +81,7 @@ static void* l_alloc(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsi
return realloc(ptr, nsize); return realloc(ptr, nsize);
} }
LUALIB_API lua_State* luaL_newstate(void) lua_State* luaL_newstate(void)
{ {
return lua_newstate(l_alloc, NULL); return lua_newstate(l_alloc, NULL);
} }

View File

@ -385,8 +385,7 @@ static int math_sign(lua_State* L)
static int math_round(lua_State* L) static int math_round(lua_State* L)
{ {
double v = luaL_checknumber(L, 1); lua_pushnumber(L, round(luaL_checknumber(L, 1)));
lua_pushnumber(L, round(v));
return 1; return 1;
} }
@ -429,7 +428,7 @@ static const luaL_Reg mathlib[] = {
/* /*
** Open math library ** Open math library
*/ */
LUALIB_API int luaopen_math(lua_State* L) int luaopen_math(lua_State* L)
{ {
uint64_t seed = uintptr_t(L); uint64_t seed = uintptr_t(L);
seed ^= time(NULL); seed ^= time(NULL);

View File

@ -33,11 +33,17 @@
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32) #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32)
#endif #endif
#if LUA_VECTOR_SIZE == 4
static_assert(sizeof(TValue) == ABISWITCH(24, 24, 24), "size mismatch for value");
static_assert(sizeof(LuaNode) == ABISWITCH(48, 48, 48), "size mismatch for table entry");
#else
static_assert(sizeof(TValue) == ABISWITCH(16, 16, 16), "size mismatch for value"); static_assert(sizeof(TValue) == ABISWITCH(16, 16, 16), "size mismatch for value");
static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry");
#endif
static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header");
static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header"); static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header");
static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header"); static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header");
static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry");
const size_t kSizeClasses = LUA_SIZECLASSES; const size_t kSizeClasses = LUA_SIZECLASSES;
const size_t kMaxSmallSize = 512; const size_t kMaxSmallSize = 512;

View File

@ -18,12 +18,20 @@
inline bool luai_veceq(const float* a, const float* b) inline bool luai_veceq(const float* a, const float* b)
{ {
#if LUA_VECTOR_SIZE == 4
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
#else
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2]; return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
#endif
} }
inline bool luai_vecisnan(const float* a) inline bool luai_vecisnan(const float* a)
{ {
#if LUA_VECTOR_SIZE == 4
return a[0] != a[0] || a[1] != a[1] || a[2] != a[2] || a[3] != a[3];
#else
return a[0] != a[0] || a[1] != a[1] || a[2] != a[2]; return a[0] != a[0] || a[1] != a[1] || a[2] != a[2];
#endif
} }
LUAU_FASTMATH_BEGIN LUAU_FASTMATH_BEGIN

View File

@ -15,7 +15,7 @@
const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL}; const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL};
int luaO_log2(unsigned int x) int luaO_log2(unsigned int x)
{ {

View File

@ -47,7 +47,7 @@ typedef union
typedef struct lua_TValue typedef struct lua_TValue
{ {
Value value; Value value;
int extra; int extra[LUA_EXTRA_SIZE];
int tt; int tt;
} TValue; } TValue;
@ -105,7 +105,19 @@ typedef struct lua_TValue
i_o->tt = LUA_TNUMBER; \ i_o->tt = LUA_TNUMBER; \
} }
#define setvvalue(obj, x, y, z) \ #if LUA_VECTOR_SIZE == 4
#define setvvalue(obj, x, y, z, w) \
{ \
TValue* i_o = (obj); \
float* i_v = i_o->value.v; \
i_v[0] = (x); \
i_v[1] = (y); \
i_v[2] = (z); \
i_v[3] = (w); \
i_o->tt = LUA_TVECTOR; \
}
#else
#define setvvalue(obj, x, y, z, w) \
{ \ { \
TValue* i_o = (obj); \ TValue* i_o = (obj); \
float* i_v = i_o->value.v; \ float* i_v = i_o->value.v; \
@ -114,6 +126,7 @@ typedef struct lua_TValue
i_v[2] = (z); \ i_v[2] = (z); \
i_o->tt = LUA_TVECTOR; \ i_o->tt = LUA_TVECTOR; \
} }
#endif
#define setpvalue(obj, x) \ #define setpvalue(obj, x) \
{ \ { \
@ -364,7 +377,7 @@ typedef struct Closure
typedef struct TKey typedef struct TKey
{ {
::Value value; ::Value value;
int extra; int extra[LUA_EXTRA_SIZE];
unsigned tt : 4; unsigned tt : 4;
int next : 28; /* for chaining */ int next : 28; /* for chaining */
} TKey; } TKey;
@ -381,7 +394,7 @@ typedef struct LuaNode
LuaNode* n_ = (node); \ LuaNode* n_ = (node); \
const TValue* i_o = (obj); \ const TValue* i_o = (obj); \
n_->key.value = i_o->value; \ n_->key.value = i_o->value; \
n_->key.extra = i_o->extra; \ memcpy(n_->key.extra, i_o->extra, sizeof(n_->key.extra)); \
n_->key.tt = i_o->tt; \ n_->key.tt = i_o->tt; \
checkliveness(L->global, i_o); \ checkliveness(L->global, i_o); \
} }
@ -392,7 +405,7 @@ typedef struct LuaNode
TValue* i_o = (obj); \ TValue* i_o = (obj); \
const LuaNode* n_ = (node); \ const LuaNode* n_ = (node); \
i_o->value = n_->key.value; \ i_o->value = n_->key.value; \
i_o->extra = n_->key.extra; \ memcpy(i_o->extra, n_->key.extra, sizeof(i_o->extra)); \
i_o->tt = n_->key.tt; \ i_o->tt = n_->key.tt; \
checkliveness(L->global, i_o); \ checkliveness(L->global, i_o); \
} }

View File

@ -186,7 +186,7 @@ static const luaL_Reg syslib[] = {
{NULL, NULL}, {NULL, NULL},
}; };
LUALIB_API int luaopen_os(lua_State* L) int luaopen_os(lua_State* L)
{ {
luaL_register(L, LUA_OSLIBNAME, syslib); luaL_register(L, LUA_OSLIBNAME, syslib);
return 1; return 1;

View File

@ -1657,7 +1657,7 @@ static void createmetatable(lua_State* L)
/* /*
** Open string library ** Open string library
*/ */
LUALIB_API int luaopen_string(lua_State* L) int luaopen_string(lua_State* L)
{ {
luaL_register(L, LUA_STRLIBNAME, strlib); luaL_register(L, LUA_STRLIBNAME, strlib);
createmetatable(L); createmetatable(L);

View File

@ -31,18 +31,19 @@ LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false)
#define MAXSIZE (1 << MAXBITS) #define MAXSIZE (1 << MAXBITS)
static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect"); static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect");
// TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case // TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case
static_assert(TKey{{NULL}, 0, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt"); static_assert(TKey{{NULL}, {0}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt");
static_assert(TKey{{NULL}, 0, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next");
static_assert(TKey{{NULL}, 0, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next");
// reset cache of absent metamethods, cache is updated in luaT_gettm // reset cache of absent metamethods, cache is updated in luaT_gettm
#define invalidateTMcache(t) t->flags = 0 #define invalidateTMcache(t) t->flags = 0
// empty hash data points to dummynode so that we can always dereference it // empty hash data points to dummynode so that we can always dereference it
const LuaNode luaH_dummynode = { const LuaNode luaH_dummynode = {
{{NULL}, 0, LUA_TNIL}, /* value */ {{NULL}, {0}, LUA_TNIL}, /* value */
{{NULL}, 0, LUA_TNIL, 0} /* key */ {{NULL}, {0}, LUA_TNIL, 0} /* key */
}; };
#define dummynode (&luaH_dummynode) #define dummynode (&luaH_dummynode)
@ -96,7 +97,7 @@ static LuaNode* hashnum(const Table* t, double n)
static LuaNode* hashvec(const Table* t, const float* v) static LuaNode* hashvec(const Table* t, const float* v)
{ {
unsigned int i[3]; unsigned int i[LUA_VECTOR_SIZE];
memcpy(i, v, sizeof(i)); memcpy(i, v, sizeof(i));
// convert -0 to 0 to make sure they hash to the same value // convert -0 to 0 to make sure they hash to the same value
@ -112,6 +113,12 @@ static LuaNode* hashvec(const Table* t, const float* v)
// Optimized Spatial Hashing for Collision Detection of Deformable Objects // Optimized Spatial Hashing for Collision Detection of Deformable Objects
unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791); unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);
#if LUA_VECTOR_SIZE == 4
i[3] = (i[3] == 0x8000000) ? 0 : i[3];
i[3] ^= i[3] >> 17;
h ^= i[3] * 39916801;
#endif
return hashpow2(t, h); return hashpow2(t, h);
} }

View File

@ -527,7 +527,7 @@ static const luaL_Reg tab_funcs[] = {
{NULL, NULL}, {NULL, NULL},
}; };
LUALIB_API int luaopen_table(lua_State* L) int luaopen_table(lua_State* L)
{ {
luaL_register(L, LUA_TABLIBNAME, tab_funcs); luaL_register(L, LUA_TABLIBNAME, tab_funcs);

View File

@ -283,7 +283,7 @@ static const luaL_Reg funcs[] = {
{NULL, NULL}, {NULL, NULL},
}; };
LUALIB_API int luaopen_utf8(lua_State* L) int luaopen_utf8(lua_State* L)
{ {
luaL_register(L, LUA_UTF8LIBNAME, funcs); luaL_register(L, LUA_UTF8LIBNAME, funcs);

View File

@ -601,7 +601,13 @@ static void luau_execute(lua_State* L)
const char* name = getstr(tsvalue(kv)); const char* name = getstr(tsvalue(kv));
int ic = (name[0] | ' ') - 'x'; int ic = (name[0] | ' ') - 'x';
if (unsigned(ic) < 3 && name[1] == '\0') #if LUA_VECTOR_SIZE == 4
// 'w' is before 'x' in ascii, so ic is -1 when indexing with 'w'
if (ic == -1)
ic = 3;
#endif
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
{ {
setnvalue(ra, rb->value.v[ic]); setnvalue(ra, rb->value.v[ic]);
VM_NEXT(); VM_NEXT();
@ -1526,7 +1532,7 @@ static void luau_execute(lua_State* L)
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
const float* vc = rc->value.v; const float* vc = rc->value.v;
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]); setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
VM_NEXT(); VM_NEXT();
} }
else else
@ -1572,7 +1578,7 @@ static void luau_execute(lua_State* L)
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
const float* vc = rc->value.v; const float* vc = rc->value.v;
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]); setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
VM_NEXT(); VM_NEXT();
} }
else else
@ -1618,21 +1624,21 @@ static void luau_execute(lua_State* L)
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
float vc = cast_to(float, nvalue(rc)); float vc = cast_to(float, nvalue(rc));
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc); setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
VM_NEXT(); VM_NEXT();
} }
else if (ttisvector(rb) && ttisvector(rc)) else if (ttisvector(rb) && ttisvector(rc))
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
const float* vc = rc->value.v; const float* vc = rc->value.v;
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]); setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
VM_NEXT(); VM_NEXT();
} }
else if (ttisnumber(rb) && ttisvector(rc)) else if (ttisnumber(rb) && ttisvector(rc))
{ {
float vb = cast_to(float, nvalue(rb)); float vb = cast_to(float, nvalue(rb));
const float* vc = rc->value.v; const float* vc = rc->value.v;
setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2]); setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2], vb * vc[3]);
VM_NEXT(); VM_NEXT();
} }
else else
@ -1679,21 +1685,21 @@ static void luau_execute(lua_State* L)
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
float vc = cast_to(float, nvalue(rc)); float vc = cast_to(float, nvalue(rc));
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc); setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
VM_NEXT(); VM_NEXT();
} }
else if (ttisvector(rb) && ttisvector(rc)) else if (ttisvector(rb) && ttisvector(rc))
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
const float* vc = rc->value.v; const float* vc = rc->value.v;
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]); setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
VM_NEXT(); VM_NEXT();
} }
else if (ttisnumber(rb) && ttisvector(rc)) else if (ttisnumber(rb) && ttisvector(rc))
{ {
float vb = cast_to(float, nvalue(rb)); float vb = cast_to(float, nvalue(rb));
const float* vc = rc->value.v; const float* vc = rc->value.v;
setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2]); setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2], vb / vc[3]);
VM_NEXT(); VM_NEXT();
} }
else else
@ -1826,7 +1832,7 @@ static void luau_execute(lua_State* L)
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
float vc = cast_to(float, nvalue(kv)); float vc = cast_to(float, nvalue(kv));
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc); setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
VM_NEXT(); VM_NEXT();
} }
else else
@ -1872,7 +1878,7 @@ static void luau_execute(lua_State* L)
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
float vc = cast_to(float, nvalue(kv)); float vc = cast_to(float, nvalue(kv));
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc); setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
VM_NEXT(); VM_NEXT();
} }
else else
@ -2037,7 +2043,7 @@ static void luau_execute(lua_State* L)
else if (ttisvector(rb)) else if (ttisvector(rb))
{ {
const float* vb = rb->value.v; const float* vb = rb->value.v;
setvvalue(ra, -vb[0], -vb[1], -vb[2]); setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
VM_NEXT(); VM_NEXT();
} }
else else

View File

@ -9,6 +9,7 @@
#include "lgc.h" #include "lgc.h"
#include "lmem.h" #include "lmem.h"
#include "lbytecode.h" #include "lbytecode.h"
#include "lapi.h"
#include <string.h> #include <string.h>
@ -162,9 +163,8 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
size_t GCthreshold = L->global->GCthreshold; size_t GCthreshold = L->global->GCthreshold;
L->global->GCthreshold = SIZE_MAX; L->global->GCthreshold = SIZE_MAX;
// env is 0 for current environment and a stack relative index otherwise // env is 0 for current environment and a stack index otherwise
LUAU_ASSERT(env <= 0 && L->top - L->base >= -env); Table* envt = (env == 0) ? hvalue(gt(L)) : hvalue(luaA_toobject(L, env));
Table* envt = (env == 0) ? hvalue(gt(L)) : hvalue(L->top + env);
TString* source = luaS_new(L, chunkname); TString* source = luaS_new(L, chunkname);

View File

@ -401,19 +401,19 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM
switch (op) switch (op)
{ {
case TM_ADD: case TM_ADD:
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]); setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
return; return;
case TM_SUB: case TM_SUB:
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]); setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
return; return;
case TM_MUL: case TM_MUL:
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]); setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
return; return;
case TM_DIV: case TM_DIV:
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]); setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
return; return;
case TM_UNM: case TM_UNM:
setvvalue(ra, -vb[0], -vb[1], -vb[2]); setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
return; return;
default: default:
break; break;
@ -430,10 +430,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM
switch (op) switch (op)
{ {
case TM_MUL: case TM_MUL:
setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc); setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc);
return; return;
case TM_DIV: case TM_DIV:
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc); setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc);
return; return;
default: default:
break; break;
@ -451,10 +451,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM
switch (op) switch (op)
{ {
case TM_MUL: case TM_MUL:
setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2]); setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]);
return; return;
case TM_DIV: case TM_DIV:
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2]); setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]);
return; return;
default: default:
break; break;

View File

@ -88,7 +88,7 @@ for i=1,N do
local y=ymin+(j-1)*dy local y=ymin+(j-1)*dy
S = S + level(x,y) S = S + level(x,y)
end end
-- if i % 10 == 0 then print(collectgarbage"count") end -- if i % 10 == 0 then print(collectgarbage("count")) end
end end
print(S) print(S)

View File

@ -88,7 +88,7 @@ for i=1,N do
local y=ymin+(j-1)*dy local y=ymin+(j-1)*dy
S = S + level(x,y) S = S + level(x,y)
end end
-- if i % 10 == 0 then print(collectgarbage"count") end -- if i % 10 == 0 then print(collectgarbage("count")) end
end end
print(S) print(S)

View File

@ -275,7 +275,7 @@ local function memory(s)
local t=os.clock() local t=os.clock()
--local dt=string.format("%f",t-t0) --local dt=string.format("%f",t-t0)
local dt=t-t0 local dt=t-t0
--io.stdout:write(s,"\t",dt," sec\t",t," sec\t",math.floor(collectgarbage"count"/1024),"M\n") --io.stdout:write(s,"\t",dt," sec\t",t," sec\t",math.floor(collectgarbage("count")/1024),"M\n")
t0=t t0=t
end end
@ -286,7 +286,7 @@ local function do_(f,s)
end end
local function julia(l,a,b) local function julia(l,a,b)
memory"begin" memory("begin")
cx=a cy=b cx=a cy=b
root=newcell() root=newcell()
exterior=newcell() exterior.color=white exterior=newcell() exterior.color=white
@ -297,14 +297,14 @@ memory"begin"
do_(update,"update") do_(update,"update")
repeat repeat
N=0 color(root,Rxmin,Rxmax,Rymin,Rymax) --print("color",N) N=0 color(root,Rxmin,Rxmax,Rymin,Rymax) --print("color",N)
until N==0 memory"color" until N==0 memory("color")
repeat repeat
N=0 prewhite(root,Rxmin,Rxmax,Rymin,Rymax) --print("prewhite",N) N=0 prewhite(root,Rxmin,Rxmax,Rymin,Rymax) --print("prewhite",N)
until N==0 memory"prewhite" until N==0 memory("prewhite")
do_(recolor,"recolor") do_(recolor,"recolor")
do_(colorup,"colorup") --print("colorup",N) do_(colorup,"colorup") --print("colorup",N)
local g,b=do_(area,"area") --print("area",g,b,g+b) local g,b=do_(area,"area") --print("area",g,b,g+b)
show(i) memory"output" show(i) memory("output")
--print("edges",nE) --print("edges",nE)
end end
end end

View File

@ -23,9 +23,11 @@ const bool kFuzzCompiler = true;
const bool kFuzzLinter = true; const bool kFuzzLinter = true;
const bool kFuzzTypeck = true; const bool kFuzzTypeck = true;
const bool kFuzzVM = true; const bool kFuzzVM = true;
const bool kFuzzTypes = true;
const bool kFuzzTranspile = true; const bool kFuzzTranspile = true;
// Should we generate type annotations?
const bool kFuzzTypes = true;
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!"); static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
std::string protoprint(const luau::StatBlock& stat, bool types); 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_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); 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") TEST_CASE_FIXTURE(ACFixture, "type_correct_sealed_table")
{ {
check(R"( check(R"(
@ -2210,8 +2243,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource")
TEST_CASE_FIXTURE(ACFixture, "autocompleteSource_require") TEST_CASE_FIXTURE(ACFixture, "autocompleteSource_require")
{ {
ScopedFastFlag luauResolveModuleNameWithoutACurrentModule("LuauResolveModuleNameWithoutACurrentModule", true);
std::string_view source = R"( std::string_view source = R"(
local a = require(w -- Line 1 local a = require(w -- Line 1
-- | Column 27 -- | Column 27
@ -2287,8 +2318,6 @@ until
TEST_CASE_FIXTURE(ACFixture, "if_then_else_elseif_completions") TEST_CASE_FIXTURE(ACFixture, "if_then_else_elseif_completions")
{ {
ScopedFastFlag sff{"ElseElseIfCompletionImprovements", true};
check(R"( check(R"(
local elsewhere = false 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") TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack")
{ {
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
check(R"( check(R"(
type A<T...> = () -> T... type A<T...> = () -> T...
local a: A<(number, s@1> 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"( CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"(
LOADN R0 20 LOADN R0 20
RETURN R0 1 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 // 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") TEST_CASE("DebugSource")
{ {
const char* source = R"( 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(); TEST_SUITE_END();

View File

@ -67,44 +67,42 @@ static int lua_vector(lua_State* L)
double y = luaL_checknumber(L, 2); double y = luaL_checknumber(L, 2);
double z = luaL_checknumber(L, 3); double z = luaL_checknumber(L, 3);
#if LUA_VECTOR_SIZE == 4
double w = luaL_optnumber(L, 4, 0.0);
lua_pushvector(L, float(x), float(y), float(z), float(w));
#else
lua_pushvector(L, float(x), float(y), float(z)); lua_pushvector(L, float(x), float(y), float(z));
#endif
return 1; return 1;
} }
static int lua_vector_dot(lua_State* L) static int lua_vector_dot(lua_State* L)
{ {
const float* a = lua_tovector(L, 1); const float* a = luaL_checkvector(L, 1);
const float* b = lua_tovector(L, 2); const float* b = luaL_checkvector(L, 2);
if (a && b) lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
{ return 1;
lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
return 1;
}
throw std::runtime_error("invalid arguments to vector:Dot");
} }
static int lua_vector_index(lua_State* L) static int lua_vector_index(lua_State* L)
{ {
const float* v = luaL_checkvector(L, 1);
const char* name = luaL_checkstring(L, 2); const char* name = luaL_checkstring(L, 2);
if (const float* v = lua_tovector(L, 1)) if (strcmp(name, "Magnitude") == 0)
{ {
if (strcmp(name, "Magnitude") == 0) lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
{ return 1;
lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
return 1;
}
if (strcmp(name, "Dot") == 0)
{
lua_pushcfunction(L, lua_vector_dot, "Dot");
return 1;
}
} }
throw std::runtime_error(Luau::format("%s is not a valid member of vector", name)); if (strcmp(name, "Dot") == 0)
{
lua_pushcfunction(L, lua_vector_dot, "Dot");
return 1;
}
luaL_error(L, "%s is not a valid member of vector", name);
} }
static int lua_vector_namecall(lua_State* L) static int lua_vector_namecall(lua_State* L)
@ -115,7 +113,7 @@ static int lua_vector_namecall(lua_State* L)
return lua_vector_dot(L); return lua_vector_dot(L);
} }
throw std::runtime_error(Luau::format("%s is not a valid method of vector", luaL_checkstring(L, 1))); luaL_error(L, "%s is not a valid method of vector", luaL_checkstring(L, 1));
} }
int lua_silence(lua_State* L) int lua_silence(lua_State* L)
@ -373,11 +371,17 @@ TEST_CASE("Pack")
TEST_CASE("Vector") TEST_CASE("Vector")
{ {
ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true};
runConformance("vector.lua", [](lua_State* L) { runConformance("vector.lua", [](lua_State* L) {
lua_pushcfunction(L, lua_vector, "vector"); lua_pushcfunction(L, lua_vector, "vector");
lua_setglobal(L, "vector"); lua_setglobal(L, "vector");
#if LUA_VECTOR_SIZE == 4
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
#else
lua_pushvector(L, 0.0f, 0.0f, 0.0f); lua_pushvector(L, 0.0f, 0.0f, 0.0f);
#endif
luaL_newmetatable(L, "vector"); luaL_newmetatable(L, "vector");
lua_pushstring(L, "__index"); lua_pushstring(L, "__index");
@ -504,6 +508,9 @@ TEST_CASE("Debugger")
cb->debugbreak = [](lua_State* L, lua_Debug* ar) { cb->debugbreak = [](lua_State* L, lua_Debug* ar) {
breakhits++; 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 // for every breakpoint, we break on the first invocation and continue on second
// this allows us to easily step off breakpoints // this allows us to easily step off breakpoints
// (real implementaiton may require singlestepping) // (real implementaiton may require singlestepping)
@ -524,7 +531,7 @@ TEST_CASE("Debugger")
L, L,
[](lua_State* L) -> int { [](lua_State* L) -> int {
int line = luaL_checkinteger(L, 1); int line = luaL_checkinteger(L, 1);
bool enabled = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true; bool enabled = luaL_optboolean(L, 2, true);
lua_Debug ar = {}; lua_Debug ar = {};
lua_getinfo(L, 1, "f", &ar); lua_getinfo(L, 1, "f", &ar);
@ -699,21 +706,52 @@ TEST_CASE("ApiFunctionCalls")
StateRef globalState = runConformance("apicalls.lua"); StateRef globalState = runConformance("apicalls.lua");
lua_State* L = globalState.get(); lua_State* L = globalState.get();
lua_getfield(L, LUA_GLOBALSINDEX, "add"); // lua_call
lua_pushnumber(L, 40); {
lua_pushnumber(L, 2); lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_call(L, 2, 1); lua_pushnumber(L, 40);
CHECK(lua_isnumber(L, -1)); lua_pushnumber(L, 2);
CHECK(lua_tonumber(L, -1) == 42); lua_call(L, 2, 1);
lua_pop(L, 1); CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
}
lua_getfield(L, LUA_GLOBALSINDEX, "add"); // lua_pcall
lua_pushnumber(L, 40); {
lua_pushnumber(L, 2); lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pcall(L, 2, 1, 0); lua_pushnumber(L, 40);
CHECK(lua_isnumber(L, -1)); lua_pushnumber(L, 2);
CHECK(lua_tonumber(L, -1) == 42); lua_pcall(L, 2, 1, 0);
lua_pop(L, 1); 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) static bool endsWith(const std::string& str, const std::string& suffix)
@ -727,8 +765,6 @@ static bool endsWith(const std::string& str, const std::string& suffix)
#if !LUA_USE_LONGJMP #if !LUA_USE_LONGJMP
TEST_CASE("ExceptionObject") TEST_CASE("ExceptionObject")
{ {
ScopedFastFlag sff("LuauExceptionMessageFix", true);
struct ExceptionResult struct ExceptionResult
{ {
bool exceptionGenerated; bool exceptionGenerated;

View File

@ -19,19 +19,6 @@ static const char* mainModuleName = "MainModule";
namespace Luau 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) std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* context, AstExpr* expr)
{ {
if (AstExprGlobal* g = expr->as<AstExprGlobal>()) if (AstExprGlobal* g = expr->as<AstExprGlobal>())
@ -81,24 +68,6 @@ std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* cont
return std::nullopt; 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 std::string TestFileResolver::getHumanReadableModuleName(const ModuleName& name) const
{ {
return name; return name;
@ -324,6 +293,13 @@ std::optional<TypeId> Fixture::findTypeAtPosition(Position position)
return Luau::findTypeAtPosition(*module, *sourceModule, 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) TypeId Fixture::requireTypeAtPosition(Position position)
{ {
auto ty = findTypeAtPosition(position); auto ty = findTypeAtPosition(position);

View File

@ -64,12 +64,8 @@ struct TestFileResolver
return SourceCode{it->second, sourceType}; return SourceCode{it->second, sourceType};
} }
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override;
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) 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::string getHumanReadableModuleName(const ModuleName& name) const override;
std::optional<std::string> getEnvironmentForModule(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); std::optional<TypeId> findTypeAtPosition(Position position);
TypeId requireTypeAtPosition(Position position); TypeId requireTypeAtPosition(Position position);
std::optional<TypeId> findExpectedTypeAtPosition(Position position);
std::optional<TypeId> lookupType(const std::string& name); std::optional<TypeId> lookupType(const std::string& name);
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, 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 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 std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override
{ {
if (AstExprGlobal* g = expr->as<AstExprGlobal>()) if (AstExprGlobal* g = expr->as<AstExprGlobal>())
@ -86,11 +74,6 @@ struct NaiveFileResolver : NullFileResolver
return std::nullopt; return std::nullopt;
} }
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
{
return lhs + "/" + ModuleName(rhs);
}
}; };
} // namespace } // 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); 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") TEST_CASE_FIXTURE(Fixture, "DuplicateLocal")
{ {
LintResult result = lint(R"( LintResult result = lint(R"(

View File

@ -44,9 +44,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive")
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
// numberType is persistent. We leave it as-is. // 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); CHECK_EQ(newNumber, typeChecker.numberType);
} }
@ -56,12 +57,13 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive")
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
// Create a new number type that isn't persistent // Create a new number type that isn't persistent
unfreeze(typeChecker.globalTypes); unfreeze(typeChecker.globalTypes);
TypeId oldNumber = typeChecker.globalTypes.addType(PrimitiveTypeVar{PrimitiveTypeVar::Number}); TypeId oldNumber = typeChecker.globalTypes.addType(PrimitiveTypeVar{PrimitiveTypeVar::Number});
freeze(typeChecker.globalTypes); freeze(typeChecker.globalTypes);
TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks); TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks, cloneState);
CHECK_NE(newNumber, oldNumber); CHECK_NE(newNumber, oldNumber);
CHECK_EQ(*oldNumber, *newNumber); CHECK_EQ(*oldNumber, *newNumber);
@ -89,9 +91,10 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
TypeArena dest; TypeArena dest;
TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks); TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks, cloneState);
TableTypeVar* ttv = getMutable<TableTypeVar>(counterCopy); TableTypeVar* ttv = getMutable<TableTypeVar>(counterCopy);
REQUIRE(ttv != nullptr); REQUIRE(ttv != nullptr);
@ -142,11 +145,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union")
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
unfreeze(typeChecker.globalTypes); unfreeze(typeChecker.globalTypes);
TypeId oldUnion = typeChecker.globalTypes.addType(UnionTypeVar{{typeChecker.numberType, typeChecker.stringType}}); TypeId oldUnion = typeChecker.globalTypes.addType(UnionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
freeze(typeChecker.globalTypes); freeze(typeChecker.globalTypes);
TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks); TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks, cloneState);
CHECK_NE(newUnion, oldUnion); CHECK_NE(newUnion, oldUnion);
CHECK_EQ("number | string", toString(newUnion)); CHECK_EQ("number | string", toString(newUnion));
@ -159,11 +163,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection")
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
unfreeze(typeChecker.globalTypes); unfreeze(typeChecker.globalTypes);
TypeId oldIntersection = typeChecker.globalTypes.addType(IntersectionTypeVar{{typeChecker.numberType, typeChecker.stringType}}); TypeId oldIntersection = typeChecker.globalTypes.addType(IntersectionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
freeze(typeChecker.globalTypes); freeze(typeChecker.globalTypes);
TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks); TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks, cloneState);
CHECK_NE(newIntersection, oldIntersection); CHECK_NE(newIntersection, oldIntersection);
CHECK_EQ("number & string", toString(newIntersection)); CHECK_EQ("number & string", toString(newIntersection));
@ -188,8 +193,9 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; 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); const ClassTypeVar* ctv = get<ClassTypeVar>(cloned);
REQUIRE(ctv != nullptr); REQUIRE(ctv != nullptr);
@ -211,16 +217,16 @@ TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types")
TypeArena dest; TypeArena dest;
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
bool encounteredFreeType = false; TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, cloneState);
TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
CHECK_EQ("any", toString(clonedTy)); CHECK_EQ("any", toString(clonedTy));
CHECK(encounteredFreeType); CHECK(cloneState.encounteredFreeType);
encounteredFreeType = false; cloneState = {};
TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, &encounteredFreeType); TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, cloneState);
CHECK_EQ("...any", toString(clonedTp)); CHECK_EQ("...any", toString(clonedTp));
CHECK(encounteredFreeType); CHECK(cloneState.encounteredFreeType);
} }
TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables") TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
@ -232,12 +238,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
TypeArena dest; TypeArena dest;
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
bool encounteredFreeType = false; TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, cloneState);
TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned); const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned);
CHECK_EQ(clonedTtv->state, TableState::Sealed); CHECK_EQ(clonedTtv->state, TableState::Sealed);
CHECK(encounteredFreeType); CHECK(cloneState.encounteredFreeType);
} }
TEST_CASE_FIXTURE(Fixture, "clone_self_property") 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"); "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 = 500;
#endif
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
TypeArena src;
TypeId table = src.addType(TableTypeVar{});
TypeId nested = table;
for (unsigned 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(); 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") TEST_CASE_FIXTURE(Fixture, "parse_type_pack_type_parameters")
{ {
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
AstStat* stat = parse(R"( AstStat* stat = parse(R"(
type Packed<T...> = () -> T... 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") TEST_CASE_FIXTURE(Fixture, "transpile_type_packs")
{ {
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
std::string code = R"( std::string code = R"(
type Packed<T...> = (T...)->(T...) type Packed<T...> = (T...)->(T...)
local a: Packed<> 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") TEST_CASE_FIXTURE(Fixture, "non_recursive_aliases_that_reuse_a_generic_name")
{ {
ScopedFastFlag sff1{"LuauSubstitutionDontReplaceIgnoredTypes", true};
CheckResult result = check(R"( CheckResult result = check(R"(
type Array<T> = { [number]: T } type Array<T> = { [number]: T }
type Tuple<T, V> = Array<T | V> 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") TEST_CASE_FIXTURE(Fixture, "bound_tables_do_not_clone_original_fields")
{ {
ScopedFastFlag luauCloneBoundTables{"LuauCloneBoundTables", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local exports = {} local exports = {}
local nested = {} local nested = {}
@ -627,4 +625,23 @@ return exports
LUAU_REQUIRE_NO_ERRORS(result); 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(); TEST_SUITE_END();

View File

@ -31,8 +31,6 @@ TEST_SUITE_BEGIN("ProvisionalTests");
*/ */
TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
{ {
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
const std::string code = R"( const std::string code = R"(
function f(a) function f(a)
if type(a) == "boolean" then 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) -> ()')"); 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(); 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])); // 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(); TEST_SUITE_END();

View File

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

View File

@ -3,6 +3,7 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h"
#include "Fixture.h" #include "Fixture.h"
#include "ScopedFlags.h" #include "ScopedFlags.h"
@ -323,4 +324,48 @@ TEST_CASE("tagging_props")
CHECK(Luau::hasTag(prop, "foo")); 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(); TEST_SUITE_END();

View File

@ -2,7 +2,13 @@
print('testing function calls through API') print('testing function calls through API')
function add(a, b) 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 end
return('OK') 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 = {} 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") 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") assert((function() a = newproxy(true) return concat(a == newproxy(true),a ~= newproxy(true)) end)() == "false,true")
-- rawequal -- rawequal
@ -876,4 +877,4 @@ assert(concat(typeof(5), typeof(nil), typeof({}), typeof(newproxy())) == "number
testgetfenv() -- DONT MOVE THIS LINE testgetfenv() -- DONT MOVE THIS LINE
return'OK' return 'OK'

View File

@ -419,11 +419,5 @@ co = coroutine.create(function ()
return loadstring("return a")() return loadstring("return a")()
end) end)
a = {a = 15}
-- debug.setfenv(co, a)
-- assert(debug.getfenv(co) == a)
-- assert(select(2, coroutine.resume(co)) == a)
-- assert(select(2, coroutine.resume(co)) == a.a)
return 'OK'
return'OK'

View File

@ -237,4 +237,4 @@ repeat
i = i+1 i = i+1
until i==c until i==c
return'OK' return 'OK'

View File

@ -373,4 +373,4 @@ do
assert(f() == 42) assert(f() == 42)
end end
return'OK' return 'OK'

View File

@ -74,4 +74,4 @@ assert(os.difftime(t1,t2) == 60*2-19)
assert(os.time({ year = 1970, day = 1, month = 1, hour = 0}) == 0) assert(os.time({ year = 1970, day = 1, month = 1, hour = 0}) == 0)
return'OK' return 'OK'

View File

@ -98,4 +98,4 @@ assert(quuz(function(...) end) == "0 true")
assert(quuz(function(a, b) end) == "2 false") assert(quuz(function(a, b) end) == "2 false")
assert(quuz(function(a, b, ...) end) == "2 true") assert(quuz(function(a, b, ...) end) == "2 true")
return'OK' return 'OK'

View File

@ -34,15 +34,15 @@ assert(doit("error('hi', 0)") == 'hi')
assert(doit("unpack({}, 1, n=2^30)")) assert(doit("unpack({}, 1, n=2^30)"))
assert(doit("a=math.sin()")) assert(doit("a=math.sin()"))
assert(not doit("tostring(1)") and doit("tostring()")) assert(not doit("tostring(1)") and doit("tostring()"))
assert(doit"tonumber()") assert(doit("tonumber()"))
assert(doit"repeat until 1; a") assert(doit("repeat until 1; a"))
checksyntax("break label", "", "label", 1) checksyntax("break label", "", "label", 1)
assert(doit";") assert(doit(";"))
assert(doit"a=1;;") assert(doit("a=1;;"))
assert(doit"return;;") assert(doit("return;;"))
assert(doit"assert(false)") assert(doit("assert(false)"))
assert(doit"assert(nil)") assert(doit("assert(nil)"))
assert(doit"a=math.sin\n(3)") assert(doit("a=math.sin\n(3)"))
assert(doit("function a (... , ...) end")) assert(doit("function a (... , ...) end"))
assert(doit("function a (, ...) end")) assert(doit("function a (, ...) end"))
@ -59,7 +59,7 @@ checkmessage("a=1; local a,bbbb=2,3; a = math.sin(1) and bbbb(3)",
"local 'bbbb'") "local 'bbbb'")
checkmessage("a={}; do local a=1 end a:bbbb(3)", "method 'bbbb'") checkmessage("a={}; do local a=1 end a:bbbb(3)", "method 'bbbb'")
checkmessage("local a={}; a.bbbb(3)", "field 'bbbb'") checkmessage("local a={}; a.bbbb(3)", "field 'bbbb'")
assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'")) assert(not string.find(doit("a={13}; local bbbb=1; a[bbbb](3)"), "'bbbb'"))
checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number") checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number")
aaa = nil aaa = nil
@ -67,14 +67,14 @@ checkmessage("aaa.bbb:ddd(9)", "global 'aaa'")
checkmessage("local aaa={bbb=1}; aaa.bbb:ddd(9)", "field 'bbb'") checkmessage("local aaa={bbb=1}; aaa.bbb:ddd(9)", "field 'bbb'")
checkmessage("local aaa={bbb={}}; aaa.bbb:ddd(9)", "method 'ddd'") checkmessage("local aaa={bbb={}}; aaa.bbb:ddd(9)", "method 'ddd'")
checkmessage("local a,b,c; (function () a = b+1 end)()", "upvalue 'b'") checkmessage("local a,b,c; (function () a = b+1 end)()", "upvalue 'b'")
assert(not doit"local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)") assert(not doit("local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)"))
checkmessage("b=1; local aaa='a'; x=aaa+b", "local 'aaa'") checkmessage("b=1; local aaa='a'; x=aaa+b", "local 'aaa'")
checkmessage("aaa={}; x=3/aaa", "global 'aaa'") checkmessage("aaa={}; x=3/aaa", "global 'aaa'")
checkmessage("aaa='2'; b=nil;x=aaa*b", "global 'b'") checkmessage("aaa='2'; b=nil;x=aaa*b", "global 'b'")
checkmessage("aaa={}; x=-aaa", "global 'aaa'") checkmessage("aaa={}; x=-aaa", "global 'aaa'")
assert(not string.find(doit"aaa={}; x=(aaa or aaa)+(aaa and aaa)", "'aaa'")) assert(not string.find(doit("aaa={}; x=(aaa or aaa)+(aaa and aaa)"), "'aaa'"))
assert(not string.find(doit"aaa={}; (aaa or aaa)()", "'aaa'")) assert(not string.find(doit("aaa={}; (aaa or aaa)()"), "'aaa'"))
checkmessage([[aaa=9 checkmessage([[aaa=9
repeat until 3==3 repeat until 3==3
@ -122,10 +122,10 @@ function lineerror (s)
return line and line+0 return line and line+0
end end
assert(lineerror"local a\n for i=1,'a' do \n print(i) \n end" == 2) assert(lineerror("local a\n for i=1,'a' do \n print(i) \n end") == 2)
-- assert(lineerror"\n local a \n for k,v in 3 \n do \n print(k) \n end" == 3) -- assert(lineerror("\n local a \n for k,v in 3 \n do \n print(k) \n end") == 3)
-- assert(lineerror"\n\n for k,v in \n 3 \n do \n print(k) \n end" == 4) -- assert(lineerror("\n\n for k,v in \n 3 \n do \n print(k) \n end") == 4)
assert(lineerror"function a.x.y ()\na=a+1\nend" == 1) assert(lineerror("function a.x.y ()\na=a+1\nend") == 1)
local p = [[ local p = [[
function g() f() end function g() f() end

View File

@ -77,7 +77,7 @@ end
local function dosteps (siz) local function dosteps (siz)
collectgarbage() collectgarbage()
collectgarbage"stop" collectgarbage("stop")
local a = {} local a = {}
for i=1,100 do a[i] = {{}}; local b = {} end for i=1,100 do a[i] = {{}}; local b = {} end
local x = gcinfo() local x = gcinfo()
@ -99,11 +99,11 @@ assert(dosteps(10000) == 1)
do do
local x = gcinfo() local x = gcinfo()
collectgarbage() collectgarbage()
collectgarbage"stop" collectgarbage("stop")
repeat repeat
local a = {} local a = {}
until gcinfo() > 1000 until gcinfo() > 1000
collectgarbage"restart" collectgarbage("restart")
repeat repeat
local a = {} local a = {}
until gcinfo() < 1000 until gcinfo() < 1000
@ -123,7 +123,7 @@ for n in pairs(b) do
end end
b = nil b = nil
collectgarbage() collectgarbage()
for n in pairs(a) do error'cannot be here' end for n in pairs(a) do error("cannot be here") end
for i=1,lim do a[i] = i end for i=1,lim do a[i] = i end
for i=1,lim do assert(a[i] == i) end for i=1,lim do assert(a[i] == i) end

View File

@ -368,9 +368,9 @@ assert(next(a,nil) == 1000 and next(a,1000) == nil)
assert(next({}) == nil) assert(next({}) == nil)
assert(next({}, nil) == nil) assert(next({}, nil) == nil)
for a,b in pairs{} do error"not here" end for a,b in pairs{} do error("not here") end
for i=1,0 do error'not here' end for i=1,0 do error("not here") end
for i=0,1,-1 do error'not here' end for i=0,1,-1 do error("not here") end
a = nil; for i=1,1 do assert(not a); a=1 end; assert(a) a = nil; for i=1,1 do assert(not a); a=1 end; assert(a)
a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a) a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a)

View File

@ -144,4 +144,4 @@ coroutine.resume(co)
resumeerror(co, "fail") resumeerror(co, "fail")
checkresults({ true, false, "fail" }, coroutine.resume(co)) checkresults({ true, false, "fail" }, coroutine.resume(co))
return'OK' return 'OK'

View File

@ -205,4 +205,4 @@ for p, c in string.gmatch(x, "()(" .. utf8.charpattern .. ")") do
end end
end end
return'OK' return 'OK'

View File

@ -1,6 +1,9 @@
-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
print('testing vectors') print('testing vectors')
-- detect vector size
local vector_size = if pcall(function() return vector(0, 0, 0).w end) then 4 else 3
-- equality -- equality
assert(vector(1, 2, 3) == vector(1, 2, 3)) assert(vector(1, 2, 3) == vector(1, 2, 3))
assert(vector(0, 1, 2) == vector(-0, 1, 2)) assert(vector(0, 1, 2) == vector(-0, 1, 2))
@ -13,8 +16,14 @@ assert(not rawequal(vector(1, 2, 3), vector(1, 2, 4)))
-- type & tostring -- type & tostring
assert(type(vector(1, 2, 3)) == "vector") assert(type(vector(1, 2, 3)) == "vector")
assert(tostring(vector(1, 2, 3)) == "1, 2, 3")
assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5") if vector_size == 4 then
assert(tostring(vector(1, 2, 3, 4)) == "1, 2, 3, 4")
assert(tostring(vector(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0")
else
assert(tostring(vector(1, 2, 3)) == "1, 2, 3")
assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5")
end
local t = {} local t = {}
@ -42,12 +51,19 @@ assert(8 * vector(8, 16, 24) == vector(64, 128, 192));
assert(vector(1, 2, 4) * '8' == vector(8, 16, 32)); assert(vector(1, 2, 4) * '8' == vector(8, 16, 32));
assert('8' * vector(8, 16, 24) == vector(64, 128, 192)); assert('8' * vector(8, 16, 24) == vector(64, 128, 192));
assert(vector(1, 2, 4) / vector(8, 16, 24) == vector(1/8, 2/16, 4/24)); if vector_size == 4 then
assert(vector(1, 2, 4, 8) / vector(8, 16, 24, 32) == vector(1/8, 2/16, 4/24, 8/32));
assert(8 / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4));
assert('8' / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4));
else
assert(vector(1, 2, 4) / vector(8, 16, 24, 1) == vector(1/8, 2/16, 4/24));
assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3));
assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3));
end
assert(vector(1, 2, 4) / 8 == vector(1/8, 1/4, 1/2)); assert(vector(1, 2, 4) / 8 == vector(1/8, 1/4, 1/2));
assert(vector(1, 2, 4) / (1 / val) == vector(1/8, 2/8, 4/8)); assert(vector(1, 2, 4) / (1 / val) == vector(1/8, 2/8, 4/8));
assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3));
assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2)); assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2));
assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3));
assert(-vector(1, 2, 4) == vector(-1, -2, -4)); assert(-vector(1, 2, 4) == vector(-1, -2, -4));
@ -71,4 +87,9 @@ assert(pcall(function() local t = {} rawset(t, vector(0/0, 2, 3), 1) end) == fal
-- make sure we cover both builtin and C impl -- make sure we cover both builtin and C impl
assert(vector(1, 2, 4) == vector("1", "2", "4")) assert(vector(1, 2, 4) == vector("1", "2", "4"))
-- additional checks for 4-component vectors
if vector_size == 4 then
assert(vector(1, 2, 3, 4).w == 4)
end
return 'OK' return 'OK'

View File

@ -458,13 +458,16 @@ def display(root, title, colors, flip = False):
framewidth = 1200 - 20 framewidth = 1200 - 20
def pixels(x):
return float(x) / root.width * framewidth if root.width > 0 else 0
for n in root.subtree(): for n in root.subtree():
if n.width / root.width * framewidth < 0.1: if pixels(n.width) < 0.1:
continue 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 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 height = 15
if colors == "cold": if colors == "cold":