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