mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/506 (#270)
- Fix some cases where type checking would overflow the native stack - Improve autocomplete behavior when assigning a partially written function call (not currently exposed through command line tools) - Improve autocomplete type inference feedback for some expressions where previously the type would not be known - Improve quantification performance during type checking for large types - Improve type checking for table literals when the expected type of the table is known because of a type annotation - Fix type checking errors in cases where required module has errors in the resulting type - Fix debug line information for multi-line chained call sequences (Add function name information for "attempt to call a nil value" #255) - lua_newuserdata now takes 2 arguments to match Lua/LuaJIT APIs better; lua_newuserdatatagged should be used if the third argument was non-0. - lua_ref can no longer be used with LUA_REGISTRYINDEX to prevent mistakes when migrating Lua FFI (Inconsistency with lua_ref #247) - Fix assertions and possible crashes when executing script code indirectly via metatable dispatch from lua_equal/lua_lessthan/lua_getfield/etc. (Hitting a crash in an assert after lua_equal is called. #259) - Fix flamegraph scripts to run under Python 2
This commit is contained in:
parent
f5ec6df7ba
commit
32fb6d10a7
@ -51,13 +51,6 @@ struct FileResolver
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// DEPRECATED APIS
|
||||
// These are going to be removed with LuauNewRequireTrace2
|
||||
virtual bool moduleExists(const ModuleName& name) const = 0;
|
||||
virtual std::optional<ModuleName> fromAstFragment(AstExpr* expr) const = 0;
|
||||
virtual ModuleName concat(const ModuleName& lhs, std::string_view rhs) const = 0;
|
||||
virtual std::optional<ModuleName> getParentModuleName(const ModuleName& name) const = 0;
|
||||
};
|
||||
|
||||
struct NullFileResolver : FileResolver
|
||||
@ -66,22 +59,6 @@ struct NullFileResolver : FileResolver
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
bool moduleExists(const ModuleName& name) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs;
|
||||
}
|
||||
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -78,9 +78,15 @@ void unfreeze(TypeArena& arena);
|
||||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
struct CloneState
|
||||
{
|
||||
int recursionCount = 0;
|
||||
bool encounteredFreeType = false;
|
||||
};
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
|
||||
|
||||
struct Module
|
||||
{
|
||||
|
31
Analysis/include/Luau/ToDot.h
Normal file
31
Analysis/include/Luau/ToDot.h
Normal 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
|
@ -25,15 +25,6 @@ struct TxnLog
|
||||
{
|
||||
}
|
||||
|
||||
explicit TxnLog(const std::vector<std::pair<TypeId, TypeId>>& ownedSeen)
|
||||
: originalSeenSize(ownedSeen.size())
|
||||
, ownedSeen(ownedSeen)
|
||||
, sharedSeen(nullptr)
|
||||
{
|
||||
// This is deprecated!
|
||||
LUAU_ASSERT(!FFlag::LuauShareTxnSeen);
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
|
||||
|
@ -297,7 +297,6 @@ private:
|
||||
|
||||
private:
|
||||
Unifier mkUnifier(const Location& location);
|
||||
Unifier mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location);
|
||||
|
||||
// These functions are only safe to call when we are in the process of typechecking a module.
|
||||
|
||||
|
@ -517,21 +517,6 @@ extern SingletonTypes singletonTypes;
|
||||
void persist(TypeId ty);
|
||||
void persist(TypePackId tp);
|
||||
|
||||
struct ToDotOptions
|
||||
{
|
||||
bool showPointers = true; // Show pointer value in the node label
|
||||
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
|
||||
};
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts);
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts);
|
||||
|
||||
std::string toDot(TypeId ty);
|
||||
std::string toDot(TypePackId tp);
|
||||
|
||||
void dumpDot(TypeId ty);
|
||||
void dumpDot(TypePackId tp);
|
||||
|
||||
const TypeLevel* getLevel(TypeId ty);
|
||||
TypeLevel* getMutableLevel(TypeId ty);
|
||||
|
||||
|
@ -19,12 +19,6 @@ enum Variance
|
||||
Invariant
|
||||
};
|
||||
|
||||
struct UnifierCounters
|
||||
{
|
||||
int recursionCount = 0;
|
||||
int iterationCount = 0;
|
||||
};
|
||||
|
||||
struct Unifier
|
||||
{
|
||||
TypeArena* const types;
|
||||
@ -37,20 +31,11 @@ struct Unifier
|
||||
Variance variance = Covariant;
|
||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||
|
||||
UnifierCounters* counters;
|
||||
UnifierCounters countersData;
|
||||
|
||||
std::shared_ptr<UnifierCounters> counters_DEPRECATED;
|
||||
|
||||
UnifierSharedState& sharedState;
|
||||
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& ownedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED = nullptr,
|
||||
UnifierCounters* counters = nullptr);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED = nullptr,
|
||||
UnifierCounters* counters = nullptr);
|
||||
Variance variance, UnifierSharedState& sharedState);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy);
|
||||
@ -92,9 +77,9 @@ private:
|
||||
public:
|
||||
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
|
||||
void occursCheck(TypeId needle, TypeId haystack);
|
||||
void occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
void occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
void occursCheck(TypePackId needle, TypePackId haystack);
|
||||
void occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
void occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
|
||||
Unifier makeChildUnifier();
|
||||
|
||||
@ -106,10 +91,6 @@ private:
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
|
||||
// Remove with FFlagLuauCacheUnifyTableResults
|
||||
DenseHashSet<TypeId> tempSeenTy_DEPRECATED{nullptr};
|
||||
DenseHashSet<TypePackId> tempSeenTp_DEPRECATED{nullptr};
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -24,6 +24,12 @@ struct TypeIdPairHash
|
||||
}
|
||||
};
|
||||
|
||||
struct UnifierCounters
|
||||
{
|
||||
int recursionCount = 0;
|
||||
int iterationCount = 0;
|
||||
};
|
||||
|
||||
struct UnifierSharedState
|
||||
{
|
||||
UnifierSharedState(InternalErrorReporter* iceHandler)
|
||||
@ -39,6 +45,8 @@ struct UnifierSharedState
|
||||
|
||||
DenseHashSet<TypeId> tempSeenTy{nullptr};
|
||||
DenseHashSet<TypePackId> tempSeenTp{nullptr};
|
||||
|
||||
UnifierCounters counters;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -5,8 +5,6 @@
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCacheUnifyTableResults)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -101,7 +99,7 @@ void visit(TypeId ty, F& f, Set& seen)
|
||||
// Some visitors want to see bound tables, that's why we visit the original type
|
||||
if (apply(ty, *ttv, seen, f))
|
||||
{
|
||||
if (FFlag::LuauCacheUnifyTableResults && ttv->boundTo)
|
||||
if (ttv->boundTo)
|
||||
{
|
||||
visit(*ttv->boundTo, f, seen);
|
||||
}
|
||||
|
@ -12,9 +12,9 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(ElseElseIfCompletionImprovements, false);
|
||||
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
@ -203,8 +203,9 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
||||
{
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, nullptr);
|
||||
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, nullptr);
|
||||
CloneState cloneState;
|
||||
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
auto errors = unifier.canUnify(expectedType, actualType);
|
||||
return errors.empty();
|
||||
@ -229,28 +230,51 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
||||
|
||||
TypeId expectedType = follow(*it);
|
||||
|
||||
if (canUnify(expectedType, ty))
|
||||
return TypeCorrectKind::Correct;
|
||||
|
||||
// We also want to suggest functions that return compatible result
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
|
||||
|
||||
if (!ftv)
|
||||
return TypeCorrectKind::None;
|
||||
|
||||
auto [retHead, retTail] = flatten(ftv->retType);
|
||||
|
||||
if (!retHead.empty())
|
||||
return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
|
||||
// We might only have a variadic tail pack, check if the element is compatible
|
||||
if (retTail)
|
||||
if (FFlag::LuauAutocompletePreferToCallFunctions)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)))
|
||||
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
}
|
||||
// We also want to suggest functions that return compatible result
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
auto [retHead, retTail] = flatten(ftv->retType);
|
||||
|
||||
return TypeCorrectKind::None;
|
||||
if (!retHead.empty() && canUnify(expectedType, retHead.front()))
|
||||
return TypeCorrectKind::CorrectFunctionResult;
|
||||
|
||||
// We might only have a variadic tail pack, check if the element is compatible
|
||||
if (retTail)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(expectedType, vtp->ty))
|
||||
return TypeCorrectKind::CorrectFunctionResult;
|
||||
}
|
||||
}
|
||||
|
||||
return canUnify(expectedType, ty) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (canUnify(expectedType, ty))
|
||||
return TypeCorrectKind::Correct;
|
||||
|
||||
// We also want to suggest functions that return compatible result
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
|
||||
|
||||
if (!ftv)
|
||||
return TypeCorrectKind::None;
|
||||
|
||||
auto [retHead, retTail] = flatten(ftv->retType);
|
||||
|
||||
if (!retHead.empty())
|
||||
return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
|
||||
// We might only have a variadic tail pack, check if the element is compatible
|
||||
if (retTail)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)))
|
||||
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
}
|
||||
|
||||
return TypeCorrectKind::None;
|
||||
}
|
||||
}
|
||||
|
||||
enum class PropIndexType
|
||||
@ -1413,7 +1437,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
|
||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); FFlag::ElseElseIfCompletionImprovements && statIf && !statIf->hasElse)
|
||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->hasElse)
|
||||
{
|
||||
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
||||
finder.ancestry};
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauNewRequireTrace2)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
|
||||
@ -473,9 +471,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
||||
if (!checkRequirePath(typechecker, expr.args.data[0]))
|
||||
return std::nullopt;
|
||||
|
||||
const AstExpr* require = FFlag::LuauNewRequireTrace2 ? &expr : expr.args.data[0];
|
||||
|
||||
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require))
|
||||
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr))
|
||||
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
|
||||
|
||||
return std::nullopt;
|
||||
|
@ -7,57 +7,14 @@
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
static std::string wrongNumberOfArgsString_DEPRECATED(size_t expectedCount, size_t actualCount, bool isTypeArgs = false)
|
||||
{
|
||||
std::string s = "expects " + std::to_string(expectedCount) + " ";
|
||||
|
||||
if (isTypeArgs)
|
||||
s += "type ";
|
||||
|
||||
s += "argument";
|
||||
if (expectedCount != 1)
|
||||
s += "s";
|
||||
|
||||
s += ", but ";
|
||||
|
||||
if (actualCount == 0)
|
||||
{
|
||||
s += "none";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actualCount < expectedCount)
|
||||
s += "only ";
|
||||
|
||||
s += std::to_string(actualCount);
|
||||
}
|
||||
|
||||
s += (actualCount == 1) ? " is" : " are";
|
||||
|
||||
s += " specified";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||
{
|
||||
std::string s;
|
||||
std::string s = "expects ";
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
s = "expects ";
|
||||
if (isVariadic)
|
||||
s += "at least ";
|
||||
|
||||
if (isVariadic)
|
||||
s += "at least ";
|
||||
|
||||
s += std::to_string(expectedCount) + " ";
|
||||
}
|
||||
else
|
||||
{
|
||||
s = "expects " + std::to_string(expectedCount) + " ";
|
||||
}
|
||||
s += std::to_string(expectedCount) + " ";
|
||||
|
||||
if (argPrefix)
|
||||
s += std::string(argPrefix) + " ";
|
||||
@ -188,10 +145,7 @@ struct ErrorConverter
|
||||
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
|
||||
" are required here";
|
||||
case CountMismatch::Arg:
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
|
||||
else
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString_DEPRECATED(e.expected, e.actual);
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unknown context");
|
||||
@ -232,7 +186,7 @@ struct ErrorConverter
|
||||
std::string operator()(const Luau::IncorrectGenericParameterCount& e) const
|
||||
{
|
||||
std::string name = e.name;
|
||||
if (!e.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !e.typeFun.typePackParams.empty()))
|
||||
if (!e.typeFun.typeParams.empty() || !e.typeFun.typePackParams.empty())
|
||||
{
|
||||
name += "<";
|
||||
bool first = true;
|
||||
@ -246,36 +200,25 @@ struct ErrorConverter
|
||||
name += toString(t);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (TypePackId t : e.typeFun.typePackParams)
|
||||
{
|
||||
for (TypePackId t : e.typeFun.typePackParams)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
name += ", ";
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
name += ", ";
|
||||
|
||||
name += toString(t);
|
||||
}
|
||||
name += toString(t);
|
||||
}
|
||||
|
||||
name += ">";
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
if (e.typeFun.typeParams.size() != e.actualParameters)
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
|
||||
if (e.typeFun.typeParams.size() != e.actualParameters)
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
|
||||
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString_DEPRECATED(e.typeFun.typeParams.size(), e.actualParameters, /*isTypeArgs*/ true);
|
||||
}
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::SyntaxError& e) const
|
||||
@ -591,11 +534,8 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
|
||||
if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size())
|
||||
return false;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
|
||||
return false;
|
||||
}
|
||||
if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < typeFun.typeParams.size(); ++i)
|
||||
{
|
||||
@ -603,13 +543,10 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (size_t i = 0; i < typeFun.typePackParams.size(); ++i)
|
||||
{
|
||||
for (size_t i = 0; i < typeFun.typePackParams.size(); ++i)
|
||||
{
|
||||
if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
|
||||
return false;
|
||||
}
|
||||
if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -733,14 +670,14 @@ bool containsParseErrorName(const TypeError& error)
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState cloneState)
|
||||
{
|
||||
auto clone = [&](auto&& ty) {
|
||||
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks);
|
||||
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks, cloneState);
|
||||
};
|
||||
|
||||
auto visitErrorData = [&](auto&& e) {
|
||||
copyError(e, destArena, seenTypes, seenTypePacks);
|
||||
copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
|
||||
};
|
||||
|
||||
if constexpr (false)
|
||||
@ -864,9 +801,10 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
|
||||
{
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
auto visitErrorData = [&](auto&& e) {
|
||||
copyError(e, destArena, seenTypes, seenTypePacks);
|
||||
copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
|
||||
};
|
||||
|
||||
LUAU_ASSERT(!destArena.typeVars.isFrozen());
|
||||
|
@ -18,10 +18,7 @@
|
||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauResolveModuleNameWithoutACurrentModule, false)
|
||||
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistDefinitionFileTypes, false)
|
||||
LUAU_FASTFLAG(LuauNewRequireTrace2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -96,10 +93,11 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
@ -110,7 +108,7 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
@ -427,15 +425,16 @@ CheckResult Frontend::check(const ModuleName& name)
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astTypes)
|
||||
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes)
|
||||
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astExpectedTypes)
|
||||
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
@ -885,16 +884,13 @@ std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const Module
|
||||
// If we can't find the current module name, that's because we bypassed the frontend's initializer
|
||||
// and called typeChecker.check directly. (This is done by autocompleteSource, for example).
|
||||
// In that case, requires will always fail.
|
||||
if (FFlag::LuauResolveModuleNameWithoutACurrentModule)
|
||||
return std::nullopt;
|
||||
else
|
||||
throw std::runtime_error("Frontend::resolveModuleName: Unknown currentModuleName '" + currentModuleName + "'");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto& exprs = it->second.exprs;
|
||||
|
||||
const ModuleInfo* info = exprs.find(&pathExpr);
|
||||
if (!info || (!FFlag::LuauNewRequireTrace2 && info->name.empty()))
|
||||
if (!info)
|
||||
return std::nullopt;
|
||||
|
||||
return *info;
|
||||
@ -911,10 +907,7 @@ const ModulePtr FrontendModuleResolver::getModule(const ModuleName& moduleName)
|
||||
|
||||
bool FrontendModuleResolver::moduleExists(const ModuleName& moduleName) const
|
||||
{
|
||||
if (FFlag::LuauNewRequireTrace2)
|
||||
return frontend->sourceNodes.count(moduleName) != 0;
|
||||
else
|
||||
return frontend->fileResolver->moduleExists(moduleName);
|
||||
return frontend->sourceNodes.count(moduleName) != 0;
|
||||
}
|
||||
|
||||
std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const
|
||||
|
@ -2,8 +2,6 @@
|
||||
#include "Luau/IostreamHelpers.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -94,7 +92,7 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo
|
||||
{
|
||||
stream << "IncorrectGenericParameterCount { name = " << error.name;
|
||||
|
||||
if (!error.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !error.typeFun.typePackParams.empty()))
|
||||
if (!error.typeFun.typeParams.empty() || !error.typeFun.typePackParams.empty())
|
||||
{
|
||||
stream << "<";
|
||||
bool first = true;
|
||||
@ -108,17 +106,14 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo
|
||||
stream << toString(t);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (TypePackId t : error.typeFun.typePackParams)
|
||||
{
|
||||
for (TypePackId t : error.typeFun.typePackParams)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
|
||||
stream << toString(t);
|
||||
}
|
||||
stream << toString(t);
|
||||
}
|
||||
|
||||
stream << ">";
|
||||
|
@ -5,8 +5,6 @@
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -615,12 +613,7 @@ struct AstJsonEncoder : public AstVisitor
|
||||
writeNode(node, "AstStatTypeAlias", [&]() {
|
||||
PROP(name);
|
||||
PROP(generics);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
PROP(genericPacks);
|
||||
}
|
||||
|
||||
PROP(genericPacks);
|
||||
PROP(type);
|
||||
PROP(exported);
|
||||
});
|
||||
|
@ -1,20 +1,20 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Module.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
|
||||
LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneBoundTables, false)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -120,12 +120,6 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
|
||||
return allocated;
|
||||
}
|
||||
|
||||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@ -138,11 +132,12 @@ struct TypePackCloner;
|
||||
|
||||
struct TypeCloner
|
||||
{
|
||||
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
: dest(dest)
|
||||
, typeId(typeId)
|
||||
, seenTypes(seenTypes)
|
||||
, seenTypePacks(seenTypePacks)
|
||||
, cloneState(cloneState)
|
||||
{
|
||||
}
|
||||
|
||||
@ -150,8 +145,7 @@ struct TypeCloner
|
||||
TypeId typeId;
|
||||
SeenTypes& seenTypes;
|
||||
SeenTypePacks& seenTypePacks;
|
||||
|
||||
bool* encounteredFreeType = nullptr;
|
||||
CloneState& cloneState;
|
||||
|
||||
template<typename T>
|
||||
void defaultClone(const T& t);
|
||||
@ -178,13 +172,14 @@ struct TypePackCloner
|
||||
TypePackId typePackId;
|
||||
SeenTypes& seenTypes;
|
||||
SeenTypePacks& seenTypePacks;
|
||||
bool* encounteredFreeType = nullptr;
|
||||
CloneState& cloneState;
|
||||
|
||||
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
: dest(dest)
|
||||
, typePackId(typePackId)
|
||||
, seenTypes(seenTypes)
|
||||
, seenTypePacks(seenTypePacks)
|
||||
, cloneState(cloneState)
|
||||
{
|
||||
}
|
||||
|
||||
@ -197,8 +192,7 @@ struct TypePackCloner
|
||||
|
||||
void operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
TypePackId err = singletonTypes.errorRecoveryTypePack(singletonTypes.anyTypePack);
|
||||
TypePackId cloned = dest.addTypePack(*err);
|
||||
@ -218,13 +212,13 @@ struct TypePackCloner
|
||||
// We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
|
||||
void operator()(const Unifiable::Bound<TypePackId>& t)
|
||||
{
|
||||
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
void operator()(const VariadicTypePack& t)
|
||||
{
|
||||
TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, encounteredFreeType)}});
|
||||
TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, cloneState)}});
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
@ -236,10 +230,10 @@ struct TypePackCloner
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
|
||||
for (TypeId ty : t.head)
|
||||
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
if (t.tail)
|
||||
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
};
|
||||
|
||||
@ -252,8 +246,7 @@ void TypeCloner::defaultClone(const T& t)
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
cloneState.encounteredFreeType = true;
|
||||
TypeId err = singletonTypes.errorRecoveryType(singletonTypes.anyType);
|
||||
TypeId cloned = dest.addType(*err);
|
||||
seenTypes[typeId] = cloned;
|
||||
@ -266,7 +259,7 @@ void TypeCloner::operator()(const Unifiable::Generic& t)
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
|
||||
{
|
||||
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
|
||||
seenTypes[typeId] = boundTo;
|
||||
}
|
||||
|
||||
@ -294,23 +287,23 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
for (TypeId generic : t.generics)
|
||||
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
for (TypePackId genericPack : t.genericPacks)
|
||||
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
ftv->tags = t.tags;
|
||||
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, cloneState);
|
||||
ftv->argNames = t.argNames;
|
||||
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const TableTypeVar& t)
|
||||
{
|
||||
// If table is now bound to another one, we ignore the content of the original
|
||||
if (FFlag::LuauCloneBoundTables && t.boundTo)
|
||||
if (t.boundTo)
|
||||
{
|
||||
TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
|
||||
seenTypes[typeId] = boundTo;
|
||||
return;
|
||||
}
|
||||
@ -326,34 +319,21 @@ void TypeCloner::operator()(const TableTypeVar& t)
|
||||
ttv->level = TypeLevel{0, 0};
|
||||
|
||||
for (const auto& [name, prop] : t.props)
|
||||
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
|
||||
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
|
||||
|
||||
if (t.indexer)
|
||||
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, encounteredFreeType),
|
||||
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, encounteredFreeType)};
|
||||
|
||||
if (!FFlag::LuauCloneBoundTables)
|
||||
{
|
||||
if (t.boundTo)
|
||||
ttv->boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, cloneState),
|
||||
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, cloneState)};
|
||||
|
||||
for (TypeId& arg : ttv->instantiatedTypeParams)
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
if (ttv->state == TableState::Free)
|
||||
{
|
||||
if (FFlag::LuauCloneBoundTables || !t.boundTo)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
}
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
ttv->state = TableState::Sealed;
|
||||
}
|
||||
@ -369,8 +349,8 @@ void TypeCloner::operator()(const MetatableTypeVar& t)
|
||||
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result);
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, cloneState);
|
||||
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const ClassTypeVar& t)
|
||||
@ -381,13 +361,13 @@ void TypeCloner::operator()(const ClassTypeVar& t)
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
for (const auto& [name, prop] : t.props)
|
||||
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
|
||||
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
|
||||
|
||||
if (t.parent)
|
||||
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
if (t.metatable)
|
||||
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const AnyTypeVar& t)
|
||||
@ -404,7 +384,7 @@ void TypeCloner::operator()(const UnionTypeVar& t)
|
||||
LUAU_ASSERT(option != nullptr);
|
||||
|
||||
for (TypeId ty : t.options)
|
||||
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const IntersectionTypeVar& t)
|
||||
@ -416,7 +396,7 @@ void TypeCloner::operator()(const IntersectionTypeVar& t)
|
||||
LUAU_ASSERT(option != nullptr);
|
||||
|
||||
for (TypeId ty : t.parts)
|
||||
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const LazyTypeVar& t)
|
||||
@ -426,17 +406,18 @@ void TypeCloner::operator()(const LazyTypeVar& t)
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
{
|
||||
if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
TypePackId& res = seenTypePacks[tp];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks};
|
||||
cloner.encounteredFreeType = encounteredFreeType;
|
||||
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks, cloneState};
|
||||
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
|
||||
}
|
||||
|
||||
@ -446,17 +427,18 @@ TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypeP
|
||||
return res;
|
||||
}
|
||||
|
||||
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
{
|
||||
if (typeId->persistent)
|
||||
return typeId;
|
||||
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
TypeId& res = seenTypes[typeId];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks};
|
||||
cloner.encounteredFreeType = encounteredFreeType;
|
||||
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
|
||||
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
}
|
||||
@ -467,19 +449,16 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
|
||||
return res;
|
||||
}
|
||||
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
{
|
||||
TypeFun result;
|
||||
for (TypeId ty : typeFun.typeParams)
|
||||
result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId tp : typeFun.typePackParams)
|
||||
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
}
|
||||
for (TypePackId tp : typeFun.typePackParams)
|
||||
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -519,19 +498,18 @@ bool Module::clonePublicInterface()
|
||||
LUAU_ASSERT(interfaceTypes.typeVars.empty());
|
||||
LUAU_ASSERT(interfaceTypes.typePacks.empty());
|
||||
|
||||
bool encounteredFreeType = false;
|
||||
|
||||
SeenTypePacks seenTypePacks;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
ScopePtr moduleScope = getModuleScope();
|
||||
|
||||
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
if (moduleScope->varargPack)
|
||||
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (auto& pair : moduleScope->exportedTypeBindings)
|
||||
pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (TypeId ty : moduleScope->returnType)
|
||||
if (get<GenericTypeVar>(follow(ty)))
|
||||
@ -540,7 +518,7 @@ bool Module::clonePublicInterface()
|
||||
freeze(internalTypes);
|
||||
freeze(interfaceTypes);
|
||||
|
||||
return encounteredFreeType;
|
||||
return cloneState.encounteredFreeType;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyVisitOnce, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -79,7 +81,16 @@ struct Quantifier
|
||||
void quantify(ModulePtr module, TypeId ty, TypeLevel level)
|
||||
{
|
||||
Quantifier q{std::move(module), level};
|
||||
visitTypeVar(ty, q);
|
||||
|
||||
if (FFlag::LuauQuantifyVisitOnce)
|
||||
{
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(ty, q, seen);
|
||||
}
|
||||
else
|
||||
{
|
||||
visitTypeVar(ty, q);
|
||||
}
|
||||
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
@ -4,182 +4,9 @@
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTraceRequireLookupChild, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewRequireTrace2, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct RequireTracerOld : AstVisitor
|
||||
{
|
||||
explicit RequireTracerOld(FileResolver* fileResolver, const ModuleName& currentModuleName)
|
||||
: fileResolver(fileResolver)
|
||||
, currentModuleName(currentModuleName)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauNewRequireTrace2);
|
||||
}
|
||||
|
||||
FileResolver* const fileResolver;
|
||||
ModuleName currentModuleName;
|
||||
DenseHashMap<AstLocal*, ModuleName> locals{nullptr};
|
||||
RequireTraceResult result;
|
||||
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr)
|
||||
{
|
||||
if (auto g = expr->as<AstExprGlobal>(); g && g->name == "script")
|
||||
return currentModuleName;
|
||||
|
||||
return fileResolver->fromAstFragment(expr);
|
||||
}
|
||||
|
||||
bool visit(AstStatLocal* stat) override
|
||||
{
|
||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||
{
|
||||
AstLocal* local = stat->vars.data[i];
|
||||
|
||||
if (local->annotation)
|
||||
{
|
||||
if (AstTypeTypeof* ann = local->annotation->as<AstTypeTypeof>())
|
||||
ann->expr->visit(this);
|
||||
}
|
||||
|
||||
if (i < stat->values.size)
|
||||
{
|
||||
AstExpr* expr = stat->values.data[i];
|
||||
expr->visit(this);
|
||||
|
||||
const ModuleInfo* info = result.exprs.find(expr);
|
||||
if (info)
|
||||
locals[local] = info->name;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprGlobal* global) override
|
||||
{
|
||||
std::optional<ModuleName> name = fromAstFragment(global);
|
||||
if (name)
|
||||
result.exprs[global] = {*name};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* local) override
|
||||
{
|
||||
const ModuleName* name = locals.find(local->local);
|
||||
if (name)
|
||||
result.exprs[local] = {*name};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexName* indexName) override
|
||||
{
|
||||
indexName->expr->visit(this);
|
||||
|
||||
const ModuleInfo* info = result.exprs.find(indexName->expr);
|
||||
if (info)
|
||||
{
|
||||
if (indexName->index == "parent" || indexName->index == "Parent")
|
||||
{
|
||||
if (auto parent = fileResolver->getParentModuleName(info->name))
|
||||
result.exprs[indexName] = {*parent};
|
||||
}
|
||||
else
|
||||
result.exprs[indexName] = {fileResolver->concat(info->name, indexName->index.value)};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexExpr* indexExpr) override
|
||||
{
|
||||
indexExpr->expr->visit(this);
|
||||
|
||||
const ModuleInfo* info = result.exprs.find(indexExpr->expr);
|
||||
const AstExprConstantString* str = indexExpr->index->as<AstExprConstantString>();
|
||||
if (info && str)
|
||||
{
|
||||
result.exprs[indexExpr] = {fileResolver->concat(info->name, std::string_view(str->value.data, str->value.size))};
|
||||
}
|
||||
|
||||
indexExpr->index->visit(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprTypeAssertion* expr) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we see game:GetService("StringLiteral") or Game:GetService("StringLiteral"), then rewrite to game.StringLiteral.
|
||||
// Else traverse arguments and trace requires to them.
|
||||
bool visit(AstExprCall* call) override
|
||||
{
|
||||
for (AstExpr* arg : call->args)
|
||||
arg->visit(this);
|
||||
|
||||
call->func->visit(this);
|
||||
|
||||
AstExprGlobal* globalName = call->func->as<AstExprGlobal>();
|
||||
if (globalName && globalName->name == "require" && call->args.size >= 1)
|
||||
{
|
||||
if (const ModuleInfo* moduleInfo = result.exprs.find(call->args.data[0]))
|
||||
result.requires.push_back({moduleInfo->name, call->location});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AstExprIndexName* indexName = call->func->as<AstExprIndexName>();
|
||||
if (!indexName)
|
||||
return false;
|
||||
|
||||
std::optional<ModuleName> rootName = fromAstFragment(indexName->expr);
|
||||
|
||||
if (FFlag::LuauTraceRequireLookupChild && !rootName)
|
||||
{
|
||||
if (const ModuleInfo* moduleInfo = result.exprs.find(indexName->expr))
|
||||
rootName = moduleInfo->name;
|
||||
}
|
||||
|
||||
if (!rootName)
|
||||
return false;
|
||||
|
||||
bool supportedLookup = indexName->index == "GetService" ||
|
||||
(FFlag::LuauTraceRequireLookupChild && (indexName->index == "FindFirstChild" || indexName->index == "WaitForChild"));
|
||||
|
||||
if (!supportedLookup)
|
||||
return false;
|
||||
|
||||
if (call->args.size != 1)
|
||||
return false;
|
||||
|
||||
AstExprConstantString* name = call->args.data[0]->as<AstExprConstantString>();
|
||||
if (!name)
|
||||
return false;
|
||||
|
||||
std::string_view v{name->value.data, name->value.size};
|
||||
if (v.end() != std::find(v.begin(), v.end(), '/'))
|
||||
return false;
|
||||
|
||||
result.exprs[call] = {fileResolver->concat(*rootName, v)};
|
||||
|
||||
// 'WaitForChild' can be used on modules that are not available at the typecheck time, but will be available at runtime
|
||||
// If we fail to find such module, we will not report an UnknownRequire error
|
||||
if (FFlag::LuauTraceRequireLookupChild && indexName->index == "WaitForChild")
|
||||
result.exprs[call].optional = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct RequireTracer : AstVisitor
|
||||
{
|
||||
RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName)
|
||||
@ -188,7 +15,6 @@ struct RequireTracer : AstVisitor
|
||||
, currentModuleName(currentModuleName)
|
||||
, locals(nullptr)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNewRequireTrace2);
|
||||
}
|
||||
|
||||
bool visit(AstExprTypeAssertion* expr) override
|
||||
@ -328,24 +154,13 @@ struct RequireTracer : AstVisitor
|
||||
std::vector<AstExprCall*> requires;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName)
|
||||
{
|
||||
if (FFlag::LuauNewRequireTrace2)
|
||||
{
|
||||
RequireTraceResult result;
|
||||
RequireTracer tracer{result, fileResolver, currentModuleName};
|
||||
root->visit(&tracer);
|
||||
tracer.process();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
RequireTracerOld tracer{fileResolver, currentModuleName};
|
||||
root->visit(&tracer);
|
||||
return tracer.result;
|
||||
}
|
||||
RequireTraceResult result;
|
||||
RequireTracer tracer{result, fileResolver, currentModuleName};
|
||||
root->visit(&tracer);
|
||||
tracer.process();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -7,8 +7,6 @@
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubstitutionDontReplaceIgnoredTypes, false)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -39,11 +37,8 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
||||
for (TypeId itp : ttv->instantiatedTypeParams)
|
||||
visitChild(itp);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp);
|
||||
}
|
||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp);
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
@ -339,10 +334,10 @@ std::optional<TypeId> Substitution::substitute(TypeId ty)
|
||||
return std::nullopt;
|
||||
|
||||
for (auto [oldTy, newTy] : newTypes)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy))
|
||||
if (!ignoreChildren(oldTy))
|
||||
replaceChildren(newTy);
|
||||
for (auto [oldTp, newTp] : newPacks)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp))
|
||||
if (!ignoreChildren(oldTp))
|
||||
replaceChildren(newTp);
|
||||
TypeId newTy = replace(ty);
|
||||
return newTy;
|
||||
@ -359,10 +354,10 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
|
||||
return std::nullopt;
|
||||
|
||||
for (auto [oldTy, newTy] : newTypes)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy))
|
||||
if (!ignoreChildren(oldTy))
|
||||
replaceChildren(newTy);
|
||||
for (auto [oldTp, newTp] : newPacks)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp))
|
||||
if (!ignoreChildren(oldTp))
|
||||
replaceChildren(newTp);
|
||||
TypePackId newTp = replace(tp);
|
||||
return newTp;
|
||||
@ -393,10 +388,7 @@ TypeId Substitution::clone(TypeId ty)
|
||||
clone.name = ttv->name;
|
||||
clone.syntheticName = ttv->syntheticName;
|
||||
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
|
||||
|
||||
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
|
||||
clone.tags = ttv->tags;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
@ -505,11 +497,8 @@ void Substitution::replaceChildren(TypeId ty)
|
||||
for (TypeId& itp : ttv->instantiatedTypeParams)
|
||||
itp = replace(itp);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId& itp : ttv->instantiatedTypePackParams)
|
||||
itp = replace(itp);
|
||||
}
|
||||
for (TypePackId& itp : ttv->instantiatedTypePackParams)
|
||||
itp = replace(itp);
|
||||
}
|
||||
else if (MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(ty))
|
||||
{
|
||||
|
378
Analysis/src/ToDot.cpp
Normal file
378
Analysis/src/ToDot.cpp
Normal 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
|
@ -11,7 +11,7 @@
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -59,11 +59,8 @@ struct FindCyclicTypes
|
||||
for (TypeId itp : ttv.instantiatedTypeParams)
|
||||
visitTypeVar(itp, *this, seen);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId itp : ttv.instantiatedTypePackParams)
|
||||
visitTypeVar(itp, *this, seen);
|
||||
}
|
||||
for (TypePackId itp : ttv.instantiatedTypePackParams)
|
||||
visitTypeVar(itp, *this, seen);
|
||||
|
||||
return exhaustive;
|
||||
}
|
||||
@ -248,58 +245,45 @@ struct TypeVarStringifier
|
||||
|
||||
void stringify(const std::vector<TypeId>& types, const std::vector<TypePackId>& typePacks)
|
||||
{
|
||||
if (types.size() == 0 && (!FFlag::LuauTypeAliasPacks || typePacks.size() == 0))
|
||||
if (types.size() == 0 && typePacks.size() == 0)
|
||||
return;
|
||||
|
||||
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size()))
|
||||
if (types.size() || typePacks.size())
|
||||
state.emit("<");
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
bool first = true;
|
||||
bool first = true;
|
||||
|
||||
for (TypeId ty : types)
|
||||
{
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
for (TypeId ty : types)
|
||||
{
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
first = false;
|
||||
|
||||
stringify(ty);
|
||||
}
|
||||
|
||||
bool singleTp = typePacks.size() == 1;
|
||||
|
||||
for (TypePackId tp : typePacks)
|
||||
{
|
||||
if (isEmpty(tp) && singleTp)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
else
|
||||
first = false;
|
||||
|
||||
stringify(ty);
|
||||
}
|
||||
if (!singleTp)
|
||||
state.emit("(");
|
||||
|
||||
bool singleTp = typePacks.size() == 1;
|
||||
stringify(tp);
|
||||
|
||||
for (TypePackId tp : typePacks)
|
||||
{
|
||||
if (isEmpty(tp) && singleTp)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
else
|
||||
first = false;
|
||||
|
||||
if (!singleTp)
|
||||
state.emit("(");
|
||||
|
||||
stringify(tp);
|
||||
|
||||
if (!singleTp)
|
||||
state.emit(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < types.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
state.emit(", ");
|
||||
|
||||
stringify(types[i]);
|
||||
}
|
||||
if (!singleTp)
|
||||
state.emit(")");
|
||||
}
|
||||
|
||||
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size()))
|
||||
if (types.size() || typePacks.size())
|
||||
state.emit(">");
|
||||
}
|
||||
|
||||
@ -767,12 +751,23 @@ struct TypePackStringifier
|
||||
else
|
||||
state.emit(", ");
|
||||
|
||||
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
|
||||
|
||||
if (!elemNames.empty() && elemNames[elemIndex])
|
||||
if (FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
if (elemIndex < elemNames.size() && elemNames[elemIndex])
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
|
||||
|
||||
if (!elemNames.empty() && elemNames[elemIndex])
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
}
|
||||
}
|
||||
elemIndex++;
|
||||
|
||||
@ -929,38 +924,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
|
||||
|
||||
result.name += ttv->name ? *ttv->name : *ttv->syntheticName;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ttv->instantiatedTypeParams.empty() && (!FFlag::LuauTypeAliasPacks || ttv->instantiatedTypePackParams.empty()))
|
||||
return result;
|
||||
|
||||
result.name += "<";
|
||||
|
||||
bool first = true;
|
||||
for (TypeId ty : ttv->instantiatedTypeParams)
|
||||
{
|
||||
if (!first)
|
||||
result.name += ", ";
|
||||
else
|
||||
first = false;
|
||||
|
||||
tvs.stringify(ty);
|
||||
}
|
||||
|
||||
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
|
||||
{
|
||||
result.truncated = true;
|
||||
result.name += "... <TRUNCATED>";
|
||||
}
|
||||
else
|
||||
{
|
||||
result.name += ">";
|
||||
}
|
||||
}
|
||||
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -1161,17 +1125,37 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
|
||||
s += ", ";
|
||||
first = false;
|
||||
|
||||
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (!ftv.argNames.empty())
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
s += toString_(*argPackIter);
|
||||
|
||||
++argPackIter;
|
||||
if (!ftv.argNames.empty())
|
||||
if (FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
LUAU_ASSERT(argNameIter != ftv.argNames.end());
|
||||
++argNameIter;
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (argNameIter != ftv.argNames.end())
|
||||
{
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
++argNameIter;
|
||||
}
|
||||
else
|
||||
{
|
||||
s += "_: ";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (!ftv.argNames.empty())
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
}
|
||||
|
||||
s += toString_(*argPackIter);
|
||||
++argPackIter;
|
||||
|
||||
if (!FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
if (!ftv.argNames.empty())
|
||||
{
|
||||
LUAU_ASSERT(argNameIter != ftv.argNames.end());
|
||||
++argNameIter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,6 @@
|
||||
#include <limits>
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace
|
||||
{
|
||||
bool isIdentifierStartChar(char c)
|
||||
@ -787,7 +785,7 @@ struct Printer
|
||||
|
||||
writer.keyword("type");
|
||||
writer.identifier(a->name.value);
|
||||
if (a->generics.size > 0 || (FFlag::LuauTypeAliasPacks && a->genericPacks.size > 0))
|
||||
if (a->generics.size > 0 || a->genericPacks.size > 0)
|
||||
{
|
||||
writer.symbol("<");
|
||||
CommaSeparatorInserter comma(writer);
|
||||
@ -798,14 +796,11 @@ struct Printer
|
||||
writer.identifier(o.value);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (auto o : a->genericPacks)
|
||||
{
|
||||
for (auto o : a->genericPacks)
|
||||
{
|
||||
comma();
|
||||
writer.identifier(o.value);
|
||||
writer.symbol("...");
|
||||
}
|
||||
comma();
|
||||
writer.identifier(o.value);
|
||||
writer.symbol("...");
|
||||
}
|
||||
|
||||
writer.symbol(">");
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauShareTxnSeen, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -36,11 +34,8 @@ void TxnLog::rollback()
|
||||
for (auto it = tableChanges.rbegin(); it != tableChanges.rend(); ++it)
|
||||
std::swap(it->first->boundTo, it->second);
|
||||
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
{
|
||||
LUAU_ASSERT(originalSeenSize <= sharedSeen->size());
|
||||
sharedSeen->resize(originalSeenSize);
|
||||
}
|
||||
LUAU_ASSERT(originalSeenSize <= sharedSeen->size());
|
||||
sharedSeen->resize(originalSeenSize);
|
||||
}
|
||||
|
||||
void TxnLog::concat(TxnLog rhs)
|
||||
@ -53,45 +48,25 @@ void TxnLog::concat(TxnLog rhs)
|
||||
|
||||
tableChanges.insert(tableChanges.end(), rhs.tableChanges.begin(), rhs.tableChanges.end());
|
||||
rhs.tableChanges.clear();
|
||||
|
||||
if (!FFlag::LuauShareTxnSeen)
|
||||
{
|
||||
ownedSeen.swap(rhs.ownedSeen);
|
||||
rhs.ownedSeen.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
|
||||
else
|
||||
return (ownedSeen.end() != std::find(ownedSeen.begin(), ownedSeen.end(), sortedPair));
|
||||
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
|
||||
}
|
||||
|
||||
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
sharedSeen->push_back(sortedPair);
|
||||
else
|
||||
ownedSeen.push_back(sortedPair);
|
||||
sharedSeen->push_back(sortedPair);
|
||||
}
|
||||
|
||||
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
{
|
||||
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
||||
sharedSeen->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(sortedPair == ownedSeen.back());
|
||||
ownedSeen.pop_back();
|
||||
}
|
||||
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
||||
sharedSeen->pop_back();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -13,8 +13,6 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||
{
|
||||
char* result = (char*)allocator.allocate(contents.size() + 1);
|
||||
@ -131,12 +129,9 @@ public:
|
||||
parameters.data[i] = {Luau::visit(*this, ttv.instantiatedTypeParams[i]->ty), {}};
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
|
||||
{
|
||||
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
|
||||
{
|
||||
parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])};
|
||||
}
|
||||
parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])};
|
||||
}
|
||||
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName(ttv.name->c_str()), parameters.size != 0, parameters);
|
||||
@ -250,20 +245,7 @@ public:
|
||||
|
||||
AstTypePack* argTailAnnotation = nullptr;
|
||||
if (argTail)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
argTailAnnotation = rehydrate(*argTail);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypePackId tail = *argTail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
argTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
argTailAnnotation = rehydrate(*argTail);
|
||||
|
||||
AstArray<std::optional<AstArgumentName>> argNames;
|
||||
argNames.size = ftv.argNames.size();
|
||||
@ -292,20 +274,7 @@ public:
|
||||
|
||||
AstTypePack* retTailAnnotation = nullptr;
|
||||
if (retTail)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
retTailAnnotation = rehydrate(*retTail);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypePackId tail = *retTail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
retTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
retTailAnnotation = rehydrate(*retTail);
|
||||
|
||||
return allocator->alloc<AstTypeFunction>(
|
||||
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation});
|
||||
@ -518,18 +487,7 @@ public:
|
||||
const auto& [v, tail] = flatten(ret);
|
||||
|
||||
if (tail)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypePackId tailPack = *tail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tailPack))
|
||||
variadicAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), typeAst(vtp->ty));
|
||||
}
|
||||
}
|
||||
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
|
||||
|
||||
fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation};
|
||||
}
|
||||
|
@ -23,22 +23,20 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 500)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
|
||||
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClassPropertyAccessAsString, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStrictRequire, false)
|
||||
LUAU_FASTFLAG(LuauSubstitutionDontReplaceIgnoredTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
|
||||
LUAU_FASTFLAG(LuauNewRequireTrace2)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTailArgumentTypeInfo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauModuleRequireErrorPack, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -562,12 +560,6 @@ ErrorVec TypeChecker::canUnify(TypePackId left, TypePackId right, const Location
|
||||
return canUnify_(left, right, location);
|
||||
}
|
||||
|
||||
ErrorVec TypeChecker::canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId superTy, TypeId subTy, const Location& location)
|
||||
{
|
||||
Unifier state = mkUnifier(seen, location);
|
||||
return state.canUnify(superTy, subTy);
|
||||
}
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec TypeChecker::canUnify_(Id superTy, Id subTy, const Location& location)
|
||||
{
|
||||
@ -1152,61 +1144,20 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
Location location = scope->typeAliasLocations[name];
|
||||
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
||||
else
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, errorRecoveryType(anyType)};
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
||||
}
|
||||
else
|
||||
{
|
||||
ScopePtr aliasScope =
|
||||
FFlag::LuauQuantifyInPlace2 ? childScope(scope, typealias.location, subLevel) : childScope(scope, typealias.location);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks);
|
||||
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks);
|
||||
|
||||
TypeId ty = freshType(aliasScope);
|
||||
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> generics;
|
||||
for (AstName generic : typealias.generics)
|
||||
{
|
||||
Name n = generic.value;
|
||||
|
||||
// These generics are the only thing that will ever be added to aliasScope, so we can be certain that
|
||||
// a collision can only occur when two generic typevars have the same name.
|
||||
if (aliasScope->privateTypeBindings.end() != aliasScope->privateTypeBindings.find(n))
|
||||
{
|
||||
// TODO(jhuelsman): report the exact span of the generic type parameter whose name is a duplicate.
|
||||
reportError(TypeError{typealias.location, DuplicateGenericParameter{n}});
|
||||
}
|
||||
|
||||
TypeId g;
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction)
|
||||
{
|
||||
TypeId& cached = scope->typeAliasTypeParameters[n];
|
||||
if (!cached)
|
||||
cached = addType(GenericTypeVar{aliasScope->level, n});
|
||||
g = cached;
|
||||
}
|
||||
else
|
||||
g = addType(GenericTypeVar{aliasScope->level, n});
|
||||
generics.push_back(g);
|
||||
aliasScope->privateTypeBindings[n] = TypeFun{{}, g};
|
||||
}
|
||||
|
||||
TypeId ty = freshType(aliasScope);
|
||||
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
bindingsMap[name] = {std::move(generics), ty};
|
||||
}
|
||||
TypeId ty = freshType(aliasScope);
|
||||
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1223,14 +1174,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, ty};
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (TypePackId tp : binding->typePackParams)
|
||||
{
|
||||
for (TypePackId tp : binding->typePackParams)
|
||||
{
|
||||
auto generic = get<GenericTypePack>(tp);
|
||||
LUAU_ASSERT(generic);
|
||||
aliasScope->privateTypePackBindings[generic->name] = tp;
|
||||
}
|
||||
auto generic = get<GenericTypePack>(tp);
|
||||
LUAU_ASSERT(generic);
|
||||
aliasScope->privateTypePackBindings[generic->name] = tp;
|
||||
}
|
||||
|
||||
TypeId ty = resolveType(aliasScope, *typealias.type);
|
||||
@ -1241,19 +1189,16 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
{
|
||||
// Copy can be skipped if this is an identical alias
|
||||
if (ttv->name != name || ttv->instantiatedTypeParams != binding->typeParams ||
|
||||
(FFlag::LuauTypeAliasPacks && ttv->instantiatedTypePackParams != binding->typePackParams))
|
||||
ttv->instantiatedTypePackParams != binding->typePackParams)
|
||||
{
|
||||
// This is a shallow clone, original recursive links to self are not updated
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
|
||||
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
|
||||
clone.name = name;
|
||||
clone.instantiatedTypeParams = binding->typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
clone.instantiatedTypePackParams = binding->typePackParams;
|
||||
clone.instantiatedTypePackParams = binding->typePackParams;
|
||||
|
||||
ty = addType(std::move(clone));
|
||||
}
|
||||
@ -1262,9 +1207,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
{
|
||||
ttv->name = name;
|
||||
ttv->instantiatedTypeParams = binding->typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
ttv->instantiatedTypePackParams = binding->typePackParams;
|
||||
ttv->instantiatedTypePackParams = binding->typePackParams;
|
||||
}
|
||||
}
|
||||
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
|
||||
@ -1289,7 +1232,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
|
||||
}
|
||||
|
||||
// We don't have generic classes, so this assertion _should_ never be hit.
|
||||
LUAU_ASSERT(lookupType->typeParams.size() == 0 && (!FFlag::LuauTypeAliasPacks || lookupType->typePackParams.size() == 0));
|
||||
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
|
||||
superTy = lookupType->type;
|
||||
|
||||
if (!get<ClassTypeVar>(follow(*superTy)))
|
||||
@ -1851,6 +1794,24 @@ TypeId TypeChecker::checkExprTable(
|
||||
if (isNonstrictMode() && !getTableType(exprType) && !get<FunctionTypeVar>(exprType))
|
||||
exprType = anyType;
|
||||
|
||||
if (FFlag::LuauPropertiesGetExpectedType && expectedTable)
|
||||
{
|
||||
auto it = expectedTable->props.find(key->value.data);
|
||||
if (it != expectedTable->props.end())
|
||||
{
|
||||
Property expectedProp = it->second;
|
||||
ErrorVec errors = tryUnify(expectedProp.type, exprType, k->location);
|
||||
if (errors.empty())
|
||||
exprType = expectedProp.type;
|
||||
}
|
||||
else if (expectedTable->indexer && isString(expectedTable->indexer->indexType))
|
||||
{
|
||||
ErrorVec errors = tryUnify(expectedTable->indexer->indexResultType, exprType, k->location);
|
||||
if (errors.empty())
|
||||
exprType = expectedTable->indexer->indexResultType;
|
||||
}
|
||||
}
|
||||
|
||||
props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location};
|
||||
}
|
||||
else
|
||||
@ -3744,17 +3705,29 @@ ExprResult<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const L
|
||||
for (size_t i = 0; i < exprs.size; ++i)
|
||||
{
|
||||
AstExpr* expr = exprs.data[i];
|
||||
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
|
||||
|
||||
if (i == lastIndex && (expr->is<AstExprCall>() || expr->is<AstExprVarargs>()))
|
||||
{
|
||||
auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
|
||||
insert(exprPredicates);
|
||||
|
||||
if (FFlag::LuauTailArgumentTypeInfo)
|
||||
{
|
||||
if (std::optional<TypeId> firstTy = first(typePack))
|
||||
{
|
||||
if (!currentModule->astTypes.find(expr))
|
||||
currentModule->astTypes[expr] = follow(*firstTy);
|
||||
}
|
||||
|
||||
if (expectedType)
|
||||
currentModule->astExpectedTypes[expr] = *expectedType;
|
||||
}
|
||||
|
||||
tp->tail = typePack;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
|
||||
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
|
||||
insert(exprPredicates);
|
||||
|
||||
@ -3797,7 +3770,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
||||
LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker");
|
||||
LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str());
|
||||
|
||||
if (FFlag::LuauNewRequireTrace2 && moduleInfo.name.empty())
|
||||
if (moduleInfo.name.empty())
|
||||
{
|
||||
if (FFlag::LuauStrictRequire && currentModule->mode == Mode::Strict)
|
||||
{
|
||||
@ -3814,7 +3787,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
||||
// There are two reasons why we might fail to find the module:
|
||||
// either the file does not exist or there's a cycle. If there's a cycle
|
||||
// we will already have reported the error.
|
||||
if (!resolver->moduleExists(moduleInfo.name) && (FFlag::LuauTraceRequireLookupChild ? !moduleInfo.optional : true))
|
||||
if (!resolver->moduleExists(moduleInfo.name) && !moduleInfo.optional)
|
||||
{
|
||||
std::string reportedModulePath = resolver->getHumanReadableModuleName(moduleInfo.name);
|
||||
reportError(TypeError{location, UnknownRequire{reportedModulePath}});
|
||||
@ -3830,7 +3803,12 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
std::optional<TypeId> moduleType = first(module->getModuleScope()->returnType);
|
||||
TypePackId modulePack = module->getModuleScope()->returnType;
|
||||
|
||||
if (FFlag::LuauModuleRequireErrorPack && get<Unifiable::Error>(modulePack))
|
||||
return errorRecoveryType(scope);
|
||||
|
||||
std::optional<TypeId> moduleType = first(modulePack);
|
||||
if (!moduleType)
|
||||
{
|
||||
std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name);
|
||||
@ -3840,7 +3818,8 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks);
|
||||
CloneState cloneState;
|
||||
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeChecker::tablify(TypeId type)
|
||||
@ -4326,11 +4305,6 @@ Unifier TypeChecker::mkUnifier(const Location& location)
|
||||
return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState};
|
||||
}
|
||||
|
||||
Unifier TypeChecker::mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location)
|
||||
{
|
||||
return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, seen, location, Variance::Covariant, unifierState};
|
||||
}
|
||||
|
||||
TypeId TypeChecker::freshType(const ScopePtr& scope)
|
||||
{
|
||||
return freshType(scope->level);
|
||||
@ -4477,117 +4451,82 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
if (lit->parameters.size == 0 && tf->typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf->typePackParams.empty()))
|
||||
{
|
||||
if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty())
|
||||
return tf->type;
|
||||
}
|
||||
else if (!FFlag::LuauTypeAliasPacks && lit->parameters.size != tf->typeParams.size())
|
||||
|
||||
if (!lit->hasParameterList && !tf->typePackParams.empty())
|
||||
{
|
||||
reportError(TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, lit->parameters.size, 0}});
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
|
||||
if (!FFlag::LuauErrorRecoveryType)
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
std::vector<TypeId> typeParams;
|
||||
std::vector<TypeId> extraTypes;
|
||||
std::vector<TypePackId> typePackParams;
|
||||
|
||||
for (size_t i = 0; i < lit->parameters.size; ++i)
|
||||
{
|
||||
if (!lit->hasParameterList && !tf->typePackParams.empty())
|
||||
if (AstType* type = lit->parameters.data[i].type)
|
||||
{
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
|
||||
if (!FFlag::LuauErrorRecoveryType)
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
TypeId ty = resolveType(scope, *type);
|
||||
|
||||
std::vector<TypeId> typeParams;
|
||||
std::vector<TypeId> extraTypes;
|
||||
std::vector<TypePackId> typePackParams;
|
||||
|
||||
for (size_t i = 0; i < lit->parameters.size; ++i)
|
||||
{
|
||||
if (AstType* type = lit->parameters.data[i].type)
|
||||
{
|
||||
TypeId ty = resolveType(scope, *type);
|
||||
|
||||
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
|
||||
typeParams.push_back(ty);
|
||||
else if (typePackParams.empty())
|
||||
extraTypes.push_back(ty);
|
||||
else
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
|
||||
}
|
||||
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
|
||||
{
|
||||
TypePackId tp = resolveTypePack(scope, *typePack);
|
||||
|
||||
// If we have collected an implicit type pack, materialize it
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we need more regular types, we can use single element type packs to fill those in
|
||||
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
|
||||
typeParams.push_back(*first(tp));
|
||||
else
|
||||
typePackParams.push_back(tp);
|
||||
}
|
||||
}
|
||||
|
||||
// If we still haven't meterialized an implicit type pack, do it now
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
|
||||
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
|
||||
typePackParams.push_back(addTypePack({}));
|
||||
|
||||
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
|
||||
{
|
||||
reportError(
|
||||
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
|
||||
|
||||
if (FFlag::LuauErrorRecoveryType)
|
||||
{
|
||||
// Pad the types out with error recovery types
|
||||
while (typeParams.size() < tf->typeParams.size())
|
||||
typeParams.push_back(errorRecoveryType(scope));
|
||||
while (typePackParams.size() < tf->typePackParams.size())
|
||||
typePackParams.push_back(errorRecoveryTypePack(scope));
|
||||
}
|
||||
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
|
||||
typeParams.push_back(ty);
|
||||
else if (typePackParams.empty())
|
||||
extraTypes.push_back(ty);
|
||||
else
|
||||
return errorRecoveryType(scope);
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
|
||||
}
|
||||
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
|
||||
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
|
||||
{
|
||||
// If the generic parameters and the type arguments are the same, we are about to
|
||||
// perform an identity substitution, which we can just short-circuit.
|
||||
return tf->type;
|
||||
TypePackId tp = resolveTypePack(scope, *typePack);
|
||||
|
||||
// If we have collected an implicit type pack, materialize it
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we need more regular types, we can use single element type packs to fill those in
|
||||
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
|
||||
typeParams.push_back(*first(tp));
|
||||
else
|
||||
typePackParams.push_back(tp);
|
||||
}
|
||||
|
||||
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> typeParams;
|
||||
|
||||
for (const auto& param : lit->parameters)
|
||||
typeParams.push_back(resolveType(scope, *param.type));
|
||||
// If we still haven't meterialized an implicit type pack, do it now
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
|
||||
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
|
||||
typePackParams.push_back(addTypePack({}));
|
||||
|
||||
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
|
||||
{
|
||||
reportError(
|
||||
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
|
||||
|
||||
if (FFlag::LuauErrorRecoveryType)
|
||||
{
|
||||
// If there aren't enough type parameters, pad them out with error recovery types
|
||||
// (we've already reported the error)
|
||||
while (typeParams.size() < lit->parameters.size)
|
||||
// Pad the types out with error recovery types
|
||||
while (typeParams.size() < tf->typeParams.size())
|
||||
typeParams.push_back(errorRecoveryType(scope));
|
||||
while (typePackParams.size() < tf->typePackParams.size())
|
||||
typePackParams.push_back(errorRecoveryTypePack(scope));
|
||||
}
|
||||
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams)
|
||||
{
|
||||
// If the generic parameters and the type arguments are the same, we are about to
|
||||
// perform an identity substitution, which we can just short-circuit.
|
||||
return tf->type;
|
||||
}
|
||||
|
||||
return instantiateTypeFun(scope, *tf, typeParams, {}, annotation.location);
|
||||
else
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
|
||||
{
|
||||
// If the generic parameters and the type arguments are the same, we are about to
|
||||
// perform an identity substitution, which we can just short-circuit.
|
||||
return tf->type;
|
||||
}
|
||||
|
||||
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
|
||||
}
|
||||
else if (const auto& table = annotation.as<AstTypeTable>())
|
||||
{
|
||||
@ -4757,7 +4696,7 @@ bool ApplyTypeFunction::isDirty(TypePackId tp)
|
||||
|
||||
bool ApplyTypeFunction::ignoreChildren(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypeVar>(ty))
|
||||
if (get<GenericTypeVar>(ty))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
@ -4765,7 +4704,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
|
||||
|
||||
bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypePack>(tp))
|
||||
if (get<GenericTypePack>(tp))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
@ -4788,36 +4727,26 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp)
|
||||
// Really this should just replace the arguments,
|
||||
// but for bug-compatibility with existing code, we replace
|
||||
// all generics by free type variables.
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
TypePackId& arg = typePackArguments[tp];
|
||||
if (arg)
|
||||
return arg;
|
||||
else
|
||||
return addTypePack(FreeTypePack{level});
|
||||
}
|
||||
TypePackId& arg = typePackArguments[tp];
|
||||
if (arg)
|
||||
return arg;
|
||||
else
|
||||
{
|
||||
return addTypePack(FreeTypePack{level});
|
||||
}
|
||||
}
|
||||
|
||||
TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& typePackParams, const Location& location)
|
||||
{
|
||||
if (tf.typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf.typePackParams.empty()))
|
||||
if (tf.typeParams.empty() && tf.typePackParams.empty())
|
||||
return tf.type;
|
||||
|
||||
applyTypeFunction.typeArguments.clear();
|
||||
for (size_t i = 0; i < tf.typeParams.size(); ++i)
|
||||
applyTypeFunction.typeArguments[tf.typeParams[i]] = typeParams[i];
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
applyTypeFunction.typePackArguments.clear();
|
||||
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
|
||||
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
|
||||
}
|
||||
applyTypeFunction.typePackArguments.clear();
|
||||
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
|
||||
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
|
||||
|
||||
applyTypeFunction.currentModule = currentModule;
|
||||
applyTypeFunction.level = scope->level;
|
||||
@ -4866,9 +4795,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
||||
if (ttv)
|
||||
{
|
||||
ttv->instantiatedTypeParams = typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -4884,9 +4811,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
||||
}
|
||||
|
||||
ttv->instantiatedTypeParams = typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4914,7 +4839,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
|
||||
}
|
||||
|
||||
TypeId g;
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks)
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction)
|
||||
{
|
||||
TypeId& cached = scope->parent->typeAliasTypeParameters[n];
|
||||
if (!cached)
|
||||
@ -4944,7 +4869,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
|
||||
}
|
||||
|
||||
TypePackId g;
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks)
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction)
|
||||
{
|
||||
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
|
||||
if (!cached)
|
||||
@ -5245,7 +5170,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
|
||||
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
|
||||
|
||||
auto typeFun = globalScope->lookupType(typeguardP.kind);
|
||||
if (!typeFun || !typeFun->typeParams.empty() || (FFlag::LuauTypeAliasPacks && !typeFun->typePackParams.empty()))
|
||||
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
|
||||
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
|
||||
|
||||
TypeId type = follow(typeFun->type);
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false)
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||
|
||||
@ -739,369 +738,6 @@ void persist(TypePackId tp)
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct StateDot
|
||||
{
|
||||
StateDot(ToDotOptions opts)
|
||||
: opts(opts)
|
||||
{
|
||||
}
|
||||
|
||||
ToDotOptions opts;
|
||||
|
||||
std::unordered_set<TypeId> seenTy;
|
||||
std::unordered_set<TypePackId> seenTp;
|
||||
std::unordered_map<TypeId, int> tyToIndex;
|
||||
std::unordered_map<TypePackId, int> tpToIndex;
|
||||
int nextIndex = 1;
|
||||
std::string result;
|
||||
|
||||
bool canDuplicatePrimitive(TypeId ty);
|
||||
|
||||
void visitChildren(TypeId ty, int index);
|
||||
void visitChildren(TypePackId ty, int index);
|
||||
|
||||
void visitChild(TypeId ty, int parentIndex, const char* linkName = nullptr);
|
||||
void visitChild(TypePackId tp, int parentIndex, const char* linkName = nullptr);
|
||||
|
||||
void startNode(int index);
|
||||
void finishNode();
|
||||
|
||||
void startNodeLabel();
|
||||
void finishNodeLabel(TypeId ty);
|
||||
void finishNodeLabel(TypePackId tp);
|
||||
};
|
||||
|
||||
bool StateDot::canDuplicatePrimitive(TypeId ty)
|
||||
{
|
||||
if (get<BoundTypeVar>(ty))
|
||||
return false;
|
||||
|
||||
return get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty);
|
||||
}
|
||||
|
||||
void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName)
|
||||
{
|
||||
if (!tyToIndex.count(ty) || (opts.duplicatePrimitives && canDuplicatePrimitive(ty)))
|
||||
tyToIndex[ty] = nextIndex++;
|
||||
|
||||
int index = tyToIndex[ty];
|
||||
|
||||
if (parentIndex != 0)
|
||||
{
|
||||
if (linkName)
|
||||
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, index, linkName);
|
||||
else
|
||||
formatAppend(result, "n%d -> n%d;\n", parentIndex, index);
|
||||
}
|
||||
|
||||
if (opts.duplicatePrimitives && canDuplicatePrimitive(ty))
|
||||
{
|
||||
if (get<PrimitiveTypeVar>(ty))
|
||||
formatAppend(result, "n%d [label=\"%s\"];\n", index, toStringDetailed(ty, {}).name.c_str());
|
||||
else if (get<AnyTypeVar>(ty))
|
||||
formatAppend(result, "n%d [label=\"any\"];\n", index);
|
||||
}
|
||||
else
|
||||
{
|
||||
visitChildren(ty, index);
|
||||
}
|
||||
}
|
||||
|
||||
void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName)
|
||||
{
|
||||
if (!tpToIndex.count(tp))
|
||||
tpToIndex[tp] = nextIndex++;
|
||||
|
||||
if (linkName)
|
||||
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName);
|
||||
else
|
||||
formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]);
|
||||
|
||||
visitChildren(tp, tpToIndex[tp]);
|
||||
}
|
||||
|
||||
void StateDot::startNode(int index)
|
||||
{
|
||||
formatAppend(result, "n%d [", index);
|
||||
}
|
||||
|
||||
void StateDot::finishNode()
|
||||
{
|
||||
formatAppend(result, "];\n");
|
||||
}
|
||||
|
||||
void StateDot::startNodeLabel()
|
||||
{
|
||||
formatAppend(result, "label=\"");
|
||||
}
|
||||
|
||||
void StateDot::finishNodeLabel(TypeId ty)
|
||||
{
|
||||
if (opts.showPointers)
|
||||
formatAppend(result, "\n0x%p", ty);
|
||||
// additional common attributes can be added here as well
|
||||
result += "\"";
|
||||
}
|
||||
|
||||
void StateDot::finishNodeLabel(TypePackId tp)
|
||||
{
|
||||
if (opts.showPointers)
|
||||
formatAppend(result, "\n0x%p", tp);
|
||||
// additional common attributes can be added here as well
|
||||
result += "\"";
|
||||
}
|
||||
|
||||
void StateDot::visitChildren(TypeId ty, int index)
|
||||
{
|
||||
if (seenTy.count(ty))
|
||||
return;
|
||||
seenTy.insert(ty);
|
||||
|
||||
startNode(index);
|
||||
startNodeLabel();
|
||||
|
||||
if (const BoundTypeVar* btv = get<BoundTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "BoundTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(btv->boundTo, index);
|
||||
}
|
||||
else if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "FunctionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(ftv->argTypes, index, "arg");
|
||||
visitChild(ftv->retType, index, "ret");
|
||||
}
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
if (ttv->name)
|
||||
formatAppend(result, "TableTypeVar %s", ttv->name->c_str());
|
||||
else if (ttv->syntheticName)
|
||||
formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str());
|
||||
else
|
||||
formatAppend(result, "TableTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
if (ttv->boundTo)
|
||||
return visitChild(*ttv->boundTo, index, "boundTo");
|
||||
|
||||
for (const auto& [name, prop] : ttv->props)
|
||||
visitChild(prop.type, index, name.c_str());
|
||||
if (ttv->indexer)
|
||||
{
|
||||
visitChild(ttv->indexer->indexType, index, "[index]");
|
||||
visitChild(ttv->indexer->indexResultType, index, "[value]");
|
||||
}
|
||||
for (TypeId itp : ttv->instantiatedTypeParams)
|
||||
visitChild(itp, index, "typeParam");
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp, index, "typePackParam");
|
||||
}
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "MetatableTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(mtv->table, index, "table");
|
||||
visitChild(mtv->metatable, index, "metatable");
|
||||
}
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "UnionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (TypeId opt : utv->options)
|
||||
visitChild(opt, index);
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "IntersectionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (TypeId part : itv->parts)
|
||||
visitChild(part, index);
|
||||
}
|
||||
else if (const GenericTypeVar* gtv = get<GenericTypeVar>(ty))
|
||||
{
|
||||
if (gtv->explicitName)
|
||||
formatAppend(result, "GenericTypeVar %s", gtv->name.c_str());
|
||||
else
|
||||
formatAppend(result, "GenericTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "FreeTypeVar %d", ftv->index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<AnyTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "AnyTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<PrimitiveTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str());
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<ErrorTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "ErrorTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "ClassTypeVar %s", ctv->name.c_str());
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
visitChild(prop.type, index, name.c_str());
|
||||
|
||||
if (ctv->parent)
|
||||
visitChild(*ctv->parent, index, "[parent]");
|
||||
|
||||
if (ctv->metatable)
|
||||
visitChild(*ctv->metatable, index, "[metatable]");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"unknown type kind");
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
}
|
||||
|
||||
void StateDot::visitChildren(TypePackId tp, int index)
|
||||
{
|
||||
if (seenTp.count(tp))
|
||||
return;
|
||||
seenTp.insert(tp);
|
||||
|
||||
startNode(index);
|
||||
startNodeLabel();
|
||||
|
||||
if (const BoundTypePack* btp = get<BoundTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "BoundTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
visitChild(btp->boundTo, index);
|
||||
}
|
||||
else if (const TypePack* tpp = get<TypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "TypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
for (TypeId tv : tpp->head)
|
||||
visitChild(tv, index);
|
||||
if (tpp->tail)
|
||||
visitChild(*tpp->tail, index, "tail");
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "VariadicTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
visitChild(vtp->ty, index);
|
||||
}
|
||||
else if (const FreeTypePack* ftp = get<FreeTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "FreeTypePack %d", ftp->index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else if (const GenericTypePack* gtp = get<GenericTypePack>(tp))
|
||||
{
|
||||
if (gtp->explicitName)
|
||||
formatAppend(result, "GenericTypePack %s", gtp->name.c_str());
|
||||
else
|
||||
formatAppend(result, "GenericTypePack %d", gtp->index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<Unifiable::Error>(tp))
|
||||
{
|
||||
formatAppend(result, "ErrorTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"unknown type pack kind");
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts)
|
||||
{
|
||||
StateDot state{opts};
|
||||
|
||||
state.result = "digraph graphname {\n";
|
||||
state.visitChild(ty, 0);
|
||||
state.result += "}";
|
||||
|
||||
return state.result;
|
||||
}
|
||||
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts)
|
||||
{
|
||||
StateDot state{opts};
|
||||
|
||||
state.result = "digraph graphname {\n";
|
||||
state.visitChild(tp, 0);
|
||||
state.result += "}";
|
||||
|
||||
return state.result;
|
||||
}
|
||||
|
||||
std::string toDot(TypeId ty)
|
||||
{
|
||||
return toDot(ty, {});
|
||||
}
|
||||
|
||||
std::string toDot(TypePackId tp)
|
||||
{
|
||||
return toDot(tp, {});
|
||||
}
|
||||
|
||||
void dumpDot(TypeId ty)
|
||||
{
|
||||
printf("%s\n", toDot(ty).c_str());
|
||||
}
|
||||
|
||||
void dumpDot(TypePackId tp)
|
||||
{
|
||||
printf("%s\n", toDot(tp).c_str());
|
||||
}
|
||||
|
||||
const TypeLevel* getLevel(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
@ -18,9 +18,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckOpts, false)
|
||||
LUAU_FASTFLAG(LuauShareTxnSeen);
|
||||
LUAU_FASTFLAGVARIABLE(LuauCacheUnifyTableResults, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false)
|
||||
LUAU_FASTFLAG(LuauSingletonTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false)
|
||||
@ -136,38 +133,19 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Locati
|
||||
, globalScope(std::move(globalScope))
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, counters(&countersData)
|
||||
, counters_DEPRECATED(std::make_shared<UnifierCounters>())
|
||||
, sharedState(sharedState)
|
||||
{
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& ownedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters)
|
||||
: types(types)
|
||||
, mode(mode)
|
||||
, globalScope(std::move(globalScope))
|
||||
, log(ownedSeen)
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, counters(counters ? counters : &countersData)
|
||||
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
|
||||
, sharedState(sharedState)
|
||||
{
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters)
|
||||
Variance variance, UnifierSharedState& sharedState)
|
||||
: types(types)
|
||||
, mode(mode)
|
||||
, globalScope(std::move(globalScope))
|
||||
, log(sharedSeen)
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, counters(counters ? counters : &countersData)
|
||||
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
|
||||
, sharedState(sharedState)
|
||||
{
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
@ -175,26 +153,18 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<
|
||||
|
||||
void Unifier::tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
counters->iterationCount = 0;
|
||||
else
|
||||
counters_DEPRECATED->iterationCount = 0;
|
||||
sharedState.counters.iterationCount = 0;
|
||||
|
||||
tryUnify_(superTy, subTy, isFunctionCall, isIntersection);
|
||||
}
|
||||
|
||||
void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
|
||||
{
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
++counters->iterationCount;
|
||||
else
|
||||
++counters_DEPRECATED->iterationCount;
|
||||
++sharedState.counters.iterationCount;
|
||||
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 &&
|
||||
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
|
||||
{
|
||||
errors.push_back(TypeError{location, UnificationTooComplex{}});
|
||||
return;
|
||||
@ -302,7 +272,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
||||
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
|
||||
return tryUnifyWithAny(subTy, superTy);
|
||||
|
||||
bool cacheEnabled = FFlag::LuauCacheUnifyTableResults && !isFunctionCall && !isIntersection;
|
||||
bool cacheEnabled = !isFunctionCall && !isIntersection;
|
||||
auto& cache = sharedState.cachedUnify;
|
||||
|
||||
// What if the types are immutable and we proved their relation before
|
||||
@ -563,8 +533,6 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
||||
|
||||
void Unifier::cacheResult(TypeId superTy, TypeId subTy)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCacheUnifyTableResults);
|
||||
|
||||
bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
|
||||
|
||||
if (superTyInfo && *superTyInfo)
|
||||
@ -686,10 +654,7 @@ ErrorVec Unifier::canUnify(TypePackId superTy, TypePackId subTy, bool isFunction
|
||||
|
||||
void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
counters->iterationCount = 0;
|
||||
else
|
||||
counters_DEPRECATED->iterationCount = 0;
|
||||
sharedState.counters.iterationCount = 0;
|
||||
|
||||
tryUnify_(superTp, subTp, isFunctionCall);
|
||||
}
|
||||
@ -700,16 +665,11 @@ void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall
|
||||
*/
|
||||
void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
|
||||
{
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
++counters->iterationCount;
|
||||
else
|
||||
++counters_DEPRECATED->iterationCount;
|
||||
++sharedState.counters.iterationCount;
|
||||
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 &&
|
||||
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
|
||||
{
|
||||
errors.push_back(TypeError{location, UnificationTooComplex{}});
|
||||
return;
|
||||
@ -1727,39 +1687,8 @@ void Unifier::tryUnify(const TableIndexer& superIndexer, const TableIndexer& sub
|
||||
tryUnify_(superIndexer.indexResultType, subIndexer.indexResultType);
|
||||
}
|
||||
|
||||
static void queueTypePack_DEPRECATED(
|
||||
std::vector<TypeId>& queue, std::unordered_set<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTypecheckOpts);
|
||||
|
||||
while (true)
|
||||
{
|
||||
a = follow(a);
|
||||
|
||||
if (seenTypePacks.count(a))
|
||||
break;
|
||||
seenTypePacks.insert(a);
|
||||
|
||||
if (get<Unifiable::Free>(a))
|
||||
{
|
||||
state.log(a);
|
||||
*asMutable(a) = Unifiable::Bound{anyTypePack};
|
||||
}
|
||||
else if (auto tp = get<TypePack>(a))
|
||||
{
|
||||
queue.insert(queue.end(), tp->head.begin(), tp->head.end());
|
||||
if (tp->tail)
|
||||
a = *tp->tail;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
|
||||
|
||||
while (true)
|
||||
{
|
||||
a = follow(a);
|
||||
@ -1837,66 +1766,9 @@ void Unifier::tryUnifyVariadics(TypePackId superTp, TypePackId subTp, bool rever
|
||||
}
|
||||
}
|
||||
|
||||
static void tryUnifyWithAny_DEPRECATED(
|
||||
std::vector<TypeId>& queue, Unifier& state, std::unordered_set<TypePackId>& seenTypePacks, TypeId anyType, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTypecheckOpts);
|
||||
|
||||
std::unordered_set<TypeId> seen;
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
TypeId ty = follow(queue.back());
|
||||
queue.pop_back();
|
||||
if (seen.count(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
|
||||
if (get<FreeTypeVar>(ty))
|
||||
{
|
||||
state.log(ty);
|
||||
*asMutable(ty) = BoundTypeVar{anyType};
|
||||
}
|
||||
else if (auto fun = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->argTypes, anyTypePack);
|
||||
queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->retType, anyTypePack);
|
||||
}
|
||||
else if (auto table = get<TableTypeVar>(ty))
|
||||
{
|
||||
for (const auto& [_name, prop] : table->props)
|
||||
queue.push_back(prop.type);
|
||||
|
||||
if (table->indexer)
|
||||
{
|
||||
queue.push_back(table->indexer->indexType);
|
||||
queue.push_back(table->indexer->indexResultType);
|
||||
}
|
||||
}
|
||||
else if (auto mt = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
queue.push_back(mt->table);
|
||||
queue.push_back(mt->metatable);
|
||||
}
|
||||
else if (get<ClassTypeVar>(ty))
|
||||
{
|
||||
// ClassTypeVars never contain free typevars.
|
||||
}
|
||||
else if (auto union_ = get<UnionTypeVar>(ty))
|
||||
queue.insert(queue.end(), union_->options.begin(), union_->options.end());
|
||||
else if (auto intersection = get<IntersectionTypeVar>(ty))
|
||||
queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end());
|
||||
else
|
||||
{
|
||||
} // Primitives, any, errors, and generics are left untouched.
|
||||
}
|
||||
}
|
||||
|
||||
static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
|
||||
TypeId anyType, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
TypeId ty = follow(queue.back());
|
||||
@ -1949,43 +1821,20 @@ void Unifier::tryUnifyWithAny(TypeId any, TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(get<AnyTypeVar>(any) || get<ErrorTypeVar>(any));
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
// These types are not visited in general loop below
|
||||
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
|
||||
return;
|
||||
}
|
||||
// These types are not visited in general loop below
|
||||
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
|
||||
return;
|
||||
|
||||
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes.anyType}});
|
||||
|
||||
const TypePackId anyTP = get<AnyTypeVar>(any) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
std::vector<TypeId> queue = {ty};
|
||||
std::vector<TypeId> queue = {ty};
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempSeenTy_DEPRECATED.clear();
|
||||
tempSeenTp_DEPRECATED.clear();
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, singletonTypes.anyType, anyTP);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unordered_set<TypePackId> seenTypePacks;
|
||||
std::vector<TypeId> queue = {ty};
|
||||
|
||||
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, singletonTypes.anyType, anyTP);
|
||||
}
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP);
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
|
||||
@ -1994,38 +1843,14 @@ void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
|
||||
|
||||
const TypeId anyTy = singletonTypes.errorRecoveryType();
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
std::vector<TypeId> queue;
|
||||
std::vector<TypeId> queue;
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any);
|
||||
queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any);
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempSeenTy_DEPRECATED.clear();
|
||||
tempSeenTp_DEPRECATED.clear();
|
||||
|
||||
queueTypePack(queue, tempSeenTp_DEPRECATED, *this, ty, any);
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, anyTy, any);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unordered_set<TypePackId> seenTypePacks;
|
||||
std::vector<TypeId> queue;
|
||||
|
||||
queueTypePack_DEPRECATED(queue, seenTypePacks, *this, ty, any);
|
||||
|
||||
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, anyTy, any);
|
||||
}
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any);
|
||||
}
|
||||
|
||||
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
|
||||
@ -2035,46 +1860,22 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
|
||||
|
||||
void Unifier::occursCheck(TypeId needle, TypeId haystack)
|
||||
{
|
||||
std::unordered_set<TypeId> seen_DEPRECATED;
|
||||
sharedState.tempSeenTy.clear();
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
sharedState.tempSeenTy.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTy, needle, haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
tempSeenTy_DEPRECATED.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, tempSeenTy_DEPRECATED, needle, haystack);
|
||||
}
|
||||
return occursCheck(sharedState.tempSeenTy, needle, haystack);
|
||||
}
|
||||
|
||||
void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
|
||||
void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
|
||||
{
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
needle = follow(needle);
|
||||
haystack = follow(haystack);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
|
||||
seen.insert(haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
|
||||
return;
|
||||
|
||||
seen_DEPRECATED.insert(haystack);
|
||||
}
|
||||
seen.insert(haystack);
|
||||
|
||||
if (get<Unifiable::Error>(needle))
|
||||
return;
|
||||
@ -2091,7 +1892,7 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
|
||||
}
|
||||
|
||||
auto check = [&](TypeId tv) {
|
||||
occursCheck(seen_DEPRECATED, seen, needle, tv);
|
||||
occursCheck(seen, needle, tv);
|
||||
};
|
||||
|
||||
if (get<FreeTypeVar>(haystack))
|
||||
@ -2121,43 +1922,20 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
|
||||
|
||||
void Unifier::occursCheck(TypePackId needle, TypePackId haystack)
|
||||
{
|
||||
std::unordered_set<TypePackId> seen_DEPRECATED;
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTp, needle, haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
tempSeenTp_DEPRECATED.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, tempSeenTp_DEPRECATED, needle, haystack);
|
||||
}
|
||||
return occursCheck(sharedState.tempSeenTp, needle, haystack);
|
||||
}
|
||||
|
||||
void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
||||
void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
||||
{
|
||||
needle = follow(needle);
|
||||
haystack = follow(haystack);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
|
||||
seen.insert(haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
|
||||
return;
|
||||
|
||||
seen_DEPRECATED.insert(haystack);
|
||||
}
|
||||
seen.insert(haystack);
|
||||
|
||||
if (get<Unifiable::Error>(needle))
|
||||
return;
|
||||
@ -2165,8 +1943,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
|
||||
if (!get<Unifiable::Free>(needle))
|
||||
ice("Expected needle pack to be free");
|
||||
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
while (!get<ErrorTypeVar>(haystack))
|
||||
{
|
||||
@ -2186,8 +1963,8 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
|
||||
{
|
||||
if (auto f = get<FunctionTypeVar>(follow(ty)))
|
||||
{
|
||||
occursCheck(seen_DEPRECATED, seen, needle, f->argTypes);
|
||||
occursCheck(seen_DEPRECATED, seen, needle, f->retType);
|
||||
occursCheck(seen, needle, f->argTypes);
|
||||
occursCheck(seen, needle, f->retType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2204,10 +1981,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
|
||||
|
||||
Unifier Unifier::makeChildUnifier()
|
||||
{
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
|
||||
else
|
||||
return Unifier{types, mode, globalScope, log.ownedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
|
||||
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState};
|
||||
}
|
||||
|
||||
bool Unifier::isNonstrictMode() const
|
||||
|
@ -13,8 +13,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeAliasPacks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseTypePackTypeParameters, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false)
|
||||
@ -782,8 +780,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
|
||||
|
||||
AstType* type = parseTypeAnnotation();
|
||||
|
||||
return allocator.alloc<AstStatTypeAlias>(
|
||||
Location(start, type->location), name->name, generics, FFlag::LuauTypeAliasPacks ? genericPacks : AstArray<AstName>{}, type, exported);
|
||||
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, generics, genericPacks, type, exported);
|
||||
}
|
||||
|
||||
AstDeclaredClassProp Parser::parseDeclaredClassMethod()
|
||||
@ -1602,30 +1599,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
||||
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
|
||||
}
|
||||
|
||||
if (FFlag::LuauParseTypePackTypeParameters)
|
||||
bool hasParameters = false;
|
||||
AstArray<AstTypeOrPack> parameters{};
|
||||
|
||||
if (lexer.current().type == '<')
|
||||
{
|
||||
bool hasParameters = false;
|
||||
AstArray<AstTypeOrPack> parameters{};
|
||||
|
||||
if (lexer.current().type == '<')
|
||||
{
|
||||
hasParameters = true;
|
||||
parameters = parseTypeParams();
|
||||
}
|
||||
|
||||
Location end = lexer.previousLocation();
|
||||
|
||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
|
||||
hasParameters = true;
|
||||
parameters = parseTypeParams();
|
||||
}
|
||||
else
|
||||
{
|
||||
AstArray<AstTypeOrPack> generics = parseTypeParams();
|
||||
|
||||
Location end = lexer.previousLocation();
|
||||
Location end = lexer.previousLocation();
|
||||
|
||||
// false in 'hasParameterList' as it is not used without FFlagLuauTypeAliasPacks
|
||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, false, generics), {}};
|
||||
}
|
||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
|
||||
}
|
||||
else if (lexer.current().type == '{')
|
||||
{
|
||||
@ -2414,37 +2399,24 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (FFlag::LuauParseTypePackTypeParameters)
|
||||
if (shouldParseTypePackAnnotation(lexer))
|
||||
{
|
||||
if (shouldParseTypePackAnnotation(lexer))
|
||||
{
|
||||
auto typePack = parseTypePackAnnotation();
|
||||
auto typePack = parseTypePackAnnotation();
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
|
||||
parameters.push_back({{}, typePack});
|
||||
}
|
||||
else if (lexer.current().type == '(')
|
||||
{
|
||||
auto [type, typePack] = parseTypeOrPackAnnotation();
|
||||
parameters.push_back({{}, typePack});
|
||||
}
|
||||
else if (lexer.current().type == '(')
|
||||
{
|
||||
auto [type, typePack] = parseTypeOrPackAnnotation();
|
||||
|
||||
if (typePack)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
|
||||
parameters.push_back({{}, typePack});
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.push_back({type, {}});
|
||||
}
|
||||
}
|
||||
else if (lexer.current().type == '>' && parameters.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (typePack)
|
||||
parameters.push_back({{}, typePack});
|
||||
else
|
||||
{
|
||||
parameters.push_back({parseTypeAnnotation(), {}});
|
||||
}
|
||||
parameters.push_back({type, {}});
|
||||
}
|
||||
else if (lexer.current().type == '>' && parameters.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -121,7 +121,7 @@ struct CliFileResolver : Luau::FileResolver
|
||||
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
|
||||
{
|
||||
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau";
|
||||
if (!moduleExists(name))
|
||||
if (!readFile(name))
|
||||
{
|
||||
// fall back to .lua if a module with .luau doesn't exist
|
||||
name = std::string(expr->value.data, expr->value.size) + ".lua";
|
||||
@ -132,27 +132,6 @@ struct CliFileResolver : Luau::FileResolver
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool moduleExists(const Luau::ModuleName& name) const override
|
||||
{
|
||||
return !!readFile(name);
|
||||
}
|
||||
|
||||
|
||||
std::optional<Luau::ModuleName> fromAstFragment(Luau::AstExpr* expr) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Luau::ModuleName concat(const Luau::ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs + "/" + std::string(rhs);
|
||||
}
|
||||
|
||||
std::optional<Luau::ModuleName> getParentModuleName(const Luau::ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct CliConfigResolver : Luau::ConfigResolver
|
||||
|
@ -526,4 +526,3 @@ int main(int argc, char** argv)
|
||||
return failed;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,13 +321,15 @@ struct Compiler
|
||||
compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i));
|
||||
}
|
||||
|
||||
setDebugLine(expr->func);
|
||||
setDebugLineEnd(expr->func);
|
||||
|
||||
if (expr->self)
|
||||
{
|
||||
AstExprIndexName* fi = expr->func->as<AstExprIndexName>();
|
||||
LUAU_ASSERT(fi);
|
||||
|
||||
setDebugLine(fi->indexLocation);
|
||||
|
||||
BytecodeBuilder::StringRef iname = sref(fi->index);
|
||||
int32_t cid = bytecode.addConstantString(iname);
|
||||
if (cid < 0)
|
||||
@ -1313,6 +1315,8 @@ struct Compiler
|
||||
RegScope rs(this);
|
||||
uint8_t reg = compileExprAuto(expr->expr, rs);
|
||||
|
||||
setDebugLine(expr->indexLocation);
|
||||
|
||||
BytecodeBuilder::StringRef iname = sref(expr->index);
|
||||
int32_t cid = bytecode.addConstantString(iname);
|
||||
if (cid < 0)
|
||||
@ -2710,6 +2714,12 @@ struct Compiler
|
||||
bytecode.setDebugLine(node->location.begin.line + 1);
|
||||
}
|
||||
|
||||
void setDebugLine(const Location& location)
|
||||
{
|
||||
if (options.debugLevel >= 1)
|
||||
bytecode.setDebugLine(location.begin.line + 1);
|
||||
}
|
||||
|
||||
void setDebugLineEnd(AstNode* node)
|
||||
{
|
||||
if (options.debugLevel >= 1)
|
||||
@ -3650,7 +3660,7 @@ struct Compiler
|
||||
{
|
||||
if (options.vectorLib)
|
||||
{
|
||||
if (builtin.object == options.vectorLib && builtin.method == options.vectorCtor)
|
||||
if (builtin.isMethod(options.vectorLib, options.vectorCtor))
|
||||
return LBF_VECTOR;
|
||||
}
|
||||
else
|
||||
|
6
Makefile
6
Makefile
@ -35,7 +35,7 @@ ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Analyze.cpp
|
||||
ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
|
||||
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze
|
||||
|
||||
FUZZ_SOURCES=$(wildcard fuzz/*.cpp)
|
||||
FUZZ_SOURCES=$(wildcard fuzz/*.cpp) fuzz/luau.pb.cpp
|
||||
FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o)
|
||||
|
||||
TESTS_ARGS=
|
||||
@ -167,8 +167,8 @@ fuzz/luau.pb.cpp: fuzz/luau.proto build/libprotobuf-mutator
|
||||
cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=.
|
||||
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
|
||||
|
||||
$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator
|
||||
$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator
|
||||
$(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp
|
||||
$(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp
|
||||
|
||||
build/libprotobuf-mutator:
|
||||
git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator
|
||||
|
@ -54,6 +54,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/Scope.h
|
||||
Analysis/include/Luau/Substitution.h
|
||||
Analysis/include/Luau/Symbol.h
|
||||
Analysis/include/Luau/ToDot.h
|
||||
Analysis/include/Luau/TopoSortStatements.h
|
||||
Analysis/include/Luau/ToString.h
|
||||
Analysis/include/Luau/Transpiler.h
|
||||
@ -86,6 +87,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/Scope.cpp
|
||||
Analysis/src/Substitution.cpp
|
||||
Analysis/src/Symbol.cpp
|
||||
Analysis/src/ToDot.cpp
|
||||
Analysis/src/TopoSortStatements.cpp
|
||||
Analysis/src/ToString.cpp
|
||||
Analysis/src/Transpiler.cpp
|
||||
@ -118,6 +120,7 @@ target_sources(Luau.VM PRIVATE
|
||||
VM/src/ldo.cpp
|
||||
VM/src/lfunc.cpp
|
||||
VM/src/lgc.cpp
|
||||
VM/src/lgcdebug.cpp
|
||||
VM/src/linit.cpp
|
||||
VM/src/lmathlib.cpp
|
||||
VM/src/lmem.cpp
|
||||
@ -194,6 +197,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/RequireTracer.test.cpp
|
||||
tests/StringUtils.test.cpp
|
||||
tests/Symbol.test.cpp
|
||||
tests/ToDot.test.cpp
|
||||
tests/TopoSort.test.cpp
|
||||
tests/ToString.test.cpp
|
||||
tests/Transpiler.test.cpp
|
||||
|
@ -189,7 +189,7 @@ LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);
|
||||
LUA_API int lua_getreadonly(lua_State* L, int idx);
|
||||
LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled);
|
||||
|
||||
LUA_API void* lua_newuserdata(lua_State* L, size_t sz, int tag);
|
||||
LUA_API void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag);
|
||||
LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
|
||||
LUA_API int lua_getmetatable(lua_State* L, int objindex);
|
||||
LUA_API void lua_getfenv(lua_State* L, int idx);
|
||||
@ -288,6 +288,7 @@ LUA_API void lua_unref(lua_State* L, int ref);
|
||||
#define lua_pop(L, n) lua_settop(L, -(n)-1)
|
||||
|
||||
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||
#define lua_newuserdata(L, s) lua_newuserdatatagged(L, s, 0)
|
||||
|
||||
#define lua_strlen(L, i) lua_objlen(L, (i))
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauActivateBeforeExec)
|
||||
|
||||
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
||||
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
|
||||
"$URL: www.lua.org $\n";
|
||||
@ -937,14 +939,21 @@ void lua_call(lua_State* L, int nargs, int nresults)
|
||||
checkresults(L, nargs, nresults);
|
||||
func = L->top - (nargs + 1);
|
||||
|
||||
int wasActive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
luaD_call(L, func, nresults);
|
||||
}
|
||||
else
|
||||
{
|
||||
int oldactive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
|
||||
luaD_call(L, func, nresults);
|
||||
luaD_call(L, func, nresults);
|
||||
|
||||
if (!wasActive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
adjustresults(L, nresults);
|
||||
return;
|
||||
@ -985,14 +994,21 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc)
|
||||
c.func = L->top - (nargs + 1); /* function to be called */
|
||||
c.nresults = nresults;
|
||||
|
||||
int wasActive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
|
||||
}
|
||||
else
|
||||
{
|
||||
int oldactive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
|
||||
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
|
||||
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
|
||||
|
||||
if (!wasActive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
adjustresults(L, nresults);
|
||||
return status;
|
||||
@ -1166,7 +1182,7 @@ void lua_concat(lua_State* L, int n)
|
||||
return;
|
||||
}
|
||||
|
||||
void* lua_newuserdata(lua_State* L, size_t sz, int tag)
|
||||
void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag)
|
||||
{
|
||||
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
|
||||
luaC_checkGC(L);
|
||||
@ -1251,6 +1267,7 @@ uintptr_t lua_encodepointer(lua_State* L, uintptr_t p)
|
||||
|
||||
int lua_ref(lua_State* L, int idx)
|
||||
{
|
||||
api_check(L, idx != LUA_REGISTRYINDEX); /* idx is a stack index for value */
|
||||
int ref = LUA_REFNIL;
|
||||
global_State* g = L->global;
|
||||
StkId p = index2adr(L, idx);
|
||||
|
@ -401,7 +401,7 @@ static int luaB_newproxy(lua_State* L)
|
||||
|
||||
bool needsmt = lua_toboolean(L, 1);
|
||||
|
||||
lua_newuserdata(L, 0, 0);
|
||||
lua_newuserdata(L, 0);
|
||||
|
||||
if (needsmt)
|
||||
{
|
||||
|
@ -17,9 +17,9 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauExceptionMessageFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false)
|
||||
LUAU_FASTFLAG(LuauCoroutineClose)
|
||||
LUAU_FASTFLAGVARIABLE(LuauActivateBeforeExec, false)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
@ -74,35 +74,28 @@ public:
|
||||
|
||||
const char* what() const throw() override
|
||||
{
|
||||
if (FFlag::LuauExceptionMessageFix)
|
||||
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
|
||||
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
|
||||
{
|
||||
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
|
||||
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
|
||||
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
|
||||
if (const char* str = lua_tostring(L, -1))
|
||||
{
|
||||
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
|
||||
if (const char* str = lua_tostring(L, -1))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case LUA_ERRRUN:
|
||||
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
|
||||
case LUA_ERRSYNTAX:
|
||||
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
|
||||
case LUA_ERRMEM:
|
||||
return "lua_exception: " LUA_MEMERRMSG;
|
||||
case LUA_ERRERR:
|
||||
return "lua_exception: " LUA_ERRERRMSG;
|
||||
default:
|
||||
return "lua_exception: unexpected exception status";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
switch (status)
|
||||
{
|
||||
return lua_tostring(L, -1);
|
||||
case LUA_ERRRUN:
|
||||
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
|
||||
case LUA_ERRSYNTAX:
|
||||
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
|
||||
case LUA_ERRMEM:
|
||||
return "lua_exception: " LUA_MEMERRMSG;
|
||||
case LUA_ERRERR:
|
||||
return "lua_exception: " LUA_ERRERRMSG;
|
||||
default:
|
||||
return "lua_exception: unexpected exception status";
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,7 +227,22 @@ void luaD_call(lua_State* L, StkId func, int nResults)
|
||||
if (luau_precall(L, func, nResults) == PCRLUA)
|
||||
{ /* is a Lua function? */
|
||||
L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */
|
||||
luau_execute(L); /* call it */
|
||||
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
int oldactive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
|
||||
luau_execute(L); /* call it */
|
||||
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
luau_execute(L); /* call it */
|
||||
}
|
||||
}
|
||||
L->nCcalls--;
|
||||
luaC_checkGC(L);
|
||||
@ -527,10 +535,10 @@ static void restore_stack_limit(lua_State* L)
|
||||
|
||||
int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
|
||||
{
|
||||
int status;
|
||||
unsigned short oldnCcalls = L->nCcalls;
|
||||
ptrdiff_t old_ci = saveci(L, L->ci);
|
||||
status = luaD_rawrunprotected(L, func, u);
|
||||
int oldactive = luaC_threadactive(L);
|
||||
int status = luaD_rawrunprotected(L, func, u);
|
||||
if (status != 0)
|
||||
{
|
||||
// call user-defined error function (used in xpcall)
|
||||
@ -541,6 +549,13 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
|
||||
status = LUA_ERRERR;
|
||||
}
|
||||
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
// since the call failed with an error, we might have to reset the 'active' thread state
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
if (FFlag::LuauCcallRestoreFix)
|
||||
{
|
||||
// Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
|
||||
|
548
VM/src/lgc.cpp
548
VM/src/lgc.cpp
@ -10,9 +10,7 @@
|
||||
#include "ldo.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauRescanGrayAgainForwardBarrier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false)
|
||||
|
||||
LUAU_FASTFLAG(LuauArrayBoundary)
|
||||
@ -988,7 +986,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
|
||||
GCObject* o = obj2gco(t);
|
||||
|
||||
// in the second propagation stage, table assignment barrier works as a forward barrier
|
||||
if (FFlag::LuauRescanGrayAgainForwardBarrier && g->gcstate == GCSpropagateagain)
|
||||
if (g->gcstate == GCSpropagateagain)
|
||||
{
|
||||
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
|
||||
reallymarkobject(g, v);
|
||||
@ -1044,550 +1042,6 @@ void luaC_linkupval(lua_State* L, UpVal* uv)
|
||||
}
|
||||
}
|
||||
|
||||
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
|
||||
{
|
||||
LUAU_ASSERT(!isdead(g, t));
|
||||
|
||||
if (keepinvariant(g))
|
||||
{
|
||||
/* basic incremental invariant: black can't point to white */
|
||||
LUAU_ASSERT(!(isblack(f) && iswhite(t)));
|
||||
}
|
||||
}
|
||||
|
||||
static void validateref(global_State* g, GCObject* f, TValue* v)
|
||||
{
|
||||
if (iscollectable(v))
|
||||
{
|
||||
LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt);
|
||||
validateobjref(g, f, gcvalue(v));
|
||||
}
|
||||
}
|
||||
|
||||
static void validatetable(global_State* g, Table* h)
|
||||
{
|
||||
int sizenode = 1 << h->lsizenode;
|
||||
|
||||
if (FFlag::LuauArrayBoundary)
|
||||
LUAU_ASSERT(h->lastfree <= sizenode);
|
||||
else
|
||||
LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode);
|
||||
|
||||
if (h->metatable)
|
||||
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
|
||||
|
||||
for (int i = 0; i < h->sizearray; ++i)
|
||||
validateref(g, obj2gco(h), &h->array[i]);
|
||||
|
||||
for (int i = 0; i < sizenode; ++i)
|
||||
{
|
||||
LuaNode* n = &h->node[i];
|
||||
|
||||
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
|
||||
LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode);
|
||||
|
||||
if (!ttisnil(gval(n)))
|
||||
{
|
||||
TValue k = {};
|
||||
k.tt = gkey(n)->tt;
|
||||
k.value = gkey(n)->value;
|
||||
|
||||
validateref(g, obj2gco(h), &k);
|
||||
validateref(g, obj2gco(h), gval(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void validateclosure(global_State* g, Closure* cl)
|
||||
{
|
||||
validateobjref(g, obj2gco(cl), obj2gco(cl->env));
|
||||
|
||||
if (cl->isC)
|
||||
{
|
||||
for (int i = 0; i < cl->nupvalues; ++i)
|
||||
validateref(g, obj2gco(cl), &cl->c.upvals[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
|
||||
|
||||
validateobjref(g, obj2gco(cl), obj2gco(cl->l.p));
|
||||
|
||||
for (int i = 0; i < cl->nupvalues; ++i)
|
||||
validateref(g, obj2gco(cl), &cl->l.uprefs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void validatestack(global_State* g, lua_State* l)
|
||||
{
|
||||
validateref(g, obj2gco(l), gt(l));
|
||||
|
||||
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
|
||||
{
|
||||
LUAU_ASSERT(l->stack <= ci->base);
|
||||
LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top);
|
||||
LUAU_ASSERT(ci->top <= l->stack_last);
|
||||
}
|
||||
|
||||
// note: stack refs can violate gc invariant so we only check for liveness
|
||||
for (StkId o = l->stack; o < l->top; ++o)
|
||||
checkliveness(g, o);
|
||||
|
||||
if (l->namecall)
|
||||
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
|
||||
|
||||
for (GCObject* uv = l->openupval; uv; uv = uv->gch.next)
|
||||
{
|
||||
LUAU_ASSERT(uv->gch.tt == LUA_TUPVAL);
|
||||
LUAU_ASSERT(gco2uv(uv)->v != &gco2uv(uv)->u.value);
|
||||
}
|
||||
}
|
||||
|
||||
static void validateproto(global_State* g, Proto* f)
|
||||
{
|
||||
if (f->source)
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->source));
|
||||
|
||||
if (f->debugname)
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->debugname));
|
||||
|
||||
for (int i = 0; i < f->sizek; ++i)
|
||||
validateref(g, obj2gco(f), &f->k[i]);
|
||||
|
||||
for (int i = 0; i < f->sizeupvalues; ++i)
|
||||
if (f->upvalues[i])
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i]));
|
||||
|
||||
for (int i = 0; i < f->sizep; ++i)
|
||||
if (f->p[i])
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->p[i]));
|
||||
|
||||
for (int i = 0; i < f->sizelocvars; i++)
|
||||
if (f->locvars[i].varname)
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname));
|
||||
}
|
||||
|
||||
static void validateobj(global_State* g, GCObject* o)
|
||||
{
|
||||
/* dead objects can only occur during sweep */
|
||||
if (isdead(g, o))
|
||||
{
|
||||
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (o->gch.tt)
|
||||
{
|
||||
case LUA_TSTRING:
|
||||
break;
|
||||
|
||||
case LUA_TTABLE:
|
||||
validatetable(g, gco2h(o));
|
||||
break;
|
||||
|
||||
case LUA_TFUNCTION:
|
||||
validateclosure(g, gco2cl(o));
|
||||
break;
|
||||
|
||||
case LUA_TUSERDATA:
|
||||
if (gco2u(o)->metatable)
|
||||
validateobjref(g, o, obj2gco(gco2u(o)->metatable));
|
||||
break;
|
||||
|
||||
case LUA_TTHREAD:
|
||||
validatestack(g, gco2th(o));
|
||||
break;
|
||||
|
||||
case LUA_TPROTO:
|
||||
validateproto(g, gco2p(o));
|
||||
break;
|
||||
|
||||
case LUA_TUPVAL:
|
||||
validateref(g, o, gco2uv(o)->v);
|
||||
break;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"unexpected object type");
|
||||
}
|
||||
}
|
||||
|
||||
static void validatelist(global_State* g, GCObject* o)
|
||||
{
|
||||
while (o)
|
||||
{
|
||||
validateobj(g, o);
|
||||
|
||||
o = o->gch.next;
|
||||
}
|
||||
}
|
||||
|
||||
static void validategraylist(global_State* g, GCObject* o)
|
||||
{
|
||||
if (!keepinvariant(g))
|
||||
return;
|
||||
|
||||
while (o)
|
||||
{
|
||||
LUAU_ASSERT(isgray(o));
|
||||
|
||||
switch (o->gch.tt)
|
||||
{
|
||||
case LUA_TTABLE:
|
||||
o = gco2h(o)->gclist;
|
||||
break;
|
||||
case LUA_TFUNCTION:
|
||||
o = gco2cl(o)->gclist;
|
||||
break;
|
||||
case LUA_TTHREAD:
|
||||
o = gco2th(o)->gclist;
|
||||
break;
|
||||
case LUA_TPROTO:
|
||||
o = gco2p(o)->gclist;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"unknown object in gray list");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void luaC_validate(lua_State* L)
|
||||
{
|
||||
global_State* g = L->global;
|
||||
|
||||
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
|
||||
checkliveness(g, &g->registry);
|
||||
|
||||
for (int i = 0; i < LUA_T_COUNT; ++i)
|
||||
if (g->mt[i])
|
||||
LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i])));
|
||||
|
||||
validategraylist(g, g->weak);
|
||||
validategraylist(g, g->gray);
|
||||
validategraylist(g, g->grayagain);
|
||||
|
||||
for (int i = 0; i < g->strt.size; ++i)
|
||||
validatelist(g, g->strt.hash[i]);
|
||||
|
||||
validatelist(g, g->rootgc);
|
||||
validatelist(g, g->strbufgc);
|
||||
|
||||
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
|
||||
{
|
||||
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
||||
LUAU_ASSERT(uv->v != &uv->u.value);
|
||||
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool safejson(char ch)
|
||||
{
|
||||
return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"';
|
||||
}
|
||||
|
||||
static void dumpref(FILE* f, GCObject* o)
|
||||
{
|
||||
fprintf(f, "\"%p\"", o);
|
||||
}
|
||||
|
||||
static void dumprefs(FILE* f, TValue* data, size_t size)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
if (iscollectable(&data[i]))
|
||||
{
|
||||
if (!first)
|
||||
fputc(',', f);
|
||||
first = false;
|
||||
|
||||
dumpref(f, gcvalue(&data[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpstringdata(FILE* f, const char* data, size_t len)
|
||||
{
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
fputc(safejson(data[i]) ? data[i] : '?', f);
|
||||
}
|
||||
|
||||
static void dumpstring(FILE* f, TString* ts)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len)));
|
||||
dumpstringdata(f, ts->data, ts->len);
|
||||
fprintf(f, "\"}");
|
||||
}
|
||||
|
||||
static void dumptable(FILE* f, Table* h)
|
||||
{
|
||||
size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
|
||||
|
||||
fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size));
|
||||
|
||||
if (h->node != &luaH_dummynode)
|
||||
{
|
||||
fprintf(f, ",\"pairs\":[");
|
||||
|
||||
bool first = true;
|
||||
|
||||
for (int i = 0; i < sizenode(h); ++i)
|
||||
{
|
||||
const LuaNode& n = h->node[i];
|
||||
|
||||
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
|
||||
{
|
||||
if (!first)
|
||||
fputc(',', f);
|
||||
first = false;
|
||||
|
||||
if (iscollectable(&n.key))
|
||||
dumpref(f, gcvalue(&n.key));
|
||||
else
|
||||
fprintf(f, "null");
|
||||
|
||||
fputc(',', f);
|
||||
|
||||
if (iscollectable(&n.val))
|
||||
dumpref(f, gcvalue(&n.val));
|
||||
else
|
||||
fprintf(f, "null");
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(f, "]");
|
||||
}
|
||||
if (h->sizearray)
|
||||
{
|
||||
fprintf(f, ",\"array\":[");
|
||||
dumprefs(f, h->array, h->sizearray);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
if (h->metatable)
|
||||
{
|
||||
fprintf(f, ",\"metatable\":");
|
||||
dumpref(f, obj2gco(h->metatable));
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpclosure(FILE* f, Closure* cl)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat,
|
||||
cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues)));
|
||||
|
||||
fprintf(f, ",\"env\":");
|
||||
dumpref(f, obj2gco(cl->env));
|
||||
if (cl->isC)
|
||||
{
|
||||
if (cl->nupvalues)
|
||||
{
|
||||
fprintf(f, ",\"upvalues\":[");
|
||||
dumprefs(f, cl->c.upvals, cl->nupvalues);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(f, ",\"proto\":");
|
||||
dumpref(f, obj2gco(cl->l.p));
|
||||
if (cl->nupvalues)
|
||||
{
|
||||
fprintf(f, ",\"upvalues\":[");
|
||||
dumprefs(f, cl->l.uprefs, cl->nupvalues);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpudata(FILE* f, Udata* u)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag);
|
||||
|
||||
if (u->metatable)
|
||||
{
|
||||
fprintf(f, ",\"metatable\":");
|
||||
dumpref(f, obj2gco(u->metatable));
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpthread(FILE* f, lua_State* th)
|
||||
{
|
||||
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
||||
|
||||
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
|
||||
|
||||
if (iscollectable(&th->l_gt))
|
||||
{
|
||||
fprintf(f, ",\"env\":");
|
||||
dumpref(f, gcvalue(&th->l_gt));
|
||||
}
|
||||
|
||||
Closure* tcl = 0;
|
||||
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
|
||||
{
|
||||
if (ttisfunction(ci->func))
|
||||
{
|
||||
tcl = clvalue(ci->func);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tcl && !tcl->isC && tcl->l.p->source)
|
||||
{
|
||||
Proto* p = tcl->l.p;
|
||||
|
||||
fprintf(f, ",\"source\":\"");
|
||||
dumpstringdata(f, p->source->data, p->source->len);
|
||||
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
|
||||
}
|
||||
|
||||
if (th->top > th->stack)
|
||||
{
|
||||
fprintf(f, ",\"stack\":[");
|
||||
dumprefs(f, th->stack, th->top - th->stack);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpproto(FILE* f, Proto* p)
|
||||
{
|
||||
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
|
||||
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
|
||||
|
||||
fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size));
|
||||
|
||||
if (p->source)
|
||||
{
|
||||
fprintf(f, ",\"source\":\"");
|
||||
dumpstringdata(f, p->source->data, p->source->len);
|
||||
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
|
||||
}
|
||||
|
||||
if (p->sizek)
|
||||
{
|
||||
fprintf(f, ",\"constants\":[");
|
||||
dumprefs(f, p->k, p->sizek);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
|
||||
if (p->sizep)
|
||||
{
|
||||
fprintf(f, ",\"protos\":[");
|
||||
for (int i = 0; i < p->sizep; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
fputc(',', f);
|
||||
dumpref(f, obj2gco(p->p[i]));
|
||||
}
|
||||
fprintf(f, "]");
|
||||
}
|
||||
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpupval(FILE* f, UpVal* uv)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal)));
|
||||
|
||||
if (iscollectable(uv->v))
|
||||
{
|
||||
fprintf(f, ",\"object\":");
|
||||
dumpref(f, gcvalue(uv->v));
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpobj(FILE* f, GCObject* o)
|
||||
{
|
||||
switch (o->gch.tt)
|
||||
{
|
||||
case LUA_TSTRING:
|
||||
return dumpstring(f, gco2ts(o));
|
||||
|
||||
case LUA_TTABLE:
|
||||
return dumptable(f, gco2h(o));
|
||||
|
||||
case LUA_TFUNCTION:
|
||||
return dumpclosure(f, gco2cl(o));
|
||||
|
||||
case LUA_TUSERDATA:
|
||||
return dumpudata(f, gco2u(o));
|
||||
|
||||
case LUA_TTHREAD:
|
||||
return dumpthread(f, gco2th(o));
|
||||
|
||||
case LUA_TPROTO:
|
||||
return dumpproto(f, gco2p(o));
|
||||
|
||||
case LUA_TUPVAL:
|
||||
return dumpupval(f, gco2uv(o));
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dumplist(FILE* f, GCObject* o)
|
||||
{
|
||||
while (o)
|
||||
{
|
||||
dumpref(f, o);
|
||||
fputc(':', f);
|
||||
dumpobj(f, o);
|
||||
fputc(',', f);
|
||||
fputc('\n', f);
|
||||
|
||||
// thread has additional list containing collectable objects that are not present in rootgc
|
||||
if (o->gch.tt == LUA_TTHREAD)
|
||||
dumplist(f, gco2th(o)->openupval);
|
||||
|
||||
o = o->gch.next;
|
||||
}
|
||||
}
|
||||
|
||||
void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat))
|
||||
{
|
||||
global_State* g = L->global;
|
||||
FILE* f = static_cast<FILE*>(file);
|
||||
|
||||
fprintf(f, "{\"objects\":{\n");
|
||||
dumplist(f, g->rootgc);
|
||||
dumplist(f, g->strbufgc);
|
||||
for (int i = 0; i < g->strt.size; ++i)
|
||||
dumplist(f, g->strt.hash[i]);
|
||||
|
||||
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
|
||||
fprintf(f, "},\"roots\":{\n");
|
||||
fprintf(f, "\"mainthread\":");
|
||||
dumpref(f, obj2gco(g->mainthread));
|
||||
fprintf(f, ",\"registry\":");
|
||||
dumpref(f, gcvalue(&g->registry));
|
||||
|
||||
fprintf(f, "},\"stats\":{\n");
|
||||
|
||||
fprintf(f, "\"size\":%d,\n", int(g->totalbytes));
|
||||
|
||||
fprintf(f, "\"categories\":{\n");
|
||||
for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++)
|
||||
{
|
||||
if (size_t bytes = g->memcatbytes[i])
|
||||
{
|
||||
if (categoryName)
|
||||
fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes));
|
||||
else
|
||||
fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes));
|
||||
}
|
||||
}
|
||||
fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing ,
|
||||
fprintf(f, "}\n");
|
||||
fprintf(f, "}}\n");
|
||||
}
|
||||
|
||||
// measure the allocation rate in bytes/sec
|
||||
// returns -1 if allocation rate cannot be measured
|
||||
int64_t luaC_allocationrate(lua_State* L)
|
||||
|
558
VM/src/lgcdebug.cpp
Normal file
558
VM/src/lgcdebug.cpp
Normal 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");
|
||||
}
|
@ -23,9 +23,11 @@ const bool kFuzzCompiler = true;
|
||||
const bool kFuzzLinter = true;
|
||||
const bool kFuzzTypeck = true;
|
||||
const bool kFuzzVM = true;
|
||||
const bool kFuzzTypes = true;
|
||||
const bool kFuzzTranspile = true;
|
||||
|
||||
// Should we generate type annotations?
|
||||
const bool kFuzzTypes = true;
|
||||
|
||||
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
|
||||
|
||||
std::string protoprint(const luau::StatBlock& stat, bool types);
|
||||
|
@ -78,3 +78,26 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_fn")
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("AstQuery");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type")
|
||||
{
|
||||
ScopedFastFlag luauTailArgumentTypeInfo{"LuauTailArgumentTypeInfo", true};
|
||||
|
||||
check(R"(
|
||||
local function foo() return 2 end
|
||||
local function bar(a: number) return -a end
|
||||
bar(foo())
|
||||
)");
|
||||
|
||||
auto oty = findTypeAtPosition(Position(3, 7));
|
||||
REQUIRE(oty);
|
||||
CHECK_EQ("number", toString(*oty));
|
||||
|
||||
auto expectedOty = findExpectedTypeAtPosition(Position(3, 7));
|
||||
REQUIRE(expectedOty);
|
||||
CHECK_EQ("number", toString(*expectedOty));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1935,6 +1935,39 @@ return target(b@1
|
||||
CHECK(ac.entryMap["bar2"].typeCorrect == TypeCorrectKind::None);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses")
|
||||
{
|
||||
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
|
||||
ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true);
|
||||
|
||||
check(R"(
|
||||
local function bar(a: number) return -a end
|
||||
local abc = b@1
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
CHECK(ac.entryMap.count("bar"));
|
||||
CHECK(ac.entryMap["bar"].parens == ParenthesesRecommendation::CursorInside);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "function_result_passed_to_function_has_parentheses")
|
||||
{
|
||||
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
|
||||
ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true);
|
||||
|
||||
check(R"(
|
||||
local function foo() return 1 end
|
||||
local function bar(a: number) return -a end
|
||||
local abc = bar(@1)
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
CHECK(ac.entryMap.count("foo"));
|
||||
CHECK(ac.entryMap["foo"].parens == ParenthesesRecommendation::CursorAfter);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "type_correct_sealed_table")
|
||||
{
|
||||
check(R"(
|
||||
@ -2210,8 +2243,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource")
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocompleteSource_require")
|
||||
{
|
||||
ScopedFastFlag luauResolveModuleNameWithoutACurrentModule("LuauResolveModuleNameWithoutACurrentModule", true);
|
||||
|
||||
std::string_view source = R"(
|
||||
local a = require(w -- Line 1
|
||||
-- | Column 27
|
||||
@ -2287,8 +2318,6 @@ until
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "if_then_else_elseif_completions")
|
||||
{
|
||||
ScopedFastFlag sff{"ElseElseIfCompletionImprovements", true};
|
||||
|
||||
check(R"(
|
||||
local elsewhere = false
|
||||
|
||||
@ -2585,9 +2614,6 @@ a = if temp then even elseif true then temp else e@9
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
check(R"(
|
||||
type A<T...> = () -> T...
|
||||
local a: A<(number, s@1>
|
||||
|
@ -1057,6 +1057,18 @@ RETURN R0 1
|
||||
CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"(
|
||||
LOADN R0 20
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// codegen for a true constant condition with non-constant expressions
|
||||
CHECK_EQ("\n" + compileFunction0("return if true then {} else error()"), R"(
|
||||
NEWTABLE R0 0 0
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// codegen for a false constant condition with non-constant expressions
|
||||
CHECK_EQ("\n" + compileFunction0("return if false then error() else {}"), R"(
|
||||
NEWTABLE R0 0 0
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// codegen for a false (in this case 'nil') constant condition
|
||||
@ -2360,6 +2372,58 @@ Foo:Bar(
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugLineInfoCallChain")
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
|
||||
Luau::compileOrThrow(bcb, R"(
|
||||
local Foo = ...
|
||||
|
||||
Foo
|
||||
:Bar(1)
|
||||
:Baz(2)
|
||||
.Qux(3)
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
2: GETVARARGS R0 1
|
||||
5: LOADN R4 1
|
||||
5: NAMECALL R2 R0 K0
|
||||
5: CALL R2 2 1
|
||||
6: LOADN R4 2
|
||||
6: NAMECALL R2 R2 K1
|
||||
6: CALL R2 2 1
|
||||
7: GETTABLEKS R1 R2 K2
|
||||
7: LOADN R2 3
|
||||
7: CALL R1 1 0
|
||||
8: RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugLineInfoFastCall")
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
|
||||
Luau::compileOrThrow(bcb, R"(
|
||||
local Foo, Bar = ...
|
||||
|
||||
return
|
||||
math.max(
|
||||
Foo,
|
||||
Bar)
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
2: GETVARARGS R0 2
|
||||
5: FASTCALL2 18 R0 R1 +5
|
||||
5: MOVE R3 R0
|
||||
5: MOVE R4 R1
|
||||
5: GETIMPORT R2 2
|
||||
5: CALL R2 2 -1
|
||||
5: RETURN R2 -1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugSource")
|
||||
{
|
||||
const char* source = R"(
|
||||
@ -3742,4 +3806,108 @@ RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ConstantsNoFolding")
|
||||
{
|
||||
const char* source = "return nil, true, 42, 'hello'";
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
||||
Luau::CompileOptions options;
|
||||
options.optimizationLevel = 0;
|
||||
Luau::compileOrThrow(bcb, source, options);
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
LOADNIL R0
|
||||
LOADB R1 1
|
||||
LOADK R2 K0
|
||||
LOADK R3 K1
|
||||
RETURN R0 4
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("VectorFastCall")
|
||||
{
|
||||
const char* source = "return Vector3.new(1, 2, 3)";
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
||||
Luau::CompileOptions options;
|
||||
options.vectorLib = "Vector3";
|
||||
options.vectorCtor = "new";
|
||||
Luau::compileOrThrow(bcb, source, options);
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
LOADN R1 1
|
||||
LOADN R2 2
|
||||
LOADN R3 3
|
||||
FASTCALL 54 +2
|
||||
GETIMPORT R0 2
|
||||
CALL R0 3 -1
|
||||
RETURN R0 -1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("TypeAssertion")
|
||||
{
|
||||
// validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
print(foo() :: typeof(error("compile time")))
|
||||
)"),
|
||||
R"(
|
||||
GETIMPORT R0 1
|
||||
GETIMPORT R1 3
|
||||
CALL R1 0 1
|
||||
CALL R0 1 0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
// note that above, foo() is treated as single-arg function; removing type assertion changes the bytecode
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
print(foo())
|
||||
)"),
|
||||
R"(
|
||||
GETIMPORT R0 1
|
||||
GETIMPORT R1 3
|
||||
CALL R1 0 -1
|
||||
CALL R0 -1 0
|
||||
RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("Arithmetics")
|
||||
{
|
||||
// basic arithmetics codegen with non-constants
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local a, b = ...
|
||||
return a + b, a - b, a / b, a * b, a % b, a ^ b
|
||||
)"),
|
||||
R"(
|
||||
GETVARARGS R0 2
|
||||
ADD R2 R0 R1
|
||||
SUB R3 R0 R1
|
||||
DIV R4 R0 R1
|
||||
MUL R5 R0 R1
|
||||
MOD R6 R0 R1
|
||||
POW R7 R0 R1
|
||||
RETURN R2 6
|
||||
)");
|
||||
|
||||
// basic arithmetics codegen with constants on the right side
|
||||
// note that we don't simplify these expressions as we don't know the type of a
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local a = ...
|
||||
return a + 1, a - 1, a / 1, a * 1, a % 1, a ^ 1
|
||||
)"),
|
||||
R"(
|
||||
GETVARARGS R0 1
|
||||
ADDK R1 R0 K0
|
||||
SUBK R2 R0 K0
|
||||
DIVK R3 R0 K0
|
||||
MULK R4 R0 K0
|
||||
MODK R5 R0 K0
|
||||
POWK R6 R0 K0
|
||||
RETURN R1 6
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -508,6 +508,9 @@ TEST_CASE("Debugger")
|
||||
cb->debugbreak = [](lua_State* L, lua_Debug* ar) {
|
||||
breakhits++;
|
||||
|
||||
// make sure we can trace the stack for every breakpoint we hit
|
||||
lua_debugtrace(L);
|
||||
|
||||
// for every breakpoint, we break on the first invocation and continue on second
|
||||
// this allows us to easily step off breakpoints
|
||||
// (real implementaiton may require singlestepping)
|
||||
@ -703,21 +706,52 @@ TEST_CASE("ApiFunctionCalls")
|
||||
StateRef globalState = runConformance("apicalls.lua");
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_call(L, 2, 1);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
// lua_call
|
||||
{
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_call(L, 2, 1);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_pcall(L, 2, 1, 0);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
// lua_pcall
|
||||
{
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_pcall(L, 2, 1, 0);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// lua_equal with a sleeping thread wake up
|
||||
{
|
||||
ScopedFastFlag luauActivateBeforeExec("LuauActivateBeforeExec", true);
|
||||
|
||||
lua_State* L2 = lua_newthread(L);
|
||||
|
||||
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
|
||||
lua_pushnumber(L2, 42);
|
||||
lua_pcall(L2, 1, 1, 0);
|
||||
|
||||
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
|
||||
lua_pushnumber(L2, 42);
|
||||
lua_pcall(L2, 1, 1, 0);
|
||||
|
||||
// Reset GC
|
||||
lua_gc(L2, LUA_GCCOLLECT, 0);
|
||||
|
||||
// Try to mark 'L2' as sleeping
|
||||
// Can't control GC precisely, even in tests
|
||||
lua_gc(L2, LUA_GCSTEP, 8);
|
||||
|
||||
CHECK(lua_equal(L2, -1, -2) == 1);
|
||||
lua_pop(L2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
static bool endsWith(const std::string& str, const std::string& suffix)
|
||||
@ -731,8 +765,6 @@ static bool endsWith(const std::string& str, const std::string& suffix)
|
||||
#if !LUA_USE_LONGJMP
|
||||
TEST_CASE("ExceptionObject")
|
||||
{
|
||||
ScopedFastFlag sff("LuauExceptionMessageFix", true);
|
||||
|
||||
struct ExceptionResult
|
||||
{
|
||||
bool exceptionGenerated;
|
||||
|
@ -19,19 +19,6 @@ static const char* mainModuleName = "MainModule";
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::optional<ModuleName> TestFileResolver::fromAstFragment(AstExpr* expr) const
|
||||
{
|
||||
auto g = expr->as<AstExprGlobal>();
|
||||
if (!g)
|
||||
return std::nullopt;
|
||||
|
||||
std::string_view value = g->name.value;
|
||||
if (value == "game" || value == "Game" || value == "workspace" || value == "Workspace" || value == "script" || value == "Script")
|
||||
return ModuleName(value);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* context, AstExpr* expr)
|
||||
{
|
||||
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
|
||||
@ -81,24 +68,6 @@ std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* cont
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ModuleName TestFileResolver::concat(const ModuleName& lhs, std::string_view rhs) const
|
||||
{
|
||||
return lhs + "/" + ModuleName(rhs);
|
||||
}
|
||||
|
||||
std::optional<ModuleName> TestFileResolver::getParentModuleName(const ModuleName& name) const
|
||||
{
|
||||
std::string_view view = name;
|
||||
const size_t lastSeparatorIndex = view.find_last_of('/');
|
||||
|
||||
if (lastSeparatorIndex != std::string_view::npos)
|
||||
{
|
||||
return ModuleName(view.substr(0, lastSeparatorIndex));
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string TestFileResolver::getHumanReadableModuleName(const ModuleName& name) const
|
||||
{
|
||||
return name;
|
||||
@ -324,6 +293,13 @@ std::optional<TypeId> Fixture::findTypeAtPosition(Position position)
|
||||
return Luau::findTypeAtPosition(*module, *sourceModule, position);
|
||||
}
|
||||
|
||||
std::optional<TypeId> Fixture::findExpectedTypeAtPosition(Position position)
|
||||
{
|
||||
ModulePtr module = getMainModule();
|
||||
SourceModule* sourceModule = getMainSourceModule();
|
||||
return Luau::findExpectedTypeAtPosition(*module, *sourceModule, position);
|
||||
}
|
||||
|
||||
TypeId Fixture::requireTypeAtPosition(Position position)
|
||||
{
|
||||
auto ty = findTypeAtPosition(position);
|
||||
|
@ -64,12 +64,8 @@ struct TestFileResolver
|
||||
return SourceCode{it->second, sourceType};
|
||||
}
|
||||
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override;
|
||||
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override;
|
||||
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override;
|
||||
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override;
|
||||
|
||||
std::string getHumanReadableModuleName(const ModuleName& name) const override;
|
||||
|
||||
std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const override;
|
||||
@ -126,6 +122,7 @@ struct Fixture
|
||||
|
||||
std::optional<TypeId> findTypeAtPosition(Position position);
|
||||
TypeId requireTypeAtPosition(Position position);
|
||||
std::optional<TypeId> findExpectedTypeAtPosition(Position position);
|
||||
|
||||
std::optional<TypeId> lookupType(const std::string& name);
|
||||
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
|
||||
|
@ -46,18 +46,6 @@ NaiveModuleResolver naiveModuleResolver;
|
||||
|
||||
struct NaiveFileResolver : NullFileResolver
|
||||
{
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
|
||||
{
|
||||
AstExprGlobal* g = expr->as<AstExprGlobal>();
|
||||
if (g && g->name == "Modules")
|
||||
return "Modules";
|
||||
|
||||
if (g && g->name == "game")
|
||||
return "game";
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override
|
||||
{
|
||||
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
|
||||
@ -86,11 +74,6 @@ struct NaiveFileResolver : NullFileResolver
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs + "/" + ModuleName(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -1469,6 +1469,22 @@ _ = true and true or false -- no warning since this is is a common pattern used
|
||||
CHECK_EQ(result.warnings[6].location.begin.line + 1, 19);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsExpr")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
local correct, opaque = ...
|
||||
|
||||
if correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
|
||||
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
|
||||
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", false)}) then
|
||||
end
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 4");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line + 1, 5);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateLocal")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
|
@ -44,9 +44,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive")
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
// numberType is persistent. We leave it as-is.
|
||||
TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks);
|
||||
TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks, cloneState);
|
||||
CHECK_EQ(newNumber, typeChecker.numberType);
|
||||
}
|
||||
|
||||
@ -56,12 +57,13 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive")
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
// Create a new number type that isn't persistent
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldNumber = typeChecker.globalTypes.addType(PrimitiveTypeVar{PrimitiveTypeVar::Number});
|
||||
freeze(typeChecker.globalTypes);
|
||||
TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks);
|
||||
TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
CHECK_NE(newNumber, oldNumber);
|
||||
CHECK_EQ(*oldNumber, *newNumber);
|
||||
@ -89,9 +91,10 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
TypeArena dest;
|
||||
TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks);
|
||||
TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(counterCopy);
|
||||
REQUIRE(ttv != nullptr);
|
||||
@ -142,11 +145,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union")
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldUnion = typeChecker.globalTypes.addType(UnionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
|
||||
freeze(typeChecker.globalTypes);
|
||||
TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks);
|
||||
TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
CHECK_NE(newUnion, oldUnion);
|
||||
CHECK_EQ("number | string", toString(newUnion));
|
||||
@ -159,11 +163,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection")
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldIntersection = typeChecker.globalTypes.addType(IntersectionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
|
||||
freeze(typeChecker.globalTypes);
|
||||
TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks);
|
||||
TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
CHECK_NE(newIntersection, oldIntersection);
|
||||
CHECK_EQ("number & string", toString(newIntersection));
|
||||
@ -188,8 +193,9 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks);
|
||||
TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks, cloneState);
|
||||
const ClassTypeVar* ctv = get<ClassTypeVar>(cloned);
|
||||
REQUIRE(ctv != nullptr);
|
||||
|
||||
@ -211,16 +217,16 @@ TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types")
|
||||
TypeArena dest;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
bool encounteredFreeType = false;
|
||||
TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, cloneState);
|
||||
CHECK_EQ("any", toString(clonedTy));
|
||||
CHECK(encounteredFreeType);
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
|
||||
encounteredFreeType = false;
|
||||
TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
cloneState = {};
|
||||
TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, cloneState);
|
||||
CHECK_EQ("...any", toString(clonedTp));
|
||||
CHECK(encounteredFreeType);
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
||||
@ -232,12 +238,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
||||
TypeArena dest;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
bool encounteredFreeType = false;
|
||||
TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, cloneState);
|
||||
const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned);
|
||||
CHECK_EQ(clonedTtv->state, TableState::Sealed);
|
||||
CHECK(encounteredFreeType);
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_self_property")
|
||||
@ -267,4 +273,34 @@ TEST_CASE_FIXTURE(Fixture, "clone_self_property")
|
||||
"dot or pass 1 extra nil to suppress this warning");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
{
|
||||
#if defined(_DEBUG) || defined(_NOOPT)
|
||||
int limit = 250;
|
||||
#else
|
||||
int limit = 400;
|
||||
#endif
|
||||
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
|
||||
|
||||
TypeArena src;
|
||||
|
||||
TypeId table = src.addType(TableTypeVar{});
|
||||
TypeId nested = table;
|
||||
|
||||
for (int i = 0; i < limit + 100; i++)
|
||||
{
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(nested);
|
||||
|
||||
ttv->props["a"].type = src.addType(TableTypeVar{});
|
||||
nested = ttv->props["a"].type;
|
||||
}
|
||||
|
||||
TypeArena dest;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
CHECK_THROWS_AS(clone(table, dest, seenTypes, seenTypePacks, cloneState), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -2518,8 +2518,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_if_else_expression")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_type_pack_type_parameters")
|
||||
{
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
AstStat* stat = parse(R"(
|
||||
type Packed<T...> = () -> T...
|
||||
|
||||
|
366
tests/ToDot.test.cpp
Normal file
366
tests/ToDot.test.cpp
Normal 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();
|
@ -445,9 +445,6 @@ local a: Import.Type
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_packs")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
std::string code = R"(
|
||||
type Packed<T...> = (T...)->(T...)
|
||||
local a: Packed<>
|
||||
|
@ -537,8 +537,6 @@ TEST_CASE_FIXTURE(Fixture, "free_variables_from_typeof_in_aliases")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "non_recursive_aliases_that_reuse_a_generic_name")
|
||||
{
|
||||
ScopedFastFlag sff1{"LuauSubstitutionDontReplaceIgnoredTypes", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Array<T> = { [number]: T }
|
||||
type Tuple<T, V> = Array<T | V>
|
||||
|
@ -609,8 +609,6 @@ TEST_CASE_FIXTURE(Fixture, "typefuns_sharing_types")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_tables_do_not_clone_original_fields")
|
||||
{
|
||||
ScopedFastFlag luauCloneBoundTables{"LuauCloneBoundTables", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local exports = {}
|
||||
local nested = {}
|
||||
@ -627,4 +625,23 @@ return exports
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgumentNameSize{"LuauFunctionArgumentNameSize", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f<T, U...>(a: T, ...: U...) end
|
||||
|
||||
f(1, 2, 3)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
auto ty = findTypeAtPosition(Position(3, 0));
|
||||
REQUIRE(ty);
|
||||
ToStringOptions opts;
|
||||
opts.functionTypeArguments = true;
|
||||
CHECK_EQ(toString(*ty, opts), "(a: number, number, number) -> ()");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -31,8 +31,6 @@ TEST_SUITE_BEGIN("ProvisionalTests");
|
||||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
|
||||
const std::string code = R"(
|
||||
function f(a)
|
||||
if type(a) == "boolean" then
|
||||
|
@ -2022,4 +2022,74 @@ caused by:
|
||||
Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '<a>(a) -> ()')");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauPropertiesGetExpectedType", true},
|
||||
{"LuauExpectedTypesOfProperties", true},
|
||||
{"LuauTableSubtypingVariance", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Super = { x : number }
|
||||
type Sub = { x : number, y: number }
|
||||
type HasSuper = { p : Super }
|
||||
type HasSub = { p : Sub }
|
||||
local a: HasSuper = { p = { x = 5, y = 7 }}
|
||||
a.p = { x = 9 }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauPropertiesGetExpectedType", true},
|
||||
{"LuauExpectedTypesOfProperties", true},
|
||||
{"LuauTableSubtypingVariance", true},
|
||||
{"LuauExtendedTypeMismatchError", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Super = { x : number }
|
||||
type Sub = { x : number, y: number }
|
||||
type HasSuper = { p : Super }
|
||||
type HasSub = { p : Sub }
|
||||
local tmp = { p = { x = 5, y = 7 }}
|
||||
local a: HasSuper = tmp
|
||||
a.p = { x = 9 }
|
||||
-- needs to be an error because
|
||||
local y: number = tmp.p.y
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'tmp' could not be converted into 'HasSuper'
|
||||
caused by:
|
||||
Property 'p' is not compatible. Table type '{| x: number, y: number |}' not compatible with type 'Super' because the former has extra field 'y')");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
{"LuauPropertiesGetExpectedType", true},
|
||||
{"LuauExpectedTypesOfProperties", true},
|
||||
{"LuauTableSubtypingVariance", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Super = { x : number }
|
||||
type Sub = { x : number, y: number }
|
||||
type HasSuper = { [string] : Super }
|
||||
type HasSub = { [string] : Sub }
|
||||
local a: HasSuper = { p = { x = 5, y = 7 }}
|
||||
a.p = { x = 9 }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -4779,4 +4779,24 @@ local bar = foo.nutrition + 100
|
||||
// CHECK_EQ("Unknown type used in + operation; consider adding a type annotation to 'foo'", toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "require_failed_module")
|
||||
{
|
||||
ScopedFastFlag luauModuleRequireErrorPack{"LuauModuleRequireErrorPack", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
return unfortunately()
|
||||
)";
|
||||
|
||||
CheckResult aResult = frontend.check("game/A");
|
||||
LUAU_REQUIRE_ERRORS(aResult);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local ModuleA = require(game.A)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
std::optional<TypeId> oty = requireType("ModuleA");
|
||||
CHECK_EQ("*unknown*", toString(*oty));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -296,9 +296,6 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Packed<T...> = (T...) -> T...
|
||||
local a: Packed<>
|
||||
@ -360,9 +357,6 @@ local c: Packed<string, number, boolean>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_import")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type Packed<T, U...> = { a: T, b: (U...) -> () }
|
||||
return {}
|
||||
@ -393,9 +387,6 @@ local d: { a: typeof(c) }
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_pack_type_parameters")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type Packed<T, U...> = { a: T, b: (U...) -> () }
|
||||
return {}
|
||||
@ -431,9 +422,6 @@ type C<X...> = Import.Packed<string, (number, X...)>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_nested")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Packed1<T...> = (T...) -> (T...)
|
||||
type Packed2<T...> = (Packed1<T...>, T...) -> (Packed1<T...>, T...)
|
||||
@ -452,9 +440,6 @@ type Packed4<T...> = (Packed3<T...>, T...) -> (Packed3<T...>, T...)
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_variadic")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X<T...> = (T...) -> (string, T...)
|
||||
|
||||
@ -470,9 +455,6 @@ type E = X<(number, ...string)>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_multi")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Y<T..., U...> = (T...) -> (U...)
|
||||
type A<S...> = Y<S..., S...>
|
||||
@ -501,9 +483,6 @@ type I<S..., R...> = W<number, (string, S...), R...>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X<T...> = (T...) -> (T...)
|
||||
|
||||
@ -527,9 +506,6 @@ type F = X<(string, ...number)>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Y<T..., U...> = (T...) -> (U...)
|
||||
|
||||
@ -549,9 +525,6 @@ type D<X...> = Y<X..., (number, string, X...)>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi_tostring")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Y<T..., U...> = { f: (T...) -> (U...) }
|
||||
|
||||
@ -567,9 +540,6 @@ local b: Y<(), ()>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_backwards_compatible")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X<T> = () -> T
|
||||
type Y<T, U> = (T) -> U
|
||||
@ -588,9 +558,6 @@ type C = Y<(number), boolean>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_errors")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Packed<T, U, V...> = (T, U) -> (V...)
|
||||
local b: Packed<number>
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
#include "ScopedFlags.h"
|
||||
@ -323,4 +324,48 @@ TEST_CASE("tagging_props")
|
||||
CHECK(Luau::hasTag(prop, "foo"));
|
||||
}
|
||||
|
||||
struct VisitCountTracker
|
||||
{
|
||||
std::unordered_map<TypeId, unsigned> tyVisits;
|
||||
std::unordered_map<TypePackId, unsigned> tpVisits;
|
||||
|
||||
void cycle(TypeId) {}
|
||||
void cycle(TypePackId) {}
|
||||
|
||||
template<typename T>
|
||||
bool operator()(TypeId ty, const T& t)
|
||||
{
|
||||
tyVisits[ty]++;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator()(TypePackId tp, const T&)
|
||||
{
|
||||
tpVisits[tp]++;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "visit_once")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type T = { a: number, b: () -> () }
|
||||
local b: (T, T, T) -> T
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId bType = requireType("b");
|
||||
|
||||
VisitCountTracker tester;
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(bType, tester, seen);
|
||||
|
||||
for (auto [_, count] : tester.tyVisits)
|
||||
CHECK_EQ(count, 1);
|
||||
|
||||
for (auto [_, count] : tester.tpVisits)
|
||||
CHECK_EQ(count, 1);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -2,7 +2,13 @@
|
||||
print('testing function calls through API')
|
||||
|
||||
function add(a, b)
|
||||
return a + b
|
||||
return a + b
|
||||
end
|
||||
|
||||
local m = { __eq = function(a, b) return a.a == b.a end }
|
||||
|
||||
function create_with_tm(x)
|
||||
return setmetatable({ a = x }, m)
|
||||
end
|
||||
|
||||
return('OK')
|
||||
|
@ -441,7 +441,8 @@ assert((function() a = {} b = {} mt = { __eq = function(l, r) return #l == #r en
|
||||
assert((function() a = {} b = {} function eq(l, r) return #l == #r end setmetatable(a, {__eq = eq}) setmetatable(b, {__eq = eq}) return concat(a == b, a ~= b) end)() == "true,false")
|
||||
assert((function() a = {} b = {} setmetatable(a, {__eq = function(l, r) return #l == #r end}) setmetatable(b, {__eq = function(l, r) return #l == #r end}) return concat(a == b, a ~= b) end)() == "false,true")
|
||||
|
||||
-- userdata, reference equality (no mt)
|
||||
-- userdata, reference equality (no mt or mt.__eq)
|
||||
assert((function() a = newproxy() return concat(a == newproxy(),a ~= newproxy()) end)() == "false,true")
|
||||
assert((function() a = newproxy(true) return concat(a == newproxy(true),a ~= newproxy(true)) end)() == "false,true")
|
||||
|
||||
-- rawequal
|
||||
|
@ -458,13 +458,16 @@ def display(root, title, colors, flip = False):
|
||||
|
||||
framewidth = 1200 - 20
|
||||
|
||||
def pixels(x):
|
||||
return float(x) / root.width * framewidth if root.width > 0 else 0
|
||||
|
||||
for n in root.subtree():
|
||||
if n.width / root.width * framewidth < 0.1:
|
||||
if pixels(n.width) < 0.1:
|
||||
continue
|
||||
|
||||
x = 10 + n.offset / root.width * framewidth
|
||||
x = 10 + pixels(n.offset)
|
||||
y = (maxdepth - 1 - n.depth if flip else n.depth) * 16 + 3 * 16
|
||||
width = n.width / root.width * framewidth
|
||||
width = pixels(n.width)
|
||||
height = 15
|
||||
|
||||
if colors == "cold":
|
||||
|
Loading…
Reference in New Issue
Block a user