mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Merge branch 'master' into web-repl-improvements
This commit is contained in:
commit
deb21154d3
116 changed files with 4658 additions and 3056 deletions
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
|
@ -67,13 +67,18 @@ jobs:
|
|||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: install
|
||||
run: |
|
||||
sudo apt install llvm
|
||||
- name: make coverage
|
||||
run: |
|
||||
CXX=clang++-10 make -j2 config=coverage coverage
|
||||
- name: debug coverage
|
||||
run: |
|
||||
git status
|
||||
git log -5
|
||||
echo SHA: $GITHUB_SHA
|
||||
- name: upload coverage
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
|
|
|
@ -277,11 +277,20 @@ struct MissingUnionProperty
|
|||
bool operator==(const MissingUnionProperty& rhs) const;
|
||||
};
|
||||
|
||||
struct TypesAreUnrelated
|
||||
{
|
||||
TypeId left;
|
||||
TypeId right;
|
||||
|
||||
bool operator==(const TypesAreUnrelated& rhs) const;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError,
|
||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty>;
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
|
||||
TypesAreUnrelated>;
|
||||
|
||||
struct TypeError
|
||||
{
|
||||
|
|
|
@ -51,13 +51,6 @@ struct FileResolver
|
|||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// DEPRECATED APIS
|
||||
// These are going to be removed with LuauNewRequireTrace2
|
||||
virtual bool moduleExists(const ModuleName& name) const = 0;
|
||||
virtual std::optional<ModuleName> fromAstFragment(AstExpr* expr) const = 0;
|
||||
virtual ModuleName concat(const ModuleName& lhs, std::string_view rhs) const = 0;
|
||||
virtual std::optional<ModuleName> getParentModuleName(const ModuleName& name) const = 0;
|
||||
};
|
||||
|
||||
struct NullFileResolver : FileResolver
|
||||
|
@ -66,22 +59,6 @@ struct NullFileResolver : FileResolver
|
|||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
bool moduleExists(const ModuleName& name) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs;
|
||||
}
|
||||
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -36,6 +36,10 @@ std::ostream& operator<<(std::ostream& lhs, const IllegalRequire& error);
|
|||
std::ostream& operator<<(std::ostream& lhs, const ModuleHasCyclicDependency& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const DuplicateGenericParameter& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const CannotInferBinaryOperation& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const SwappedGenericTypeParameter& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const OptionalValueAccess& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const MissingUnionProperty& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypesAreUnrelated& error);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TableState& tv);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeVar& tv);
|
||||
|
|
|
@ -78,9 +78,15 @@ void unfreeze(TypeArena& arena);
|
|||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
struct CloneState
|
||||
{
|
||||
int recursionCount = 0;
|
||||
bool encounteredFreeType = false;
|
||||
};
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState);
|
||||
|
||||
struct Module
|
||||
{
|
||||
|
|
31
Analysis/include/Luau/ToDot.h
Normal file
31
Analysis/include/Luau/ToDot.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
struct TypeVar;
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
struct ToDotOptions
|
||||
{
|
||||
bool showPointers = true; // Show pointer value in the node label
|
||||
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
|
||||
};
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts);
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts);
|
||||
|
||||
std::string toDot(TypeId ty);
|
||||
std::string toDot(TypePackId tp);
|
||||
|
||||
void dumpDot(TypeId ty);
|
||||
void dumpDot(TypePackId tp);
|
||||
|
||||
} // namespace Luau
|
|
@ -69,8 +69,8 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
|
|||
|
||||
// It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class
|
||||
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression
|
||||
void dump(TypeId ty);
|
||||
void dump(TypePackId ty);
|
||||
std::string dump(TypeId ty);
|
||||
std::string dump(TypePackId ty);
|
||||
|
||||
std::string generateName(size_t n);
|
||||
|
||||
|
|
|
@ -25,15 +25,6 @@ struct TxnLog
|
|||
{
|
||||
}
|
||||
|
||||
explicit TxnLog(const std::vector<std::pair<TypeId, TypeId>>& ownedSeen)
|
||||
: originalSeenSize(ownedSeen.size())
|
||||
, ownedSeen(ownedSeen)
|
||||
, sharedSeen(nullptr)
|
||||
{
|
||||
// This is deprecated!
|
||||
LUAU_ASSERT(!FFlag::LuauShareTxnSeen);
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
|
||||
|
|
|
@ -156,13 +156,14 @@ struct TypeChecker
|
|||
|
||||
// Returns both the type of the lvalue and its binding (if the caller wants to mutate the binding).
|
||||
// Note: the binding may be null.
|
||||
// TODO: remove second return value with FFlagLuauUpdateFunctionNameBinding
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExpr& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||
|
||||
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName);
|
||||
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level);
|
||||
std::pair<TypeId, ScopePtr> checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
|
||||
std::optional<Location> originalNameLoc, std::optional<TypeId> expectedType);
|
||||
void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function);
|
||||
|
@ -174,7 +175,7 @@ struct TypeChecker
|
|||
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExprCall& expr);
|
||||
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
|
||||
std::optional<ExprResult<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>& argLocations, const ExprResult<TypePackId>& argListResult,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const ExprResult<TypePackId>& argListResult,
|
||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors);
|
||||
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
|
||||
const std::vector<OverloadErrorEntry>& errors);
|
||||
|
@ -277,7 +278,7 @@ public:
|
|||
[[noreturn]] void ice(const std::string& message);
|
||||
|
||||
ScopePtr childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
ScopePtr childScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
ScopePtr childScope(const ScopePtr& parent, const Location& location);
|
||||
|
||||
// Wrapper for merge(l, r, toUnion) but without the lambda junk.
|
||||
void merge(RefinementMap& l, const RefinementMap& r);
|
||||
|
@ -297,7 +298,6 @@ private:
|
|||
|
||||
private:
|
||||
Unifier mkUnifier(const Location& location);
|
||||
Unifier mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location);
|
||||
|
||||
// These functions are only safe to call when we are in the process of typechecking a module.
|
||||
|
||||
|
|
|
@ -499,6 +499,7 @@ struct SingletonTypes
|
|||
const TypePackId anyTypePack;
|
||||
|
||||
SingletonTypes();
|
||||
~SingletonTypes();
|
||||
SingletonTypes(const SingletonTypes&) = delete;
|
||||
void operator=(const SingletonTypes&) = delete;
|
||||
|
||||
|
@ -509,38 +510,22 @@ struct SingletonTypes
|
|||
|
||||
private:
|
||||
std::unique_ptr<struct TypeArena> arena;
|
||||
bool debugFreezeArena = false;
|
||||
|
||||
TypeId makeStringMetatable();
|
||||
};
|
||||
|
||||
extern SingletonTypes singletonTypes;
|
||||
SingletonTypes& getSingletonTypes();
|
||||
|
||||
void persist(TypeId ty);
|
||||
void persist(TypePackId tp);
|
||||
|
||||
struct ToDotOptions
|
||||
{
|
||||
bool showPointers = true; // Show pointer value in the node label
|
||||
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
|
||||
};
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts);
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts);
|
||||
|
||||
std::string toDot(TypeId ty);
|
||||
std::string toDot(TypePackId tp);
|
||||
|
||||
void dumpDot(TypeId ty);
|
||||
void dumpDot(TypePackId tp);
|
||||
|
||||
const TypeLevel* getLevel(TypeId ty);
|
||||
TypeLevel* getMutableLevel(TypeId ty);
|
||||
|
||||
const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name);
|
||||
bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent);
|
||||
|
||||
bool hasGeneric(TypeId ty);
|
||||
bool hasGeneric(TypePackId tp);
|
||||
|
||||
TypeVar* asMutable(TypeId ty);
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -24,7 +24,7 @@ struct TypeLevel
|
|||
int level = 0;
|
||||
int subLevel = 0;
|
||||
|
||||
// Returns true if the typelevel "this" is "bigger" than rhs
|
||||
// Returns true if the level of "this" belongs to an equal or larger scope than that of rhs
|
||||
bool subsumes(const TypeLevel& rhs) const
|
||||
{
|
||||
if (level < rhs.level)
|
||||
|
@ -38,6 +38,15 @@ struct TypeLevel
|
|||
return false;
|
||||
}
|
||||
|
||||
// Returns true if the level of "this" belongs to a larger (not equal) scope than that of rhs
|
||||
bool subsumesStrict(const TypeLevel& rhs) const
|
||||
{
|
||||
if (level == rhs.level && subLevel == rhs.subLevel)
|
||||
return false;
|
||||
else
|
||||
return subsumes(rhs);
|
||||
}
|
||||
|
||||
TypeLevel incr() const
|
||||
{
|
||||
TypeLevel result;
|
||||
|
|
|
@ -19,12 +19,6 @@ enum Variance
|
|||
Invariant
|
||||
};
|
||||
|
||||
struct UnifierCounters
|
||||
{
|
||||
int recursionCount = 0;
|
||||
int iterationCount = 0;
|
||||
};
|
||||
|
||||
struct Unifier
|
||||
{
|
||||
TypeArena* const types;
|
||||
|
@ -37,20 +31,11 @@ struct Unifier
|
|||
Variance variance = Covariant;
|
||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||
|
||||
UnifierCounters* counters;
|
||||
UnifierCounters countersData;
|
||||
|
||||
std::shared_ptr<UnifierCounters> counters_DEPRECATED;
|
||||
|
||||
UnifierSharedState& sharedState;
|
||||
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& ownedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED = nullptr,
|
||||
UnifierCounters* counters = nullptr);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED = nullptr,
|
||||
UnifierCounters* counters = nullptr);
|
||||
Variance variance, UnifierSharedState& sharedState);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy);
|
||||
|
@ -92,9 +77,9 @@ private:
|
|||
public:
|
||||
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
|
||||
void occursCheck(TypeId needle, TypeId haystack);
|
||||
void occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
void occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
void occursCheck(TypePackId needle, TypePackId haystack);
|
||||
void occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
void occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
|
||||
Unifier makeChildUnifier();
|
||||
|
||||
|
@ -107,9 +92,8 @@ private:
|
|||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
|
||||
// Remove with FFlagLuauCacheUnifyTableResults
|
||||
DenseHashSet<TypeId> tempSeenTy_DEPRECATED{nullptr};
|
||||
DenseHashSet<TypePackId> tempSeenTp_DEPRECATED{nullptr};
|
||||
// Available after regular type pack unification errors
|
||||
std::optional<int> firstPackErrorPos;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -24,6 +24,12 @@ struct TypeIdPairHash
|
|||
}
|
||||
};
|
||||
|
||||
struct UnifierCounters
|
||||
{
|
||||
int recursionCount = 0;
|
||||
int iterationCount = 0;
|
||||
};
|
||||
|
||||
struct UnifierSharedState
|
||||
{
|
||||
UnifierSharedState(InternalErrorReporter* iceHandler)
|
||||
|
@ -39,6 +45,8 @@ struct UnifierSharedState
|
|||
|
||||
DenseHashSet<TypeId> tempSeenTy{nullptr};
|
||||
DenseHashSet<TypePackId> tempSeenTp{nullptr};
|
||||
|
||||
UnifierCounters counters;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCacheUnifyTableResults)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -101,7 +99,7 @@ void visit(TypeId ty, F& f, Set& seen)
|
|||
// Some visitors want to see bound tables, that's why we visit the original type
|
||||
if (apply(ty, *ttv, seen, f))
|
||||
{
|
||||
if (FFlag::LuauCacheUnifyTableResults && ttv->boundTo)
|
||||
if (ttv->boundTo)
|
||||
{
|
||||
visit(*ttv->boundTo, f, seen);
|
||||
}
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(ElseElseIfCompletionImprovements, false);
|
||||
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteFirstArg, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
|
@ -190,7 +191,48 @@ static ParenthesesRecommendation getParenRecommendation(TypeId id, const std::ve
|
|||
return ParenthesesRecommendation::None;
|
||||
}
|
||||
|
||||
static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typeArena, AstNode* node, TypeId ty)
|
||||
static std::optional<TypeId> findExpectedTypeAt(const Module& module, AstNode* node, Position position)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAutocompleteFirstArg);
|
||||
|
||||
auto expr = node->asExpr();
|
||||
if (!expr)
|
||||
return std::nullopt;
|
||||
|
||||
// Extra care for first function call argument location
|
||||
// When we don't have anything inside () yet, we also don't have an AST node to base our lookup
|
||||
if (AstExprCall* exprCall = expr->as<AstExprCall>())
|
||||
{
|
||||
if (exprCall->args.size == 0 && exprCall->argLocation.contains(position))
|
||||
{
|
||||
auto it = module.astTypes.find(exprCall->func);
|
||||
|
||||
if (!it)
|
||||
return std::nullopt;
|
||||
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*it));
|
||||
|
||||
if (!ftv)
|
||||
return std::nullopt;
|
||||
|
||||
auto [head, tail] = flatten(ftv->argTypes);
|
||||
unsigned index = exprCall->self ? 1 : 0;
|
||||
|
||||
if (index < head.size())
|
||||
return head[index];
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto it = module.astExpectedTypes.find(expr);
|
||||
if (!it)
|
||||
return std::nullopt;
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typeArena, AstNode* node, Position position, TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
|
@ -203,8 +245,9 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
|||
{
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, nullptr);
|
||||
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, nullptr);
|
||||
CloneState cloneState;
|
||||
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
auto errors = unifier.canUnify(expectedType, actualType);
|
||||
return errors.empty();
|
||||
|
@ -219,38 +262,75 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
|||
}
|
||||
};
|
||||
|
||||
auto expr = node->asExpr();
|
||||
if (!expr)
|
||||
return TypeCorrectKind::None;
|
||||
TypeId expectedType;
|
||||
|
||||
auto it = module.astExpectedTypes.find(expr);
|
||||
if (!it)
|
||||
return TypeCorrectKind::None;
|
||||
|
||||
TypeId expectedType = follow(*it);
|
||||
|
||||
if (canUnify(expectedType, ty))
|
||||
return TypeCorrectKind::Correct;
|
||||
|
||||
// We also want to suggest functions that return compatible result
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
|
||||
|
||||
if (!ftv)
|
||||
return TypeCorrectKind::None;
|
||||
|
||||
auto [retHead, retTail] = flatten(ftv->retType);
|
||||
|
||||
if (!retHead.empty())
|
||||
return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
|
||||
// We might only have a variadic tail pack, check if the element is compatible
|
||||
if (retTail)
|
||||
if (FFlag::LuauAutocompleteFirstArg)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)))
|
||||
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
auto typeAtPosition = findExpectedTypeAt(module, node, position);
|
||||
|
||||
if (!typeAtPosition)
|
||||
return TypeCorrectKind::None;
|
||||
|
||||
expectedType = follow(*typeAtPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto expr = node->asExpr();
|
||||
if (!expr)
|
||||
return TypeCorrectKind::None;
|
||||
|
||||
auto it = module.astExpectedTypes.find(expr);
|
||||
if (!it)
|
||||
return TypeCorrectKind::None;
|
||||
|
||||
expectedType = follow(*it);
|
||||
}
|
||||
|
||||
return TypeCorrectKind::None;
|
||||
if (FFlag::LuauAutocompletePreferToCallFunctions)
|
||||
{
|
||||
// We also want to suggest functions that return compatible result
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
auto [retHead, retTail] = flatten(ftv->retType);
|
||||
|
||||
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
|
||||
|
@ -309,8 +389,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
|
|||
if (result.count(name) == 0 && name != Parser::errorName)
|
||||
{
|
||||
Luau::TypeId type = Luau::follow(prop.type);
|
||||
TypeCorrectKind typeCorrect =
|
||||
indexType == PropIndexType::Key ? TypeCorrectKind::Correct : checkTypeCorrectKind(module, typeArena, nodes.back(), type);
|
||||
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct
|
||||
: checkTypeCorrectKind(module, typeArena, nodes.back(), {{}, {}}, type);
|
||||
ParenthesesRecommendation parens =
|
||||
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
|
||||
|
||||
|
@ -668,17 +748,31 @@ std::optional<const T*> returnFirstNonnullOptionOfType(const UnionTypeVar* utv)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* node)
|
||||
static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* node, Position position)
|
||||
{
|
||||
auto expr = node->asExpr();
|
||||
if (!expr)
|
||||
return std::nullopt;
|
||||
TypeId expectedType;
|
||||
|
||||
auto it = module.astExpectedTypes.find(expr);
|
||||
if (!it)
|
||||
return std::nullopt;
|
||||
if (FFlag::LuauAutocompleteFirstArg)
|
||||
{
|
||||
auto typeAtPosition = findExpectedTypeAt(module, node, position);
|
||||
|
||||
TypeId expectedType = follow(*it);
|
||||
if (!typeAtPosition)
|
||||
return std::nullopt;
|
||||
|
||||
expectedType = follow(*typeAtPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto expr = node->asExpr();
|
||||
if (!expr)
|
||||
return std::nullopt;
|
||||
|
||||
auto it = module.astExpectedTypes.find(expr);
|
||||
if (!it)
|
||||
return std::nullopt;
|
||||
|
||||
expectedType = follow(*it);
|
||||
}
|
||||
|
||||
if (get<FunctionTypeVar>(expectedType))
|
||||
return true;
|
||||
|
@ -1147,7 +1241,7 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
|
|||
std::string n = toString(name);
|
||||
if (!result.count(n))
|
||||
{
|
||||
TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, node, binding.typeId);
|
||||
TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, node, position, binding.typeId);
|
||||
|
||||
result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt,
|
||||
binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)};
|
||||
|
@ -1157,9 +1251,10 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
|
|||
scope = scope->parent;
|
||||
}
|
||||
|
||||
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, typeChecker.nilType);
|
||||
TypeCorrectKind correctForBoolean = checkTypeCorrectKind(module, typeArena, node, typeChecker.booleanType);
|
||||
TypeCorrectKind correctForFunction = functionIsExpectedAt(module, node).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.nilType);
|
||||
TypeCorrectKind correctForBoolean = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.booleanType);
|
||||
TypeCorrectKind correctForFunction =
|
||||
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||
|
||||
if (FFlag::LuauIfElseExpressionAnalysisSupport)
|
||||
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false};
|
||||
|
@ -1413,7 +1508,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
|
||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); FFlag::ElseElseIfCompletionImprovements && statIf && !statIf->hasElse)
|
||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->hasElse)
|
||||
{
|
||||
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
||||
finder.ancestry};
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauNewRequireTrace2)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
|
||||
|
@ -219,9 +217,9 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
|||
|
||||
TypeId genericK = arena.addType(GenericTypeVar{"K"});
|
||||
TypeId genericV = arena.addType(GenericTypeVar{"V"});
|
||||
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level});
|
||||
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic});
|
||||
|
||||
std::optional<TypeId> stringMetatableTy = getMetatable(singletonTypes.stringType);
|
||||
std::optional<TypeId> stringMetatableTy = getMetatable(getSingletonTypes().stringType);
|
||||
LUAU_ASSERT(stringMetatableTy);
|
||||
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
|
||||
LUAU_ASSERT(stringMetatableTable);
|
||||
|
@ -273,7 +271,10 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
|||
persist(pair.second.typeId);
|
||||
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(pair.second.typeId))
|
||||
ttv->name = toString(pair.first);
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = toString(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
|
||||
|
@ -473,9 +474,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
|||
if (!checkRequirePath(typechecker, expr.args.data[0]))
|
||||
return std::nullopt;
|
||||
|
||||
const AstExpr* require = FFlag::LuauNewRequireTrace2 ? &expr : expr.args.data[0];
|
||||
|
||||
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require))
|
||||
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr))
|
||||
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
|
||||
|
||||
return std::nullopt;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixTonumberReturnType, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -113,7 +115,6 @@ declare function gcinfo(): number
|
|||
declare function error<T>(message: T, level: number?)
|
||||
|
||||
declare function tostring<T>(value: T): string
|
||||
declare function tonumber<T>(value: T, radix: number?): number
|
||||
|
||||
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
|
||||
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
|
||||
|
@ -204,7 +205,14 @@ declare function gcinfo(): number
|
|||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
return kBuiltinDefinitionLuaSrc;
|
||||
std::string result = kBuiltinDefinitionLuaSrc;
|
||||
|
||||
if (FFlag::LuauFixTonumberReturnType)
|
||||
result += "declare function tonumber<T>(value: T, radix: number?): number?\n";
|
||||
else
|
||||
result += "declare function tonumber<T>(value: T, radix: number?): number\n";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -7,57 +7,14 @@
|
|||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
static std::string wrongNumberOfArgsString_DEPRECATED(size_t expectedCount, size_t actualCount, bool isTypeArgs = false)
|
||||
{
|
||||
std::string s = "expects " + std::to_string(expectedCount) + " ";
|
||||
|
||||
if (isTypeArgs)
|
||||
s += "type ";
|
||||
|
||||
s += "argument";
|
||||
if (expectedCount != 1)
|
||||
s += "s";
|
||||
|
||||
s += ", but ";
|
||||
|
||||
if (actualCount == 0)
|
||||
{
|
||||
s += "none";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actualCount < expectedCount)
|
||||
s += "only ";
|
||||
|
||||
s += std::to_string(actualCount);
|
||||
}
|
||||
|
||||
s += (actualCount == 1) ? " is" : " are";
|
||||
|
||||
s += " specified";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||
{
|
||||
std::string s;
|
||||
std::string s = "expects ";
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
s = "expects ";
|
||||
if (isVariadic)
|
||||
s += "at least ";
|
||||
|
||||
if (isVariadic)
|
||||
s += "at least ";
|
||||
|
||||
s += std::to_string(expectedCount) + " ";
|
||||
}
|
||||
else
|
||||
{
|
||||
s = "expects " + std::to_string(expectedCount) + " ";
|
||||
}
|
||||
s += std::to_string(expectedCount) + " ";
|
||||
|
||||
if (argPrefix)
|
||||
s += std::string(argPrefix) + " ";
|
||||
|
@ -101,7 +58,7 @@ struct ErrorConverter
|
|||
result += "\ncaused by:\n ";
|
||||
|
||||
if (!tm.reason.empty())
|
||||
result += tm.reason + ". ";
|
||||
result += tm.reason + " ";
|
||||
|
||||
result += Luau::toString(*tm.error);
|
||||
}
|
||||
|
@ -188,10 +145,7 @@ struct ErrorConverter
|
|||
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
|
||||
" are required here";
|
||||
case CountMismatch::Arg:
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
|
||||
else
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString_DEPRECATED(e.expected, e.actual);
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unknown context");
|
||||
|
@ -232,7 +186,7 @@ struct ErrorConverter
|
|||
std::string operator()(const Luau::IncorrectGenericParameterCount& e) const
|
||||
{
|
||||
std::string name = e.name;
|
||||
if (!e.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !e.typeFun.typePackParams.empty()))
|
||||
if (!e.typeFun.typeParams.empty() || !e.typeFun.typePackParams.empty())
|
||||
{
|
||||
name += "<";
|
||||
bool first = true;
|
||||
|
@ -246,36 +200,25 @@ struct ErrorConverter
|
|||
name += toString(t);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (TypePackId t : e.typeFun.typePackParams)
|
||||
{
|
||||
for (TypePackId t : e.typeFun.typePackParams)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
name += ", ";
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
name += ", ";
|
||||
|
||||
name += toString(t);
|
||||
}
|
||||
name += toString(t);
|
||||
}
|
||||
|
||||
name += ">";
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
if (e.typeFun.typeParams.size() != e.actualParameters)
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
|
||||
if (e.typeFun.typeParams.size() != e.actualParameters)
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
|
||||
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString_DEPRECATED(e.typeFun.typeParams.size(), e.actualParameters, /*isTypeArgs*/ true);
|
||||
}
|
||||
return "Generic type '" + name + "' " +
|
||||
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::SyntaxError& e) const
|
||||
|
@ -467,6 +410,11 @@ struct ErrorConverter
|
|||
|
||||
return ss + " in the type '" + toString(e.type) + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const TypesAreUnrelated& e) const
|
||||
{
|
||||
return "Cannot cast '" + toString(e.left) + "' into '" + toString(e.right) + "' because the types are unrelated";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
|
@ -591,11 +539,8 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
|
|||
if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size())
|
||||
return false;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
|
||||
return false;
|
||||
}
|
||||
if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < typeFun.typeParams.size(); ++i)
|
||||
{
|
||||
|
@ -603,13 +548,10 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
|
|||
return false;
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (size_t i = 0; i < typeFun.typePackParams.size(); ++i)
|
||||
{
|
||||
for (size_t i = 0; i < typeFun.typePackParams.size(); ++i)
|
||||
{
|
||||
if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
|
||||
return false;
|
||||
}
|
||||
if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -721,6 +663,11 @@ bool MissingUnionProperty::operator==(const MissingUnionProperty& rhs) const
|
|||
return *type == *rhs.type && key == rhs.key;
|
||||
}
|
||||
|
||||
bool TypesAreUnrelated::operator==(const TypesAreUnrelated& rhs) const
|
||||
{
|
||||
return left == rhs.left && right == rhs.right;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
ErrorConverter converter;
|
||||
|
@ -733,14 +680,14 @@ bool containsParseErrorName(const TypeError& error)
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState cloneState)
|
||||
{
|
||||
auto clone = [&](auto&& ty) {
|
||||
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks);
|
||||
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks, cloneState);
|
||||
};
|
||||
|
||||
auto visitErrorData = [&](auto&& e) {
|
||||
copyError(e, destArena, seenTypes, seenTypePacks);
|
||||
copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
|
||||
};
|
||||
|
||||
if constexpr (false)
|
||||
|
@ -856,6 +803,11 @@ void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks&
|
|||
for (auto& ty : e.missing)
|
||||
ty = clone(ty);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypesAreUnrelated>)
|
||||
{
|
||||
e.left = clone(e.left);
|
||||
e.right = clone(e.right);
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -864,9 +816,10 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
|
|||
{
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
auto visitErrorData = [&](auto&& e) {
|
||||
copyError(e, destArena, seenTypes, seenTypePacks);
|
||||
copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
|
||||
};
|
||||
|
||||
LUAU_ASSERT(!destArena.typeVars.isFrozen());
|
||||
|
|
|
@ -18,10 +18,7 @@
|
|||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauResolveModuleNameWithoutACurrentModule, false)
|
||||
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistDefinitionFileTypes, false)
|
||||
LUAU_FASTFLAG(LuauNewRequireTrace2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -96,10 +93,11 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
@ -110,7 +108,7 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
|||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
@ -427,15 +425,16 @@ CheckResult Frontend::check(const ModuleName& name)
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astTypes)
|
||||
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes)
|
||||
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astExpectedTypes)
|
||||
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
|
@ -885,16 +884,13 @@ std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const Module
|
|||
// If we can't find the current module name, that's because we bypassed the frontend's initializer
|
||||
// and called typeChecker.check directly. (This is done by autocompleteSource, for example).
|
||||
// In that case, requires will always fail.
|
||||
if (FFlag::LuauResolveModuleNameWithoutACurrentModule)
|
||||
return std::nullopt;
|
||||
else
|
||||
throw std::runtime_error("Frontend::resolveModuleName: Unknown currentModuleName '" + currentModuleName + "'");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto& exprs = it->second.exprs;
|
||||
|
||||
const ModuleInfo* info = exprs.find(&pathExpr);
|
||||
if (!info || (!FFlag::LuauNewRequireTrace2 && info->name.empty()))
|
||||
if (!info)
|
||||
return std::nullopt;
|
||||
|
||||
return *info;
|
||||
|
@ -911,10 +907,7 @@ const ModulePtr FrontendModuleResolver::getModule(const ModuleName& moduleName)
|
|||
|
||||
bool FrontendModuleResolver::moduleExists(const ModuleName& moduleName) const
|
||||
{
|
||||
if (FFlag::LuauNewRequireTrace2)
|
||||
return frontend->sourceNodes.count(moduleName) != 0;
|
||||
else
|
||||
return frontend->fileResolver->moduleExists(moduleName);
|
||||
return frontend->sourceNodes.count(moduleName) != 0;
|
||||
}
|
||||
|
||||
std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
#include "Luau/IostreamHelpers.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -94,7 +92,7 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo
|
|||
{
|
||||
stream << "IncorrectGenericParameterCount { name = " << error.name;
|
||||
|
||||
if (!error.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !error.typeFun.typePackParams.empty()))
|
||||
if (!error.typeFun.typeParams.empty() || !error.typeFun.typePackParams.empty())
|
||||
{
|
||||
stream << "<";
|
||||
bool first = true;
|
||||
|
@ -108,17 +106,14 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo
|
|||
stream << toString(t);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (TypePackId t : error.typeFun.typePackParams)
|
||||
{
|
||||
for (TypePackId t : error.typeFun.typePackParams)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
|
||||
stream << toString(t);
|
||||
}
|
||||
stream << toString(t);
|
||||
}
|
||||
|
||||
stream << ">";
|
||||
|
@ -267,6 +262,12 @@ std::ostream& operator<<(std::ostream& stream, const MissingUnionProperty& error
|
|||
return stream << " }, key = '" + error.key + "' }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const TypesAreUnrelated& error)
|
||||
{
|
||||
stream << "TypesAreUnrelated { left = '" + toString(error.left) + "', right = '" + toString(error.right) + "' }";
|
||||
return stream;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const TableState& tv)
|
||||
{
|
||||
return stream << static_cast<std::underlying_type<TableState>::type>(tv);
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -264,7 +262,7 @@ struct AstJsonEncoder : public AstVisitor
|
|||
if (comma)
|
||||
writeRaw(",");
|
||||
else
|
||||
comma = false;
|
||||
comma = true;
|
||||
|
||||
write(a);
|
||||
}
|
||||
|
@ -381,7 +379,7 @@ struct AstJsonEncoder : public AstVisitor
|
|||
if (comma)
|
||||
writeRaw(",");
|
||||
else
|
||||
comma = false;
|
||||
comma = true;
|
||||
write(prop);
|
||||
}
|
||||
});
|
||||
|
@ -615,12 +613,7 @@ struct AstJsonEncoder : public AstVisitor
|
|||
writeNode(node, "AstStatTypeAlias", [&]() {
|
||||
PROP(name);
|
||||
PROP(generics);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
PROP(genericPacks);
|
||||
}
|
||||
|
||||
PROP(genericPacks);
|
||||
PROP(type);
|
||||
PROP(exported);
|
||||
});
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Module.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
|
||||
LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneBoundTables, false)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -23,7 +22,7 @@ static bool contains(Position pos, Comment comment)
|
|||
{
|
||||
if (comment.location.contains(pos))
|
||||
return true;
|
||||
else if (FFlag::LuauCaptureBrokenCommentSpans && comment.type == Lexeme::BrokenComment &&
|
||||
else if (comment.type == Lexeme::BrokenComment &&
|
||||
comment.location.begin <= pos) // Broken comments are broken specifically because they don't have an end
|
||||
return true;
|
||||
else if (comment.type == Lexeme::Comment && comment.location.end == pos)
|
||||
|
@ -120,12 +119,6 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
|
|||
return allocated;
|
||||
}
|
||||
|
||||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -138,11 +131,12 @@ struct TypePackCloner;
|
|||
|
||||
struct TypeCloner
|
||||
{
|
||||
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
: dest(dest)
|
||||
, typeId(typeId)
|
||||
, seenTypes(seenTypes)
|
||||
, seenTypePacks(seenTypePacks)
|
||||
, cloneState(cloneState)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -150,8 +144,7 @@ struct TypeCloner
|
|||
TypeId typeId;
|
||||
SeenTypes& seenTypes;
|
||||
SeenTypePacks& seenTypePacks;
|
||||
|
||||
bool* encounteredFreeType = nullptr;
|
||||
CloneState& cloneState;
|
||||
|
||||
template<typename T>
|
||||
void defaultClone(const T& t);
|
||||
|
@ -178,13 +171,14 @@ struct TypePackCloner
|
|||
TypePackId typePackId;
|
||||
SeenTypes& seenTypes;
|
||||
SeenTypePacks& seenTypePacks;
|
||||
bool* encounteredFreeType = nullptr;
|
||||
CloneState& cloneState;
|
||||
|
||||
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
: dest(dest)
|
||||
, typePackId(typePackId)
|
||||
, seenTypes(seenTypes)
|
||||
, seenTypePacks(seenTypePacks)
|
||||
, cloneState(cloneState)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -197,10 +191,9 @@ struct TypePackCloner
|
|||
|
||||
void operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
TypePackId err = singletonTypes.errorRecoveryTypePack(singletonTypes.anyTypePack);
|
||||
TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
|
||||
TypePackId cloned = dest.addTypePack(*err);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
@ -218,13 +211,13 @@ struct TypePackCloner
|
|||
// We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
|
||||
void operator()(const Unifiable::Bound<TypePackId>& t)
|
||||
{
|
||||
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
void operator()(const VariadicTypePack& t)
|
||||
{
|
||||
TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, encounteredFreeType)}});
|
||||
TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, cloneState)}});
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
|
@ -236,10 +229,10 @@ struct TypePackCloner
|
|||
seenTypePacks[typePackId] = cloned;
|
||||
|
||||
for (TypeId ty : t.head)
|
||||
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
if (t.tail)
|
||||
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -252,9 +245,8 @@ void TypeCloner::defaultClone(const T& t)
|
|||
|
||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
TypeId err = singletonTypes.errorRecoveryType(singletonTypes.anyType);
|
||||
cloneState.encounteredFreeType = true;
|
||||
TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType);
|
||||
TypeId cloned = dest.addType(*err);
|
||||
seenTypes[typeId] = cloned;
|
||||
}
|
||||
|
@ -266,7 +258,7 @@ void TypeCloner::operator()(const Unifiable::Generic& t)
|
|||
|
||||
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
|
||||
{
|
||||
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
|
||||
seenTypes[typeId] = boundTo;
|
||||
}
|
||||
|
||||
|
@ -294,23 +286,23 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
|
|||
seenTypes[typeId] = result;
|
||||
|
||||
for (TypeId generic : t.generics)
|
||||
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
for (TypePackId genericPack : t.genericPacks)
|
||||
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
ftv->tags = t.tags;
|
||||
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, cloneState);
|
||||
ftv->argNames = t.argNames;
|
||||
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const TableTypeVar& t)
|
||||
{
|
||||
// If table is now bound to another one, we ignore the content of the original
|
||||
if (FFlag::LuauCloneBoundTables && t.boundTo)
|
||||
if (t.boundTo)
|
||||
{
|
||||
TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
|
||||
seenTypes[typeId] = boundTo;
|
||||
return;
|
||||
}
|
||||
|
@ -326,34 +318,21 @@ void TypeCloner::operator()(const TableTypeVar& t)
|
|||
ttv->level = TypeLevel{0, 0};
|
||||
|
||||
for (const auto& [name, prop] : t.props)
|
||||
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
|
||||
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
|
||||
|
||||
if (t.indexer)
|
||||
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, encounteredFreeType),
|
||||
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, encounteredFreeType)};
|
||||
|
||||
if (!FFlag::LuauCloneBoundTables)
|
||||
{
|
||||
if (t.boundTo)
|
||||
ttv->boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, cloneState),
|
||||
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, cloneState)};
|
||||
|
||||
for (TypeId& arg : ttv->instantiatedTypeParams)
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||
arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
if (ttv->state == TableState::Free)
|
||||
{
|
||||
if (FFlag::LuauCloneBoundTables || !t.boundTo)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
}
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
ttv->state = TableState::Sealed;
|
||||
}
|
||||
|
@ -369,8 +348,8 @@ void TypeCloner::operator()(const MetatableTypeVar& t)
|
|||
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result);
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, cloneState);
|
||||
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const ClassTypeVar& t)
|
||||
|
@ -381,13 +360,13 @@ void TypeCloner::operator()(const ClassTypeVar& t)
|
|||
seenTypes[typeId] = result;
|
||||
|
||||
for (const auto& [name, prop] : t.props)
|
||||
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
|
||||
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
|
||||
|
||||
if (t.parent)
|
||||
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
if (t.metatable)
|
||||
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const AnyTypeVar& t)
|
||||
|
@ -404,7 +383,7 @@ void TypeCloner::operator()(const UnionTypeVar& t)
|
|||
LUAU_ASSERT(option != nullptr);
|
||||
|
||||
for (TypeId ty : t.options)
|
||||
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const IntersectionTypeVar& t)
|
||||
|
@ -416,7 +395,7 @@ void TypeCloner::operator()(const IntersectionTypeVar& t)
|
|||
LUAU_ASSERT(option != nullptr);
|
||||
|
||||
for (TypeId ty : t.parts)
|
||||
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const LazyTypeVar& t)
|
||||
|
@ -426,60 +405,55 @@ void TypeCloner::operator()(const LazyTypeVar& t)
|
|||
|
||||
} // anonymous namespace
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
{
|
||||
if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
TypePackId& res = seenTypePacks[tp];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks};
|
||||
cloner.encounteredFreeType = encounteredFreeType;
|
||||
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks, cloneState};
|
||||
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(res)->owningArena = &dest;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
{
|
||||
if (typeId->persistent)
|
||||
return typeId;
|
||||
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
TypeId& res = seenTypes[typeId];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks};
|
||||
cloner.encounteredFreeType = encounteredFreeType;
|
||||
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
|
||||
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
|
||||
|
||||
// TODO: Make this work when the arena of 'res' might be frozen
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(res)->owningArena = &dest;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
|
||||
{
|
||||
TypeFun result;
|
||||
for (TypeId ty : typeFun.typeParams)
|
||||
result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId tp : typeFun.typePackParams)
|
||||
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
}
|
||||
for (TypePackId tp : typeFun.typePackParams)
|
||||
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, cloneState));
|
||||
|
||||
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -519,19 +493,18 @@ bool Module::clonePublicInterface()
|
|||
LUAU_ASSERT(interfaceTypes.typeVars.empty());
|
||||
LUAU_ASSERT(interfaceTypes.typePacks.empty());
|
||||
|
||||
bool encounteredFreeType = false;
|
||||
|
||||
SeenTypePacks seenTypePacks;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
ScopePtr moduleScope = getModuleScope();
|
||||
|
||||
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
if (moduleScope->varargPack)
|
||||
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (auto& pair : moduleScope->exportedTypeBindings)
|
||||
pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
||||
tf = clone(tf, interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (TypeId ty : moduleScope->returnType)
|
||||
if (get<GenericTypeVar>(follow(ty)))
|
||||
|
@ -540,7 +513,7 @@ bool Module::clonePublicInterface()
|
|||
freeze(internalTypes);
|
||||
freeze(interfaceTypes);
|
||||
|
||||
return encounteredFreeType;
|
||||
return cloneState.encounteredFreeType;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -24,7 +24,7 @@ std::optional<LValue> tryGetLValue(const AstExpr& node)
|
|||
else if (auto indexexpr = expr->as<AstExprIndexExpr>())
|
||||
{
|
||||
if (auto lvalue = tryGetLValue(*indexexpr->expr))
|
||||
if (auto string = indexexpr->expr->as<AstExprConstantString>())
|
||||
if (auto string = indexexpr->index->as<AstExprConstantString>())
|
||||
return Field{std::make_shared<LValue>(*lvalue), std::string(string->value.data, string->value.size)};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyVisitOnce, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -79,7 +81,16 @@ struct Quantifier
|
|||
void quantify(ModulePtr module, TypeId ty, TypeLevel level)
|
||||
{
|
||||
Quantifier q{std::move(module), level};
|
||||
visitTypeVar(ty, q);
|
||||
|
||||
if (FFlag::LuauQuantifyVisitOnce)
|
||||
{
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(ty, q, seen);
|
||||
}
|
||||
else
|
||||
{
|
||||
visitTypeVar(ty, q);
|
||||
}
|
||||
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
|
|
@ -4,182 +4,9 @@
|
|||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTraceRequireLookupChild, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewRequireTrace2, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct RequireTracerOld : AstVisitor
|
||||
{
|
||||
explicit RequireTracerOld(FileResolver* fileResolver, const ModuleName& currentModuleName)
|
||||
: fileResolver(fileResolver)
|
||||
, currentModuleName(currentModuleName)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauNewRequireTrace2);
|
||||
}
|
||||
|
||||
FileResolver* const fileResolver;
|
||||
ModuleName currentModuleName;
|
||||
DenseHashMap<AstLocal*, ModuleName> locals{nullptr};
|
||||
RequireTraceResult result;
|
||||
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr)
|
||||
{
|
||||
if (auto g = expr->as<AstExprGlobal>(); g && g->name == "script")
|
||||
return currentModuleName;
|
||||
|
||||
return fileResolver->fromAstFragment(expr);
|
||||
}
|
||||
|
||||
bool visit(AstStatLocal* stat) override
|
||||
{
|
||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||
{
|
||||
AstLocal* local = stat->vars.data[i];
|
||||
|
||||
if (local->annotation)
|
||||
{
|
||||
if (AstTypeTypeof* ann = local->annotation->as<AstTypeTypeof>())
|
||||
ann->expr->visit(this);
|
||||
}
|
||||
|
||||
if (i < stat->values.size)
|
||||
{
|
||||
AstExpr* expr = stat->values.data[i];
|
||||
expr->visit(this);
|
||||
|
||||
const ModuleInfo* info = result.exprs.find(expr);
|
||||
if (info)
|
||||
locals[local] = info->name;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprGlobal* global) override
|
||||
{
|
||||
std::optional<ModuleName> name = fromAstFragment(global);
|
||||
if (name)
|
||||
result.exprs[global] = {*name};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* local) override
|
||||
{
|
||||
const ModuleName* name = locals.find(local->local);
|
||||
if (name)
|
||||
result.exprs[local] = {*name};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexName* indexName) override
|
||||
{
|
||||
indexName->expr->visit(this);
|
||||
|
||||
const ModuleInfo* info = result.exprs.find(indexName->expr);
|
||||
if (info)
|
||||
{
|
||||
if (indexName->index == "parent" || indexName->index == "Parent")
|
||||
{
|
||||
if (auto parent = fileResolver->getParentModuleName(info->name))
|
||||
result.exprs[indexName] = {*parent};
|
||||
}
|
||||
else
|
||||
result.exprs[indexName] = {fileResolver->concat(info->name, indexName->index.value)};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexExpr* indexExpr) override
|
||||
{
|
||||
indexExpr->expr->visit(this);
|
||||
|
||||
const ModuleInfo* info = result.exprs.find(indexExpr->expr);
|
||||
const AstExprConstantString* str = indexExpr->index->as<AstExprConstantString>();
|
||||
if (info && str)
|
||||
{
|
||||
result.exprs[indexExpr] = {fileResolver->concat(info->name, std::string_view(str->value.data, str->value.size))};
|
||||
}
|
||||
|
||||
indexExpr->index->visit(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprTypeAssertion* expr) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we see game:GetService("StringLiteral") or Game:GetService("StringLiteral"), then rewrite to game.StringLiteral.
|
||||
// Else traverse arguments and trace requires to them.
|
||||
bool visit(AstExprCall* call) override
|
||||
{
|
||||
for (AstExpr* arg : call->args)
|
||||
arg->visit(this);
|
||||
|
||||
call->func->visit(this);
|
||||
|
||||
AstExprGlobal* globalName = call->func->as<AstExprGlobal>();
|
||||
if (globalName && globalName->name == "require" && call->args.size >= 1)
|
||||
{
|
||||
if (const ModuleInfo* moduleInfo = result.exprs.find(call->args.data[0]))
|
||||
result.requires.push_back({moduleInfo->name, call->location});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AstExprIndexName* indexName = call->func->as<AstExprIndexName>();
|
||||
if (!indexName)
|
||||
return false;
|
||||
|
||||
std::optional<ModuleName> rootName = fromAstFragment(indexName->expr);
|
||||
|
||||
if (FFlag::LuauTraceRequireLookupChild && !rootName)
|
||||
{
|
||||
if (const ModuleInfo* moduleInfo = result.exprs.find(indexName->expr))
|
||||
rootName = moduleInfo->name;
|
||||
}
|
||||
|
||||
if (!rootName)
|
||||
return false;
|
||||
|
||||
bool supportedLookup = indexName->index == "GetService" ||
|
||||
(FFlag::LuauTraceRequireLookupChild && (indexName->index == "FindFirstChild" || indexName->index == "WaitForChild"));
|
||||
|
||||
if (!supportedLookup)
|
||||
return false;
|
||||
|
||||
if (call->args.size != 1)
|
||||
return false;
|
||||
|
||||
AstExprConstantString* name = call->args.data[0]->as<AstExprConstantString>();
|
||||
if (!name)
|
||||
return false;
|
||||
|
||||
std::string_view v{name->value.data, name->value.size};
|
||||
if (v.end() != std::find(v.begin(), v.end(), '/'))
|
||||
return false;
|
||||
|
||||
result.exprs[call] = {fileResolver->concat(*rootName, v)};
|
||||
|
||||
// 'WaitForChild' can be used on modules that are not available at the typecheck time, but will be available at runtime
|
||||
// If we fail to find such module, we will not report an UnknownRequire error
|
||||
if (FFlag::LuauTraceRequireLookupChild && indexName->index == "WaitForChild")
|
||||
result.exprs[call].optional = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct RequireTracer : AstVisitor
|
||||
{
|
||||
RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName)
|
||||
|
@ -188,7 +15,6 @@ struct RequireTracer : AstVisitor
|
|||
, currentModuleName(currentModuleName)
|
||||
, locals(nullptr)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNewRequireTrace2);
|
||||
}
|
||||
|
||||
bool visit(AstExprTypeAssertion* expr) override
|
||||
|
@ -328,24 +154,13 @@ struct RequireTracer : AstVisitor
|
|||
std::vector<AstExprCall*> requires;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName)
|
||||
{
|
||||
if (FFlag::LuauNewRequireTrace2)
|
||||
{
|
||||
RequireTraceResult result;
|
||||
RequireTracer tracer{result, fileResolver, currentModuleName};
|
||||
root->visit(&tracer);
|
||||
tracer.process();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
RequireTracerOld tracer{fileResolver, currentModuleName};
|
||||
root->visit(&tracer);
|
||||
return tracer.result;
|
||||
}
|
||||
RequireTraceResult result;
|
||||
RequireTracer tracer{result, fileResolver, currentModuleName};
|
||||
root->visit(&tracer);
|
||||
tracer.process();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubstitutionDontReplaceIgnoredTypes, false)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -39,11 +37,8 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
|||
for (TypeId itp : ttv->instantiatedTypeParams)
|
||||
visitChild(itp);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp);
|
||||
}
|
||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp);
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
|
@ -339,10 +334,10 @@ std::optional<TypeId> Substitution::substitute(TypeId ty)
|
|||
return std::nullopt;
|
||||
|
||||
for (auto [oldTy, newTy] : newTypes)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy))
|
||||
if (!ignoreChildren(oldTy))
|
||||
replaceChildren(newTy);
|
||||
for (auto [oldTp, newTp] : newPacks)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp))
|
||||
if (!ignoreChildren(oldTp))
|
||||
replaceChildren(newTp);
|
||||
TypeId newTy = replace(ty);
|
||||
return newTy;
|
||||
|
@ -359,10 +354,10 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
|
|||
return std::nullopt;
|
||||
|
||||
for (auto [oldTy, newTy] : newTypes)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy))
|
||||
if (!ignoreChildren(oldTy))
|
||||
replaceChildren(newTy);
|
||||
for (auto [oldTp, newTp] : newPacks)
|
||||
if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp))
|
||||
if (!ignoreChildren(oldTp))
|
||||
replaceChildren(newTp);
|
||||
TypePackId newTp = replace(tp);
|
||||
return newTp;
|
||||
|
@ -393,10 +388,7 @@ TypeId Substitution::clone(TypeId ty)
|
|||
clone.name = ttv->name;
|
||||
clone.syntheticName = ttv->syntheticName;
|
||||
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
|
||||
|
||||
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
|
||||
clone.tags = ttv->tags;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
|
@ -505,11 +497,8 @@ void Substitution::replaceChildren(TypeId ty)
|
|||
for (TypeId& itp : ttv->instantiatedTypeParams)
|
||||
itp = replace(itp);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId& itp : ttv->instantiatedTypePackParams)
|
||||
itp = replace(itp);
|
||||
}
|
||||
for (TypePackId& itp : ttv->instantiatedTypePackParams)
|
||||
itp = replace(itp);
|
||||
}
|
||||
else if (MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(ty))
|
||||
{
|
||||
|
|
378
Analysis/src/ToDot.cpp
Normal file
378
Analysis/src/ToDot.cpp
Normal file
|
@ -0,0 +1,378 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/ToDot.h"
|
||||
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct StateDot
|
||||
{
|
||||
StateDot(ToDotOptions opts)
|
||||
: opts(opts)
|
||||
{
|
||||
}
|
||||
|
||||
ToDotOptions opts;
|
||||
|
||||
std::unordered_set<TypeId> seenTy;
|
||||
std::unordered_set<TypePackId> seenTp;
|
||||
std::unordered_map<TypeId, int> tyToIndex;
|
||||
std::unordered_map<TypePackId, int> tpToIndex;
|
||||
int nextIndex = 1;
|
||||
std::string result;
|
||||
|
||||
bool canDuplicatePrimitive(TypeId ty);
|
||||
|
||||
void visitChildren(TypeId ty, int index);
|
||||
void visitChildren(TypePackId ty, int index);
|
||||
|
||||
void visitChild(TypeId ty, int parentIndex, const char* linkName = nullptr);
|
||||
void visitChild(TypePackId tp, int parentIndex, const char* linkName = nullptr);
|
||||
|
||||
void startNode(int index);
|
||||
void finishNode();
|
||||
|
||||
void startNodeLabel();
|
||||
void finishNodeLabel(TypeId ty);
|
||||
void finishNodeLabel(TypePackId tp);
|
||||
};
|
||||
|
||||
bool StateDot::canDuplicatePrimitive(TypeId ty)
|
||||
{
|
||||
if (get<BoundTypeVar>(ty))
|
||||
return false;
|
||||
|
||||
return get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty);
|
||||
}
|
||||
|
||||
void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName)
|
||||
{
|
||||
if (!tyToIndex.count(ty) || (opts.duplicatePrimitives && canDuplicatePrimitive(ty)))
|
||||
tyToIndex[ty] = nextIndex++;
|
||||
|
||||
int index = tyToIndex[ty];
|
||||
|
||||
if (parentIndex != 0)
|
||||
{
|
||||
if (linkName)
|
||||
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, index, linkName);
|
||||
else
|
||||
formatAppend(result, "n%d -> n%d;\n", parentIndex, index);
|
||||
}
|
||||
|
||||
if (opts.duplicatePrimitives && canDuplicatePrimitive(ty))
|
||||
{
|
||||
if (get<PrimitiveTypeVar>(ty))
|
||||
formatAppend(result, "n%d [label=\"%s\"];\n", index, toStringDetailed(ty, {}).name.c_str());
|
||||
else if (get<AnyTypeVar>(ty))
|
||||
formatAppend(result, "n%d [label=\"any\"];\n", index);
|
||||
}
|
||||
else
|
||||
{
|
||||
visitChildren(ty, index);
|
||||
}
|
||||
}
|
||||
|
||||
void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName)
|
||||
{
|
||||
if (!tpToIndex.count(tp))
|
||||
tpToIndex[tp] = nextIndex++;
|
||||
|
||||
if (parentIndex != 0)
|
||||
{
|
||||
if (linkName)
|
||||
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName);
|
||||
else
|
||||
formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]);
|
||||
}
|
||||
|
||||
visitChildren(tp, tpToIndex[tp]);
|
||||
}
|
||||
|
||||
void StateDot::startNode(int index)
|
||||
{
|
||||
formatAppend(result, "n%d [", index);
|
||||
}
|
||||
|
||||
void StateDot::finishNode()
|
||||
{
|
||||
formatAppend(result, "];\n");
|
||||
}
|
||||
|
||||
void StateDot::startNodeLabel()
|
||||
{
|
||||
formatAppend(result, "label=\"");
|
||||
}
|
||||
|
||||
void StateDot::finishNodeLabel(TypeId ty)
|
||||
{
|
||||
if (opts.showPointers)
|
||||
formatAppend(result, "\n0x%p", ty);
|
||||
// additional common attributes can be added here as well
|
||||
result += "\"";
|
||||
}
|
||||
|
||||
void StateDot::finishNodeLabel(TypePackId tp)
|
||||
{
|
||||
if (opts.showPointers)
|
||||
formatAppend(result, "\n0x%p", tp);
|
||||
// additional common attributes can be added here as well
|
||||
result += "\"";
|
||||
}
|
||||
|
||||
void StateDot::visitChildren(TypeId ty, int index)
|
||||
{
|
||||
if (seenTy.count(ty))
|
||||
return;
|
||||
seenTy.insert(ty);
|
||||
|
||||
startNode(index);
|
||||
startNodeLabel();
|
||||
|
||||
if (const BoundTypeVar* btv = get<BoundTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "BoundTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(btv->boundTo, index);
|
||||
}
|
||||
else if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "FunctionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(ftv->argTypes, index, "arg");
|
||||
visitChild(ftv->retType, index, "ret");
|
||||
}
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
if (ttv->name)
|
||||
formatAppend(result, "TableTypeVar %s", ttv->name->c_str());
|
||||
else if (ttv->syntheticName)
|
||||
formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str());
|
||||
else
|
||||
formatAppend(result, "TableTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
if (ttv->boundTo)
|
||||
return visitChild(*ttv->boundTo, index, "boundTo");
|
||||
|
||||
for (const auto& [name, prop] : ttv->props)
|
||||
visitChild(prop.type, index, name.c_str());
|
||||
if (ttv->indexer)
|
||||
{
|
||||
visitChild(ttv->indexer->indexType, index, "[index]");
|
||||
visitChild(ttv->indexer->indexResultType, index, "[value]");
|
||||
}
|
||||
for (TypeId itp : ttv->instantiatedTypeParams)
|
||||
visitChild(itp, index, "typeParam");
|
||||
|
||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp, index, "typePackParam");
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "MetatableTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(mtv->table, index, "table");
|
||||
visitChild(mtv->metatable, index, "metatable");
|
||||
}
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "UnionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (TypeId opt : utv->options)
|
||||
visitChild(opt, index);
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "IntersectionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (TypeId part : itv->parts)
|
||||
visitChild(part, index);
|
||||
}
|
||||
else if (const GenericTypeVar* gtv = get<GenericTypeVar>(ty))
|
||||
{
|
||||
if (gtv->explicitName)
|
||||
formatAppend(result, "GenericTypeVar %s", gtv->name.c_str());
|
||||
else
|
||||
formatAppend(result, "GenericTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "FreeTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<AnyTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "AnyTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<PrimitiveTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str());
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<ErrorTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "ErrorTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "ClassTypeVar %s", ctv->name.c_str());
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
visitChild(prop.type, index, name.c_str());
|
||||
|
||||
if (ctv->parent)
|
||||
visitChild(*ctv->parent, index, "[parent]");
|
||||
|
||||
if (ctv->metatable)
|
||||
visitChild(*ctv->metatable, index, "[metatable]");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"unknown type kind");
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
}
|
||||
|
||||
void StateDot::visitChildren(TypePackId tp, int index)
|
||||
{
|
||||
if (seenTp.count(tp))
|
||||
return;
|
||||
seenTp.insert(tp);
|
||||
|
||||
startNode(index);
|
||||
startNodeLabel();
|
||||
|
||||
if (const BoundTypePack* btp = get<BoundTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "BoundTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
visitChild(btp->boundTo, index);
|
||||
}
|
||||
else if (const TypePack* tpp = get<TypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "TypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
for (TypeId tv : tpp->head)
|
||||
visitChild(tv, index);
|
||||
if (tpp->tail)
|
||||
visitChild(*tpp->tail, index, "tail");
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "VariadicTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
visitChild(vtp->ty, index);
|
||||
}
|
||||
else if (const FreeTypePack* ftp = get<FreeTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "FreeTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else if (const GenericTypePack* gtp = get<GenericTypePack>(tp))
|
||||
{
|
||||
if (gtp->explicitName)
|
||||
formatAppend(result, "GenericTypePack %s", gtp->name.c_str());
|
||||
else
|
||||
formatAppend(result, "GenericTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<Unifiable::Error>(tp))
|
||||
{
|
||||
formatAppend(result, "ErrorTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"unknown type pack kind");
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts)
|
||||
{
|
||||
StateDot state{opts};
|
||||
|
||||
state.result = "digraph graphname {\n";
|
||||
state.visitChild(ty, 0);
|
||||
state.result += "}";
|
||||
|
||||
return state.result;
|
||||
}
|
||||
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts)
|
||||
{
|
||||
StateDot state{opts};
|
||||
|
||||
state.result = "digraph graphname {\n";
|
||||
state.visitChild(tp, 0);
|
||||
state.result += "}";
|
||||
|
||||
return state.result;
|
||||
}
|
||||
|
||||
std::string toDot(TypeId ty)
|
||||
{
|
||||
return toDot(ty, {});
|
||||
}
|
||||
|
||||
std::string toDot(TypePackId tp)
|
||||
{
|
||||
return toDot(tp, {});
|
||||
}
|
||||
|
||||
void dumpDot(TypeId ty)
|
||||
{
|
||||
printf("%s\n", toDot(ty).c_str());
|
||||
}
|
||||
|
||||
void dumpDot(TypePackId tp)
|
||||
{
|
||||
printf("%s\n", toDot(tp).c_str());
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -11,7 +11,14 @@
|
|||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false)
|
||||
|
||||
/*
|
||||
* Prefix generic typenames with gen-
|
||||
* Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4
|
||||
* Fair warning: Setting this will break a lot of Luau unit tests.
|
||||
*/
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -59,11 +66,8 @@ struct FindCyclicTypes
|
|||
for (TypeId itp : ttv.instantiatedTypeParams)
|
||||
visitTypeVar(itp, *this, seen);
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId itp : ttv.instantiatedTypePackParams)
|
||||
visitTypeVar(itp, *this, seen);
|
||||
}
|
||||
for (TypePackId itp : ttv.instantiatedTypePackParams)
|
||||
visitTypeVar(itp, *this, seen);
|
||||
|
||||
return exhaustive;
|
||||
}
|
||||
|
@ -248,65 +252,60 @@ struct TypeVarStringifier
|
|||
|
||||
void stringify(const std::vector<TypeId>& types, const std::vector<TypePackId>& typePacks)
|
||||
{
|
||||
if (types.size() == 0 && (!FFlag::LuauTypeAliasPacks || typePacks.size() == 0))
|
||||
if (types.size() == 0 && typePacks.size() == 0)
|
||||
return;
|
||||
|
||||
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size()))
|
||||
if (types.size() || typePacks.size())
|
||||
state.emit("<");
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
bool first = true;
|
||||
bool first = true;
|
||||
|
||||
for (TypeId ty : types)
|
||||
{
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
for (TypeId ty : types)
|
||||
{
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
first = false;
|
||||
|
||||
stringify(ty);
|
||||
}
|
||||
|
||||
bool singleTp = typePacks.size() == 1;
|
||||
|
||||
for (TypePackId tp : typePacks)
|
||||
{
|
||||
if (isEmpty(tp) && singleTp)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
else
|
||||
first = false;
|
||||
|
||||
stringify(ty);
|
||||
}
|
||||
if (!singleTp)
|
||||
state.emit("(");
|
||||
|
||||
bool singleTp = typePacks.size() == 1;
|
||||
stringify(tp);
|
||||
|
||||
for (TypePackId tp : typePacks)
|
||||
{
|
||||
if (isEmpty(tp) && singleTp)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
else
|
||||
first = false;
|
||||
|
||||
if (!singleTp)
|
||||
state.emit("(");
|
||||
|
||||
stringify(tp);
|
||||
|
||||
if (!singleTp)
|
||||
state.emit(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < types.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
state.emit(", ");
|
||||
|
||||
stringify(types[i]);
|
||||
}
|
||||
if (!singleTp)
|
||||
state.emit(")");
|
||||
}
|
||||
|
||||
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size()))
|
||||
if (types.size() || typePacks.size())
|
||||
state.emit(">");
|
||||
}
|
||||
|
||||
void operator()(TypeId ty, const Unifiable::Free& ftv)
|
||||
{
|
||||
state.result.invalid = true;
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
state.emit("free-");
|
||||
state.emit(state.getName(ty));
|
||||
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
{
|
||||
state.emit("-");
|
||||
state.emit(std::to_string(ftv.level.level));
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(TypeId, const BoundTypeVar& btv)
|
||||
|
@ -767,12 +766,23 @@ struct TypePackStringifier
|
|||
else
|
||||
state.emit(", ");
|
||||
|
||||
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
|
||||
|
||||
if (!elemNames.empty() && elemNames[elemIndex])
|
||||
if (FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
if (elemIndex < elemNames.size() && elemNames[elemIndex])
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
|
||||
|
||||
if (!elemNames.empty() && elemNames[elemIndex])
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
}
|
||||
}
|
||||
elemIndex++;
|
||||
|
||||
|
@ -807,6 +817,8 @@ struct TypePackStringifier
|
|||
|
||||
void operator()(TypePackId tp, const GenericTypePack& pack)
|
||||
{
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
state.emit("gen-");
|
||||
if (pack.explicitName)
|
||||
{
|
||||
state.result.nameMap.typePacks[tp] = pack.name;
|
||||
|
@ -822,7 +834,16 @@ struct TypePackStringifier
|
|||
void operator()(TypePackId tp, const FreeTypePack& pack)
|
||||
{
|
||||
state.result.invalid = true;
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
state.emit("free-");
|
||||
state.emit(state.getName(tp));
|
||||
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
{
|
||||
state.emit("-");
|
||||
state.emit(std::to_string(pack.level.level));
|
||||
}
|
||||
|
||||
state.emit("...");
|
||||
}
|
||||
|
||||
|
@ -929,38 +950,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
|
|||
|
||||
result.name += ttv->name ? *ttv->name : *ttv->syntheticName;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ttv->instantiatedTypeParams.empty() && (!FFlag::LuauTypeAliasPacks || ttv->instantiatedTypePackParams.empty()))
|
||||
return result;
|
||||
|
||||
result.name += "<";
|
||||
|
||||
bool first = true;
|
||||
for (TypeId ty : ttv->instantiatedTypeParams)
|
||||
{
|
||||
if (!first)
|
||||
result.name += ", ";
|
||||
else
|
||||
first = false;
|
||||
|
||||
tvs.stringify(ty);
|
||||
}
|
||||
|
||||
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
|
||||
{
|
||||
result.truncated = true;
|
||||
result.name += "... <TRUNCATED>";
|
||||
}
|
||||
else
|
||||
{
|
||||
result.name += ">";
|
||||
}
|
||||
}
|
||||
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1161,17 +1151,37 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
|
|||
s += ", ";
|
||||
first = false;
|
||||
|
||||
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (!ftv.argNames.empty())
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
s += toString_(*argPackIter);
|
||||
|
||||
++argPackIter;
|
||||
if (!ftv.argNames.empty())
|
||||
if (FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
LUAU_ASSERT(argNameIter != ftv.argNames.end());
|
||||
++argNameIter;
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (argNameIter != ftv.argNames.end())
|
||||
{
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
++argNameIter;
|
||||
}
|
||||
else
|
||||
{
|
||||
s += "_: ";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (!ftv.argNames.empty())
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
}
|
||||
|
||||
s += toString_(*argPackIter);
|
||||
++argPackIter;
|
||||
|
||||
if (!FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
if (!ftv.argNames.empty())
|
||||
{
|
||||
LUAU_ASSERT(argNameIter != ftv.argNames.end());
|
||||
++argNameIter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1197,20 +1207,24 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
|
|||
return s;
|
||||
}
|
||||
|
||||
void dump(TypeId ty)
|
||||
std::string dump(TypeId ty)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
opts.functionTypeArguments = true;
|
||||
printf("%s\n", toString(ty, opts).c_str());
|
||||
std::string s = toString(ty, opts);
|
||||
printf("%s\n", s.c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
void dump(TypePackId ty)
|
||||
std::string dump(TypePackId ty)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
opts.functionTypeArguments = true;
|
||||
printf("%s\n", toString(ty, opts).c_str());
|
||||
std::string s = toString(ty, opts);
|
||||
printf("%s\n", s.c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string generateName(size_t i)
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
#include <limits>
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
namespace
|
||||
{
|
||||
bool isIdentifierStartChar(char c)
|
||||
|
@ -787,7 +785,7 @@ struct Printer
|
|||
|
||||
writer.keyword("type");
|
||||
writer.identifier(a->name.value);
|
||||
if (a->generics.size > 0 || (FFlag::LuauTypeAliasPacks && a->genericPacks.size > 0))
|
||||
if (a->generics.size > 0 || a->genericPacks.size > 0)
|
||||
{
|
||||
writer.symbol("<");
|
||||
CommaSeparatorInserter comma(writer);
|
||||
|
@ -798,14 +796,11 @@ struct Printer
|
|||
writer.identifier(o.value);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (auto o : a->genericPacks)
|
||||
{
|
||||
for (auto o : a->genericPacks)
|
||||
{
|
||||
comma();
|
||||
writer.identifier(o.value);
|
||||
writer.symbol("...");
|
||||
}
|
||||
comma();
|
||||
writer.identifier(o.value);
|
||||
writer.symbol("...");
|
||||
}
|
||||
|
||||
writer.symbol(">");
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauShareTxnSeen, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -36,11 +34,8 @@ void TxnLog::rollback()
|
|||
for (auto it = tableChanges.rbegin(); it != tableChanges.rend(); ++it)
|
||||
std::swap(it->first->boundTo, it->second);
|
||||
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
{
|
||||
LUAU_ASSERT(originalSeenSize <= sharedSeen->size());
|
||||
sharedSeen->resize(originalSeenSize);
|
||||
}
|
||||
LUAU_ASSERT(originalSeenSize <= sharedSeen->size());
|
||||
sharedSeen->resize(originalSeenSize);
|
||||
}
|
||||
|
||||
void TxnLog::concat(TxnLog rhs)
|
||||
|
@ -53,45 +48,25 @@ void TxnLog::concat(TxnLog rhs)
|
|||
|
||||
tableChanges.insert(tableChanges.end(), rhs.tableChanges.begin(), rhs.tableChanges.end());
|
||||
rhs.tableChanges.clear();
|
||||
|
||||
if (!FFlag::LuauShareTxnSeen)
|
||||
{
|
||||
ownedSeen.swap(rhs.ownedSeen);
|
||||
rhs.ownedSeen.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
|
||||
else
|
||||
return (ownedSeen.end() != std::find(ownedSeen.begin(), ownedSeen.end(), sortedPair));
|
||||
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
|
||||
}
|
||||
|
||||
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
sharedSeen->push_back(sortedPair);
|
||||
else
|
||||
ownedSeen.push_back(sortedPair);
|
||||
sharedSeen->push_back(sortedPair);
|
||||
}
|
||||
|
||||
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
{
|
||||
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
||||
sharedSeen->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(sortedPair == ownedSeen.back());
|
||||
ownedSeen.pop_back();
|
||||
}
|
||||
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
||||
sharedSeen->pop_back();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
|
||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||
{
|
||||
char* result = (char*)allocator.allocate(contents.size() + 1);
|
||||
|
@ -131,12 +129,9 @@ public:
|
|||
parameters.data[i] = {Luau::visit(*this, ttv.instantiatedTypeParams[i]->ty), {}};
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
|
||||
{
|
||||
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
|
||||
{
|
||||
parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])};
|
||||
}
|
||||
parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])};
|
||||
}
|
||||
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName(ttv.name->c_str()), parameters.size != 0, parameters);
|
||||
|
@ -250,20 +245,7 @@ public:
|
|||
|
||||
AstTypePack* argTailAnnotation = nullptr;
|
||||
if (argTail)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
argTailAnnotation = rehydrate(*argTail);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypePackId tail = *argTail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
argTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
argTailAnnotation = rehydrate(*argTail);
|
||||
|
||||
AstArray<std::optional<AstArgumentName>> argNames;
|
||||
argNames.size = ftv.argNames.size();
|
||||
|
@ -292,20 +274,7 @@ public:
|
|||
|
||||
AstTypePack* retTailAnnotation = nullptr;
|
||||
if (retTail)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
retTailAnnotation = rehydrate(*retTail);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypePackId tail = *retTail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
retTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
retTailAnnotation = rehydrate(*retTail);
|
||||
|
||||
return allocator->alloc<AstTypeFunction>(
|
||||
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation});
|
||||
|
@ -518,18 +487,7 @@ public:
|
|||
const auto& [v, tail] = flatten(ret);
|
||||
|
||||
if (tail)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypePackId tailPack = *tail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tailPack))
|
||||
variadicAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), typeAst(vtp->ty));
|
||||
}
|
||||
}
|
||||
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
|
||||
|
||||
fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation};
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
#include "Luau/Scope.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/TopoSortStatements.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
|
||||
|
@ -23,22 +23,25 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 500)
|
|||
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
|
||||
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClassPropertyAccessAsString, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStrictRequire, false)
|
||||
LUAU_FASTFLAG(LuauSubstitutionDontReplaceIgnoredTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
|
||||
LUAU_FASTFLAG(LuauNewRequireTrace2)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTailArgumentTypeInfo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauModuleRequireErrorPack, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefiLookupFromIndexExpr, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixRecursiveMetatableCall, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalAsExpr, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdateFunctionNameBinding, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -208,14 +211,14 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
|
|||
: resolver(resolver)
|
||||
, iceHandler(iceHandler)
|
||||
, unifierState(iceHandler)
|
||||
, nilType(singletonTypes.nilType)
|
||||
, numberType(singletonTypes.numberType)
|
||||
, stringType(singletonTypes.stringType)
|
||||
, booleanType(singletonTypes.booleanType)
|
||||
, threadType(singletonTypes.threadType)
|
||||
, anyType(singletonTypes.anyType)
|
||||
, optionalNumberType(singletonTypes.optionalNumberType)
|
||||
, anyTypePack(singletonTypes.anyTypePack)
|
||||
, nilType(getSingletonTypes().nilType)
|
||||
, numberType(getSingletonTypes().numberType)
|
||||
, stringType(getSingletonTypes().stringType)
|
||||
, booleanType(getSingletonTypes().booleanType)
|
||||
, threadType(getSingletonTypes().threadType)
|
||||
, anyType(getSingletonTypes().anyType)
|
||||
, optionalNumberType(getSingletonTypes().optionalNumberType)
|
||||
, anyTypePack(getSingletonTypes().anyTypePack)
|
||||
{
|
||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
|
||||
|
@ -445,7 +448,7 @@ void TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block)
|
|||
functionDecls[*protoIter] = pair;
|
||||
++subLevel;
|
||||
|
||||
TypeId leftType = checkFunctionName(scope, *fun->name);
|
||||
TypeId leftType = checkFunctionName(scope, *fun->name, funScope->level);
|
||||
unify(leftType, funTy, fun->location);
|
||||
}
|
||||
else if (auto fun = (*protoIter)->as<AstStatLocalFunction>())
|
||||
|
@ -562,12 +565,6 @@ ErrorVec TypeChecker::canUnify(TypePackId left, TypePackId right, const Location
|
|||
return canUnify_(left, right, location);
|
||||
}
|
||||
|
||||
ErrorVec TypeChecker::canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId superTy, TypeId subTy, const Location& location)
|
||||
{
|
||||
Unifier state = mkUnifier(seen, location);
|
||||
return state.canUnify(superTy, subTy);
|
||||
}
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec TypeChecker::canUnify_(Id superTy, Id subTy, const Location& location)
|
||||
{
|
||||
|
@ -719,14 +716,15 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign)
|
|||
}
|
||||
else if (auto tail = valueIter.tail())
|
||||
{
|
||||
if (get<Unifiable::Error>(*tail))
|
||||
TypePackId tailPack = follow(*tail);
|
||||
if (get<Unifiable::Error>(tailPack))
|
||||
right = errorRecoveryType(scope);
|
||||
else if (auto vtp = get<VariadicTypePack>(*tail))
|
||||
else if (auto vtp = get<VariadicTypePack>(tailPack))
|
||||
right = vtp->ty;
|
||||
else if (get<Unifiable::Free>(*tail))
|
||||
else if (get<Unifiable::Free>(tailPack))
|
||||
{
|
||||
*asMutable(*tail) = TypePack{{left}};
|
||||
growingPack = getMutable<TypePack>(*tail);
|
||||
*asMutable(tailPack) = TypePack{{left}};
|
||||
growingPack = getMutable<TypePack>(tailPack);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1115,8 +1113,27 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
|
|||
|
||||
unify(leftType, ty, function.location);
|
||||
|
||||
if (leftTypeBinding)
|
||||
*leftTypeBinding = follow(quantify(funScope, leftType, function.name->location));
|
||||
if (FFlag::LuauUpdateFunctionNameBinding)
|
||||
{
|
||||
LUAU_ASSERT(function.name->is<AstExprIndexName>() || function.name->is<AstExprError>());
|
||||
|
||||
if (auto exprIndexName = function.name->as<AstExprIndexName>())
|
||||
{
|
||||
if (auto typeIt = currentModule->astTypes.find(exprIndexName->expr))
|
||||
{
|
||||
if (auto ttv = getMutableTableType(*typeIt))
|
||||
{
|
||||
if (auto it = ttv->props.find(exprIndexName->index.value); it != ttv->props.end())
|
||||
it->second.type = follow(quantify(funScope, leftType, function.name->location));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (leftTypeBinding)
|
||||
*leftTypeBinding = follow(quantify(funScope, leftType, function.name->location));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1152,61 +1169,22 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||
Location location = scope->typeAliasLocations[name];
|
||||
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
||||
else
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, errorRecoveryType(anyType)};
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
||||
}
|
||||
else
|
||||
{
|
||||
ScopePtr aliasScope =
|
||||
FFlag::LuauQuantifyInPlace2 ? childScope(scope, typealias.location, subLevel) : childScope(scope, typealias.location);
|
||||
ScopePtr aliasScope = childScope(scope, typealias.location);
|
||||
aliasScope->level = scope->level.incr();
|
||||
if (FFlag::LuauProperTypeLevels)
|
||||
aliasScope->level.subLevel = subLevel;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks);
|
||||
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks);
|
||||
|
||||
TypeId ty = freshType(aliasScope);
|
||||
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> generics;
|
||||
for (AstName generic : typealias.generics)
|
||||
{
|
||||
Name n = generic.value;
|
||||
|
||||
// These generics are the only thing that will ever be added to aliasScope, so we can be certain that
|
||||
// a collision can only occur when two generic typevars have the same name.
|
||||
if (aliasScope->privateTypeBindings.end() != aliasScope->privateTypeBindings.find(n))
|
||||
{
|
||||
// TODO(jhuelsman): report the exact span of the generic type parameter whose name is a duplicate.
|
||||
reportError(TypeError{typealias.location, DuplicateGenericParameter{n}});
|
||||
}
|
||||
|
||||
TypeId g;
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction)
|
||||
{
|
||||
TypeId& cached = scope->typeAliasTypeParameters[n];
|
||||
if (!cached)
|
||||
cached = addType(GenericTypeVar{aliasScope->level, n});
|
||||
g = cached;
|
||||
}
|
||||
else
|
||||
g = addType(GenericTypeVar{aliasScope->level, n});
|
||||
generics.push_back(g);
|
||||
aliasScope->privateTypeBindings[n] = TypeFun{{}, g};
|
||||
}
|
||||
|
||||
TypeId ty = freshType(aliasScope);
|
||||
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
bindingsMap[name] = {std::move(generics), ty};
|
||||
}
|
||||
TypeId ty = freshType(aliasScope);
|
||||
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1215,6 +1193,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||
ice("Not predeclared");
|
||||
|
||||
ScopePtr aliasScope = childScope(scope, typealias.location);
|
||||
aliasScope->level = scope->level.incr();
|
||||
|
||||
for (TypeId ty : binding->typeParams)
|
||||
{
|
||||
|
@ -1223,14 +1202,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, ty};
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
for (TypePackId tp : binding->typePackParams)
|
||||
{
|
||||
for (TypePackId tp : binding->typePackParams)
|
||||
{
|
||||
auto generic = get<GenericTypePack>(tp);
|
||||
LUAU_ASSERT(generic);
|
||||
aliasScope->privateTypePackBindings[generic->name] = tp;
|
||||
}
|
||||
auto generic = get<GenericTypePack>(tp);
|
||||
LUAU_ASSERT(generic);
|
||||
aliasScope->privateTypePackBindings[generic->name] = tp;
|
||||
}
|
||||
|
||||
TypeId ty = resolveType(aliasScope, *typealias.type);
|
||||
|
@ -1241,19 +1217,16 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||
{
|
||||
// Copy can be skipped if this is an identical alias
|
||||
if (ttv->name != name || ttv->instantiatedTypeParams != binding->typeParams ||
|
||||
(FFlag::LuauTypeAliasPacks && ttv->instantiatedTypePackParams != binding->typePackParams))
|
||||
ttv->instantiatedTypePackParams != binding->typePackParams)
|
||||
{
|
||||
// This is a shallow clone, original recursive links to self are not updated
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
|
||||
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
|
||||
clone.name = name;
|
||||
clone.instantiatedTypeParams = binding->typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
clone.instantiatedTypePackParams = binding->typePackParams;
|
||||
clone.instantiatedTypePackParams = binding->typePackParams;
|
||||
|
||||
ty = addType(std::move(clone));
|
||||
}
|
||||
|
@ -1262,9 +1235,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||
{
|
||||
ttv->name = name;
|
||||
ttv->instantiatedTypeParams = binding->typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
ttv->instantiatedTypePackParams = binding->typePackParams;
|
||||
ttv->instantiatedTypePackParams = binding->typePackParams;
|
||||
}
|
||||
}
|
||||
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
|
||||
|
@ -1289,7 +1260,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
|
|||
}
|
||||
|
||||
// We don't have generic classes, so this assertion _should_ never be hit.
|
||||
LUAU_ASSERT(lookupType->typeParams.size() == 0 && (!FFlag::LuauTypeAliasPacks || lookupType->typePackParams.size() == 0));
|
||||
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
|
||||
superTy = lookupType->type;
|
||||
|
||||
if (!get<ClassTypeVar>(follow(*superTy)))
|
||||
|
@ -1562,9 +1533,9 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCa
|
|||
else if (auto vtp = get<VariadicTypePack>(retPack))
|
||||
return {vtp->ty, std::move(result.predicates)};
|
||||
else if (get<Unifiable::Generic>(retPack))
|
||||
ice("Unexpected abstract type pack!");
|
||||
ice("Unexpected abstract type pack!", expr.location);
|
||||
else
|
||||
ice("Unknown TypePack type!");
|
||||
ice("Unknown TypePack type!", expr.location);
|
||||
}
|
||||
|
||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexName& expr)
|
||||
|
@ -1631,7 +1602,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||
}
|
||||
else if (tableType->state == TableState::Free)
|
||||
{
|
||||
TypeId result = freshType(scope);
|
||||
TypeId result = FFlag::LuauAscribeCorrectLevelToInferredProperitesOfFreeTables ? freshType(tableType->level) : freshType(scope);
|
||||
tableType->props[name] = {result};
|
||||
return result;
|
||||
}
|
||||
|
@ -1795,7 +1766,16 @@ TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location)
|
|||
|
||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr)
|
||||
{
|
||||
return {checkLValue(scope, expr)};
|
||||
TypeId ty = checkLValue(scope, expr);
|
||||
|
||||
if (FFlag::LuauRefiLookupFromIndexExpr)
|
||||
{
|
||||
if (std::optional<LValue> lvalue = tryGetLValue(expr))
|
||||
if (std::optional<TypeId> refiTy = resolveLValue(scope, *lvalue))
|
||||
return {*refiTy, {TruthyPredicate{std::move(*lvalue), expr.location}}};
|
||||
}
|
||||
|
||||
return {ty};
|
||||
}
|
||||
|
||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType)
|
||||
|
@ -1851,6 +1831,24 @@ TypeId TypeChecker::checkExprTable(
|
|||
if (isNonstrictMode() && !getTableType(exprType) && !get<FunctionTypeVar>(exprType))
|
||||
exprType = anyType;
|
||||
|
||||
if (FFlag::LuauPropertiesGetExpectedType && expectedTable)
|
||||
{
|
||||
auto it = expectedTable->props.find(key->value.data);
|
||||
if (it != expectedTable->props.end())
|
||||
{
|
||||
Property expectedProp = it->second;
|
||||
ErrorVec errors = tryUnify(expectedProp.type, exprType, k->location);
|
||||
if (errors.empty())
|
||||
exprType = expectedProp.type;
|
||||
}
|
||||
else if (expectedTable->indexer && isString(expectedTable->indexer->indexType))
|
||||
{
|
||||
ErrorVec errors = tryUnify(expectedTable->indexer->indexResultType, exprType, k->location);
|
||||
if (errors.empty())
|
||||
exprType = expectedTable->indexer->indexResultType;
|
||||
}
|
||||
}
|
||||
|
||||
props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location};
|
||||
}
|
||||
else
|
||||
|
@ -2460,12 +2458,27 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTy
|
|||
TypeId annotationType = resolveType(scope, *expr.annotation);
|
||||
ExprResult<TypeId> result = checkExpr(scope, *expr.expr, annotationType);
|
||||
|
||||
ErrorVec errorVec = canUnify(result.type, annotationType, expr.location);
|
||||
reportErrors(errorVec);
|
||||
if (!errorVec.empty())
|
||||
annotationType = errorRecoveryType(annotationType);
|
||||
if (FFlag::LuauBidirectionalAsExpr)
|
||||
{
|
||||
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
||||
if (canUnify(result.type, annotationType, expr.location).empty())
|
||||
return {annotationType, std::move(result.predicates)};
|
||||
|
||||
return {annotationType, std::move(result.predicates)};
|
||||
if (canUnify(annotationType, result.type, expr.location).empty())
|
||||
return {annotationType, std::move(result.predicates)};
|
||||
|
||||
reportError(expr.location, TypesAreUnrelated{result.type, annotationType});
|
||||
return {errorRecoveryType(annotationType), std::move(result.predicates)};
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorVec errorVec = canUnify(result.type, annotationType, expr.location);
|
||||
reportErrors(errorVec);
|
||||
if (!errorVec.empty())
|
||||
annotationType = errorRecoveryType(annotationType);
|
||||
|
||||
return {annotationType, std::move(result.predicates)};
|
||||
}
|
||||
}
|
||||
|
||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprError& expr)
|
||||
|
@ -2713,8 +2726,15 @@ std::pair<TypeId, TypeId*> TypeChecker::checkLValueBinding(const ScopePtr& scope
|
|||
|
||||
// Answers the question: "Can I define another function with this name?"
|
||||
// Primarily about detecting duplicates.
|
||||
TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName)
|
||||
TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level)
|
||||
{
|
||||
auto freshTy = [&]() {
|
||||
if (FFlag::LuauProperTypeLevels)
|
||||
return freshType(level);
|
||||
else
|
||||
return freshType(scope);
|
||||
};
|
||||
|
||||
if (auto globalName = funName.as<AstExprGlobal>())
|
||||
{
|
||||
const ScopePtr& globalScope = currentModule->getModuleScope();
|
||||
|
@ -2728,7 +2748,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName)
|
|||
}
|
||||
else
|
||||
{
|
||||
TypeId ty = freshType(scope);
|
||||
TypeId ty = freshTy();
|
||||
globalScope->bindings[name] = {ty, funName.location};
|
||||
return ty;
|
||||
}
|
||||
|
@ -2738,7 +2758,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName)
|
|||
Symbol name = localName->local;
|
||||
Binding& binding = scope->bindings[name];
|
||||
if (binding.typeId == nullptr)
|
||||
binding = {freshType(scope), funName.location};
|
||||
binding = {freshTy(), funName.location};
|
||||
|
||||
return binding.typeId;
|
||||
}
|
||||
|
@ -2769,7 +2789,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName)
|
|||
|
||||
Property& property = ttv->props[name];
|
||||
|
||||
property.type = freshType(scope);
|
||||
property.type = freshTy();
|
||||
property.location = indexName->indexLocation;
|
||||
ttv->methodDefinitionLocations[name] = funName.location;
|
||||
return property.type;
|
||||
|
@ -3366,7 +3386,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
|
|||
fn = follow(fn);
|
||||
|
||||
if (auto ret = checkCallOverload(
|
||||
scope, expr, fn, retPack, argPack, args, argLocations, argListResult, overloadsThatMatchArgCount, overloadsThatDont, errors))
|
||||
scope, expr, fn, retPack, argPack, args, &argLocations, argListResult, overloadsThatMatchArgCount, overloadsThatDont, errors))
|
||||
return *ret;
|
||||
}
|
||||
|
||||
|
@ -3441,9 +3461,11 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
|||
}
|
||||
|
||||
std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>& argLocations, const ExprResult<TypePackId>& argListResult,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const ExprResult<TypePackId>& argListResult,
|
||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors)
|
||||
{
|
||||
LUAU_ASSERT(argLocations);
|
||||
|
||||
fn = stripFromNilAndReport(fn, expr.func->location);
|
||||
|
||||
if (get<AnyTypeVar>(fn))
|
||||
|
@ -3467,31 +3489,44 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
|
|||
return {{retPack}};
|
||||
}
|
||||
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn);
|
||||
if (!ftv)
|
||||
std::vector<Location> metaArgLocations;
|
||||
|
||||
// Might be a callable table
|
||||
if (const MetatableTypeVar* mttv = get<MetatableTypeVar>(fn))
|
||||
{
|
||||
// Might be a callable table
|
||||
if (const MetatableTypeVar* mttv = get<MetatableTypeVar>(fn))
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, false))
|
||||
{
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, false))
|
||||
// Construct arguments with 'self' added in front
|
||||
TypePackId metaCallArgPack = addTypePack(TypePackVar(TypePack{args->head, args->tail}));
|
||||
|
||||
TypePack* metaCallArgs = getMutable<TypePack>(metaCallArgPack);
|
||||
metaCallArgs->head.insert(metaCallArgs->head.begin(), fn);
|
||||
|
||||
metaArgLocations = *argLocations;
|
||||
metaArgLocations.insert(metaArgLocations.begin(), expr.func->location);
|
||||
|
||||
if (FFlag::LuauFixRecursiveMetatableCall)
|
||||
{
|
||||
// Construct arguments with 'self' added in front
|
||||
TypePackId metaCallArgPack = addTypePack(TypePackVar(TypePack{args->head, args->tail}));
|
||||
|
||||
TypePack* metaCallArgs = getMutable<TypePack>(metaCallArgPack);
|
||||
metaCallArgs->head.insert(metaCallArgs->head.begin(), fn);
|
||||
|
||||
std::vector<Location> metaArgLocations = argLocations;
|
||||
metaArgLocations.insert(metaArgLocations.begin(), expr.func->location);
|
||||
fn = instantiate(scope, *ty, expr.func->location);
|
||||
|
||||
argPack = metaCallArgPack;
|
||||
args = metaCallArgs;
|
||||
argLocations = &metaArgLocations;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId fn = *ty;
|
||||
fn = instantiate(scope, fn, expr.func->location);
|
||||
|
||||
return checkCallOverload(scope, expr, fn, retPack, metaCallArgPack, metaCallArgs, metaArgLocations, argListResult,
|
||||
return checkCallOverload(scope, expr, fn, retPack, metaCallArgPack, metaCallArgs, &metaArgLocations, argListResult,
|
||||
overloadsThatMatchArgCount, overloadsThatDont, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn);
|
||||
if (!ftv)
|
||||
{
|
||||
reportError(TypeError{expr.func->location, CannotCallNonFunction{fn}});
|
||||
unify(retPack, errorRecoveryTypePack(scope), expr.func->location);
|
||||
return {{errorRecoveryTypePack(retPack)}};
|
||||
|
@ -3516,7 +3551,7 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
|
|||
return {};
|
||||
}
|
||||
|
||||
checkArgumentList(scope, state, argPack, ftv->argTypes, argLocations);
|
||||
checkArgumentList(scope, state, argPack, ftv->argTypes, *argLocations);
|
||||
|
||||
if (!state.errors.empty())
|
||||
{
|
||||
|
@ -3744,17 +3779,29 @@ ExprResult<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const L
|
|||
for (size_t i = 0; i < exprs.size; ++i)
|
||||
{
|
||||
AstExpr* expr = exprs.data[i];
|
||||
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
|
||||
|
||||
if (i == lastIndex && (expr->is<AstExprCall>() || expr->is<AstExprVarargs>()))
|
||||
{
|
||||
auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
|
||||
insert(exprPredicates);
|
||||
|
||||
if (FFlag::LuauTailArgumentTypeInfo)
|
||||
{
|
||||
if (std::optional<TypeId> firstTy = first(typePack))
|
||||
{
|
||||
if (!currentModule->astTypes.find(expr))
|
||||
currentModule->astTypes[expr] = follow(*firstTy);
|
||||
}
|
||||
|
||||
if (expectedType)
|
||||
currentModule->astExpectedTypes[expr] = *expectedType;
|
||||
}
|
||||
|
||||
tp->tail = typePack;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
|
||||
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
|
||||
insert(exprPredicates);
|
||||
|
||||
|
@ -3797,9 +3844,9 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
|||
LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker");
|
||||
LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str());
|
||||
|
||||
if (FFlag::LuauNewRequireTrace2 && moduleInfo.name.empty())
|
||||
if (moduleInfo.name.empty())
|
||||
{
|
||||
if (FFlag::LuauStrictRequire && currentModule->mode == Mode::Strict)
|
||||
if (currentModule->mode == Mode::Strict)
|
||||
{
|
||||
reportError(TypeError{location, UnknownRequire{}});
|
||||
return errorRecoveryType(anyType);
|
||||
|
@ -3814,7 +3861,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
|||
// There are two reasons why we might fail to find the module:
|
||||
// either the file does not exist or there's a cycle. If there's a cycle
|
||||
// we will already have reported the error.
|
||||
if (!resolver->moduleExists(moduleInfo.name) && (FFlag::LuauTraceRequireLookupChild ? !moduleInfo.optional : true))
|
||||
if (!resolver->moduleExists(moduleInfo.name) && !moduleInfo.optional)
|
||||
{
|
||||
std::string reportedModulePath = resolver->getHumanReadableModuleName(moduleInfo.name);
|
||||
reportError(TypeError{location, UnknownRequire{reportedModulePath}});
|
||||
|
@ -3830,7 +3877,12 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
|||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
std::optional<TypeId> moduleType = first(module->getModuleScope()->returnType);
|
||||
TypePackId modulePack = module->getModuleScope()->returnType;
|
||||
|
||||
if (FFlag::LuauModuleRequireErrorPack && get<Unifiable::Error>(modulePack))
|
||||
return errorRecoveryType(scope);
|
||||
|
||||
std::optional<TypeId> moduleType = first(modulePack);
|
||||
if (!moduleType)
|
||||
{
|
||||
std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name);
|
||||
|
@ -3840,7 +3892,8 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks);
|
||||
CloneState cloneState;
|
||||
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
void TypeChecker::tablify(TypeId type)
|
||||
|
@ -4289,9 +4342,11 @@ ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location&
|
|||
}
|
||||
|
||||
// Creates a new Scope and carries forward the varargs from the parent.
|
||||
ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& location, int subLevel)
|
||||
ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& location)
|
||||
{
|
||||
ScopePtr scope = std::make_shared<Scope>(parent, subLevel);
|
||||
ScopePtr scope = std::make_shared<Scope>(parent);
|
||||
if (FFlag::LuauProperTypeLevels)
|
||||
scope->level = parent->level;
|
||||
scope->varargPack = parent->varargPack;
|
||||
|
||||
currentModule->scopes.push_back(std::make_pair(location, scope));
|
||||
|
@ -4326,11 +4381,6 @@ Unifier TypeChecker::mkUnifier(const Location& location)
|
|||
return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState};
|
||||
}
|
||||
|
||||
Unifier TypeChecker::mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location)
|
||||
{
|
||||
return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, seen, location, Variance::Covariant, unifierState};
|
||||
}
|
||||
|
||||
TypeId TypeChecker::freshType(const ScopePtr& scope)
|
||||
{
|
||||
return freshType(scope->level);
|
||||
|
@ -4355,22 +4405,22 @@ TypeId TypeChecker::singletonType(std::string value)
|
|||
|
||||
TypeId TypeChecker::errorRecoveryType(const ScopePtr& scope)
|
||||
{
|
||||
return singletonTypes.errorRecoveryType();
|
||||
return getSingletonTypes().errorRecoveryType();
|
||||
}
|
||||
|
||||
TypeId TypeChecker::errorRecoveryType(TypeId guess)
|
||||
{
|
||||
return singletonTypes.errorRecoveryType(guess);
|
||||
return getSingletonTypes().errorRecoveryType(guess);
|
||||
}
|
||||
|
||||
TypePackId TypeChecker::errorRecoveryTypePack(const ScopePtr& scope)
|
||||
{
|
||||
return singletonTypes.errorRecoveryTypePack();
|
||||
return getSingletonTypes().errorRecoveryTypePack();
|
||||
}
|
||||
|
||||
TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
|
||||
{
|
||||
return singletonTypes.errorRecoveryTypePack(guess);
|
||||
return getSingletonTypes().errorRecoveryTypePack(guess);
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeChecker::filterMap(TypeId type, TypeIdPredicate predicate)
|
||||
|
@ -4477,117 +4527,82 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
|
|||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
if (lit->parameters.size == 0 && tf->typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf->typePackParams.empty()))
|
||||
{
|
||||
if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty())
|
||||
return tf->type;
|
||||
}
|
||||
else if (!FFlag::LuauTypeAliasPacks && lit->parameters.size != tf->typeParams.size())
|
||||
|
||||
if (!lit->hasParameterList && !tf->typePackParams.empty())
|
||||
{
|
||||
reportError(TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, lit->parameters.size, 0}});
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
|
||||
if (!FFlag::LuauErrorRecoveryType)
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
std::vector<TypeId> typeParams;
|
||||
std::vector<TypeId> extraTypes;
|
||||
std::vector<TypePackId> typePackParams;
|
||||
|
||||
for (size_t i = 0; i < lit->parameters.size; ++i)
|
||||
{
|
||||
if (!lit->hasParameterList && !tf->typePackParams.empty())
|
||||
if (AstType* type = lit->parameters.data[i].type)
|
||||
{
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
|
||||
if (!FFlag::LuauErrorRecoveryType)
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
TypeId ty = resolveType(scope, *type);
|
||||
|
||||
std::vector<TypeId> typeParams;
|
||||
std::vector<TypeId> extraTypes;
|
||||
std::vector<TypePackId> typePackParams;
|
||||
|
||||
for (size_t i = 0; i < lit->parameters.size; ++i)
|
||||
{
|
||||
if (AstType* type = lit->parameters.data[i].type)
|
||||
{
|
||||
TypeId ty = resolveType(scope, *type);
|
||||
|
||||
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
|
||||
typeParams.push_back(ty);
|
||||
else if (typePackParams.empty())
|
||||
extraTypes.push_back(ty);
|
||||
else
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
|
||||
}
|
||||
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
|
||||
{
|
||||
TypePackId tp = resolveTypePack(scope, *typePack);
|
||||
|
||||
// If we have collected an implicit type pack, materialize it
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we need more regular types, we can use single element type packs to fill those in
|
||||
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
|
||||
typeParams.push_back(*first(tp));
|
||||
else
|
||||
typePackParams.push_back(tp);
|
||||
}
|
||||
}
|
||||
|
||||
// If we still haven't meterialized an implicit type pack, do it now
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
|
||||
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
|
||||
typePackParams.push_back(addTypePack({}));
|
||||
|
||||
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
|
||||
{
|
||||
reportError(
|
||||
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
|
||||
|
||||
if (FFlag::LuauErrorRecoveryType)
|
||||
{
|
||||
// Pad the types out with error recovery types
|
||||
while (typeParams.size() < tf->typeParams.size())
|
||||
typeParams.push_back(errorRecoveryType(scope));
|
||||
while (typePackParams.size() < tf->typePackParams.size())
|
||||
typePackParams.push_back(errorRecoveryTypePack(scope));
|
||||
}
|
||||
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
|
||||
typeParams.push_back(ty);
|
||||
else if (typePackParams.empty())
|
||||
extraTypes.push_back(ty);
|
||||
else
|
||||
return errorRecoveryType(scope);
|
||||
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
|
||||
}
|
||||
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
|
||||
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
|
||||
{
|
||||
// If the generic parameters and the type arguments are the same, we are about to
|
||||
// perform an identity substitution, which we can just short-circuit.
|
||||
return tf->type;
|
||||
TypePackId tp = resolveTypePack(scope, *typePack);
|
||||
|
||||
// If we have collected an implicit type pack, materialize it
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we need more regular types, we can use single element type packs to fill those in
|
||||
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
|
||||
typeParams.push_back(*first(tp));
|
||||
else
|
||||
typePackParams.push_back(tp);
|
||||
}
|
||||
|
||||
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> typeParams;
|
||||
|
||||
for (const auto& param : lit->parameters)
|
||||
typeParams.push_back(resolveType(scope, *param.type));
|
||||
// If we still haven't meterialized an implicit type pack, do it now
|
||||
if (typePackParams.empty() && !extraTypes.empty())
|
||||
typePackParams.push_back(addTypePack(extraTypes));
|
||||
|
||||
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
|
||||
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
|
||||
typePackParams.push_back(addTypePack({}));
|
||||
|
||||
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
|
||||
{
|
||||
reportError(
|
||||
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
|
||||
|
||||
if (FFlag::LuauErrorRecoveryType)
|
||||
{
|
||||
// If there aren't enough type parameters, pad them out with error recovery types
|
||||
// (we've already reported the error)
|
||||
while (typeParams.size() < lit->parameters.size)
|
||||
// Pad the types out with error recovery types
|
||||
while (typeParams.size() < tf->typeParams.size())
|
||||
typeParams.push_back(errorRecoveryType(scope));
|
||||
while (typePackParams.size() < tf->typePackParams.size())
|
||||
typePackParams.push_back(errorRecoveryTypePack(scope));
|
||||
}
|
||||
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams)
|
||||
{
|
||||
// If the generic parameters and the type arguments are the same, we are about to
|
||||
// perform an identity substitution, which we can just short-circuit.
|
||||
return tf->type;
|
||||
}
|
||||
|
||||
return instantiateTypeFun(scope, *tf, typeParams, {}, annotation.location);
|
||||
else
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
|
||||
{
|
||||
// If the generic parameters and the type arguments are the same, we are about to
|
||||
// perform an identity substitution, which we can just short-circuit.
|
||||
return tf->type;
|
||||
}
|
||||
|
||||
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
|
||||
}
|
||||
else if (const auto& table = annotation.as<AstTypeTable>())
|
||||
{
|
||||
|
@ -4608,6 +4623,7 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
|
|||
else if (const auto& func = annotation.as<AstTypeFunction>())
|
||||
{
|
||||
ScopePtr funcScope = childScope(scope, func->location);
|
||||
funcScope->level = scope->level.incr();
|
||||
|
||||
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks);
|
||||
|
||||
|
@ -4757,7 +4773,7 @@ bool ApplyTypeFunction::isDirty(TypePackId tp)
|
|||
|
||||
bool ApplyTypeFunction::ignoreChildren(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypeVar>(ty))
|
||||
if (get<GenericTypeVar>(ty))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
@ -4765,7 +4781,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
|
|||
|
||||
bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypePack>(tp))
|
||||
if (get<GenericTypePack>(tp))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
@ -4788,36 +4804,26 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp)
|
|||
// Really this should just replace the arguments,
|
||||
// but for bug-compatibility with existing code, we replace
|
||||
// all generics by free type variables.
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
TypePackId& arg = typePackArguments[tp];
|
||||
if (arg)
|
||||
return arg;
|
||||
else
|
||||
return addTypePack(FreeTypePack{level});
|
||||
}
|
||||
TypePackId& arg = typePackArguments[tp];
|
||||
if (arg)
|
||||
return arg;
|
||||
else
|
||||
{
|
||||
return addTypePack(FreeTypePack{level});
|
||||
}
|
||||
}
|
||||
|
||||
TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& typePackParams, const Location& location)
|
||||
{
|
||||
if (tf.typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf.typePackParams.empty()))
|
||||
if (tf.typeParams.empty() && tf.typePackParams.empty())
|
||||
return tf.type;
|
||||
|
||||
applyTypeFunction.typeArguments.clear();
|
||||
for (size_t i = 0; i < tf.typeParams.size(); ++i)
|
||||
applyTypeFunction.typeArguments[tf.typeParams[i]] = typeParams[i];
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
applyTypeFunction.typePackArguments.clear();
|
||||
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
|
||||
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
|
||||
}
|
||||
applyTypeFunction.typePackArguments.clear();
|
||||
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
|
||||
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
|
||||
|
||||
applyTypeFunction.currentModule = currentModule;
|
||||
applyTypeFunction.level = scope->level;
|
||||
|
@ -4866,9 +4872,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
|||
if (ttv)
|
||||
{
|
||||
ttv->instantiatedTypeParams = typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -4884,9 +4888,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
|||
}
|
||||
|
||||
ttv->instantiatedTypeParams = typeParams;
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4914,7 +4916,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
|
|||
}
|
||||
|
||||
TypeId g;
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks)
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction)
|
||||
{
|
||||
TypeId& cached = scope->parent->typeAliasTypeParameters[n];
|
||||
if (!cached)
|
||||
|
@ -4944,7 +4946,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
|
|||
}
|
||||
|
||||
TypePackId g;
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks)
|
||||
if (FFlag::LuauRecursiveTypeParameterRestriction)
|
||||
{
|
||||
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
|
||||
if (!cached)
|
||||
|
@ -5245,7 +5247,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
|
|||
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
|
||||
|
||||
auto typeFun = globalScope->lookupType(typeguardP.kind);
|
||||
if (!typeFun || !typeFun->typeParams.empty() || (FFlag::LuauTypeAliasPacks && !typeFun->typePackParams.empty()))
|
||||
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
|
||||
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
|
||||
|
||||
TypeId type = follow(typeFun->type);
|
||||
|
|
|
@ -19,7 +19,7 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globa
|
|||
TypeId unwrapped = follow(*metatable);
|
||||
|
||||
if (get<AnyTypeVar>(unwrapped))
|
||||
return singletonTypes.anyType;
|
||||
return getSingletonTypes().anyType;
|
||||
|
||||
const TableTypeVar* mtt = getTableType(unwrapped);
|
||||
if (!mtt)
|
||||
|
@ -61,12 +61,12 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const Sc
|
|||
{
|
||||
std::optional<TypeId> r = first(follow(itf->retType));
|
||||
if (!r)
|
||||
return singletonTypes.nilType;
|
||||
return getSingletonTypes().nilType;
|
||||
else
|
||||
return *r;
|
||||
}
|
||||
else if (get<AnyTypeVar>(index))
|
||||
return singletonTypes.anyType;
|
||||
return getSingletonTypes().anyType;
|
||||
else
|
||||
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTFLAG(LuauTypeAliasPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false)
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -580,11 +580,25 @@ SingletonTypes::SingletonTypes()
|
|||
, arena(new TypeArena)
|
||||
{
|
||||
TypeId stringMetatable = makeStringMetatable();
|
||||
stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, makeStringMetatable()};
|
||||
stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, stringMetatable};
|
||||
persist(stringMetatable);
|
||||
|
||||
debugFreezeArena = FFlag::DebugLuauFreezeArena;
|
||||
freeze(*arena);
|
||||
}
|
||||
|
||||
SingletonTypes::~SingletonTypes()
|
||||
{
|
||||
// Destroy the arena with the same memory management flags it was created with
|
||||
bool prevFlag = FFlag::DebugLuauFreezeArena;
|
||||
FFlag::DebugLuauFreezeArena.value = debugFreezeArena;
|
||||
|
||||
unfreeze(*arena);
|
||||
arena.reset(nullptr);
|
||||
|
||||
FFlag::DebugLuauFreezeArena.value = prevFlag;
|
||||
}
|
||||
|
||||
TypeId SingletonTypes::makeStringMetatable()
|
||||
{
|
||||
const TypeId optionalNumber = arena->addType(UnionTypeVar{{nilType, numberType}});
|
||||
|
@ -642,6 +656,9 @@ TypeId SingletonTypes::makeStringMetatable()
|
|||
|
||||
TypeId tableType = arena->addType(TableTypeVar{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(tableType))
|
||||
ttv->name = "string";
|
||||
|
||||
return arena->addType(TableTypeVar{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
}
|
||||
|
||||
|
@ -671,7 +688,11 @@ TypePackId SingletonTypes::errorRecoveryTypePack(TypePackId guess)
|
|||
return &errorTypePack_;
|
||||
}
|
||||
|
||||
SingletonTypes singletonTypes;
|
||||
SingletonTypes& getSingletonTypes()
|
||||
{
|
||||
static SingletonTypes singletonTypes;
|
||||
return singletonTypes;
|
||||
}
|
||||
|
||||
void persist(TypeId ty)
|
||||
{
|
||||
|
@ -720,6 +741,18 @@ void persist(TypeId ty)
|
|||
for (TypeId opt : itv->parts)
|
||||
queue.push_back(opt);
|
||||
}
|
||||
else if (auto mtv = get<MetatableTypeVar>(t))
|
||||
{
|
||||
queue.push_back(mtv->table);
|
||||
queue.push_back(mtv->metatable);
|
||||
}
|
||||
else if (get<GenericTypeVar>(t) || get<AnyTypeVar>(t) || get<FreeTypeVar>(t) || get<SingletonTypeVar>(t) || get<PrimitiveTypeVar>(t))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"TypeId is not supported in a persist call");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -737,371 +770,19 @@ void persist(TypePackId tp)
|
|||
if (p->tail)
|
||||
persist(*p->tail);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct StateDot
|
||||
{
|
||||
StateDot(ToDotOptions opts)
|
||||
: opts(opts)
|
||||
else if (auto vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
persist(vtp->ty);
|
||||
}
|
||||
|
||||
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)
|
||||
else if (get<GenericTypePack>(tp))
|
||||
{
|
||||
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);
|
||||
LUAU_ASSERT(!"TypePackId is not supported in a persist call");
|
||||
}
|
||||
}
|
||||
|
||||
void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName)
|
||||
{
|
||||
if (!tpToIndex.count(tp))
|
||||
tpToIndex[tp] = nextIndex++;
|
||||
|
||||
if (linkName)
|
||||
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName);
|
||||
else
|
||||
formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]);
|
||||
|
||||
visitChildren(tp, tpToIndex[tp]);
|
||||
}
|
||||
|
||||
void StateDot::startNode(int index)
|
||||
{
|
||||
formatAppend(result, "n%d [", index);
|
||||
}
|
||||
|
||||
void StateDot::finishNode()
|
||||
{
|
||||
formatAppend(result, "];\n");
|
||||
}
|
||||
|
||||
void StateDot::startNodeLabel()
|
||||
{
|
||||
formatAppend(result, "label=\"");
|
||||
}
|
||||
|
||||
void StateDot::finishNodeLabel(TypeId ty)
|
||||
{
|
||||
if (opts.showPointers)
|
||||
formatAppend(result, "\n0x%p", ty);
|
||||
// additional common attributes can be added here as well
|
||||
result += "\"";
|
||||
}
|
||||
|
||||
void StateDot::finishNodeLabel(TypePackId tp)
|
||||
{
|
||||
if (opts.showPointers)
|
||||
formatAppend(result, "\n0x%p", tp);
|
||||
// additional common attributes can be added here as well
|
||||
result += "\"";
|
||||
}
|
||||
|
||||
void StateDot::visitChildren(TypeId ty, int index)
|
||||
{
|
||||
if (seenTy.count(ty))
|
||||
return;
|
||||
seenTy.insert(ty);
|
||||
|
||||
startNode(index);
|
||||
startNodeLabel();
|
||||
|
||||
if (const BoundTypeVar* btv = get<BoundTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "BoundTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(btv->boundTo, index);
|
||||
}
|
||||
else if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "FunctionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(ftv->argTypes, index, "arg");
|
||||
visitChild(ftv->retType, index, "ret");
|
||||
}
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
if (ttv->name)
|
||||
formatAppend(result, "TableTypeVar %s", ttv->name->c_str());
|
||||
else if (ttv->syntheticName)
|
||||
formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str());
|
||||
else
|
||||
formatAppend(result, "TableTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
if (ttv->boundTo)
|
||||
return visitChild(*ttv->boundTo, index, "boundTo");
|
||||
|
||||
for (const auto& [name, prop] : ttv->props)
|
||||
visitChild(prop.type, index, name.c_str());
|
||||
if (ttv->indexer)
|
||||
{
|
||||
visitChild(ttv->indexer->indexType, index, "[index]");
|
||||
visitChild(ttv->indexer->indexResultType, index, "[value]");
|
||||
}
|
||||
for (TypeId itp : ttv->instantiatedTypeParams)
|
||||
visitChild(itp, index, "typeParam");
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks)
|
||||
{
|
||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp, index, "typePackParam");
|
||||
}
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "MetatableTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
visitChild(mtv->table, index, "table");
|
||||
visitChild(mtv->metatable, index, "metatable");
|
||||
}
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "UnionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (TypeId opt : utv->options)
|
||||
visitChild(opt, index);
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "IntersectionTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (TypeId part : itv->parts)
|
||||
visitChild(part, index);
|
||||
}
|
||||
else if (const GenericTypeVar* gtv = get<GenericTypeVar>(ty))
|
||||
{
|
||||
if (gtv->explicitName)
|
||||
formatAppend(result, "GenericTypeVar %s", gtv->name.c_str());
|
||||
else
|
||||
formatAppend(result, "GenericTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "FreeTypeVar %d", ftv->index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<AnyTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "AnyTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<PrimitiveTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str());
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<ErrorTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "ErrorTypeVar %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty))
|
||||
{
|
||||
formatAppend(result, "ClassTypeVar %s", ctv->name.c_str());
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
visitChild(prop.type, index, name.c_str());
|
||||
|
||||
if (ctv->parent)
|
||||
visitChild(*ctv->parent, index, "[parent]");
|
||||
|
||||
if (ctv->metatable)
|
||||
visitChild(*ctv->metatable, index, "[metatable]");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"unknown type kind");
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
}
|
||||
|
||||
void StateDot::visitChildren(TypePackId tp, int index)
|
||||
{
|
||||
if (seenTp.count(tp))
|
||||
return;
|
||||
seenTp.insert(tp);
|
||||
|
||||
startNode(index);
|
||||
startNodeLabel();
|
||||
|
||||
if (const BoundTypePack* btp = get<BoundTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "BoundTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
visitChild(btp->boundTo, index);
|
||||
}
|
||||
else if (const TypePack* tpp = get<TypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "TypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
for (TypeId tv : tpp->head)
|
||||
visitChild(tv, index);
|
||||
if (tpp->tail)
|
||||
visitChild(*tpp->tail, index, "tail");
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "VariadicTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
visitChild(vtp->ty, index);
|
||||
}
|
||||
else if (const FreeTypePack* ftp = get<FreeTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "FreeTypePack %d", ftp->index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else if (const GenericTypePack* gtp = get<GenericTypePack>(tp))
|
||||
{
|
||||
if (gtp->explicitName)
|
||||
formatAppend(result, "GenericTypePack %s", gtp->name.c_str());
|
||||
else
|
||||
formatAppend(result, "GenericTypePack %d", gtp->index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else if (get<Unifiable::Error>(tp))
|
||||
{
|
||||
formatAppend(result, "ErrorTypePack %d", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"unknown type pack kind");
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts)
|
||||
{
|
||||
StateDot state{opts};
|
||||
|
||||
state.result = "digraph graphname {\n";
|
||||
state.visitChild(ty, 0);
|
||||
state.result += "}";
|
||||
|
||||
return state.result;
|
||||
}
|
||||
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts)
|
||||
{
|
||||
StateDot state{opts};
|
||||
|
||||
state.result = "digraph graphname {\n";
|
||||
state.visitChild(tp, 0);
|
||||
state.result += "}";
|
||||
|
||||
return state.result;
|
||||
}
|
||||
|
||||
std::string toDot(TypeId ty)
|
||||
{
|
||||
return toDot(ty, {});
|
||||
}
|
||||
|
||||
std::string toDot(TypePackId tp)
|
||||
{
|
||||
return toDot(tp, {});
|
||||
}
|
||||
|
||||
void dumpDot(TypeId ty)
|
||||
{
|
||||
printf("%s\n", toDot(ty).c_str());
|
||||
}
|
||||
|
||||
void dumpDot(TypePackId tp)
|
||||
{
|
||||
printf("%s\n", toDot(tp).c_str());
|
||||
}
|
||||
|
||||
const TypeLevel* getLevel(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
@ -1121,167 +802,6 @@ TypeLevel* getMutableLevel(TypeId ty)
|
|||
return const_cast<TypeLevel*>(getLevel(ty));
|
||||
}
|
||||
|
||||
struct QVarFinder
|
||||
{
|
||||
mutable DenseHashSet<const void*> seen;
|
||||
|
||||
QVarFinder()
|
||||
: seen(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool hasSeen(const void* tv) const
|
||||
{
|
||||
if (seen.contains(tv))
|
||||
return true;
|
||||
|
||||
seen.insert(tv);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasGeneric(TypeId tid) const
|
||||
{
|
||||
if (hasSeen(&tid->ty))
|
||||
return false;
|
||||
|
||||
return Luau::visit(*this, tid->ty);
|
||||
}
|
||||
|
||||
bool hasGeneric(TypePackId tp) const
|
||||
{
|
||||
if (hasSeen(&tp->ty))
|
||||
return false;
|
||||
|
||||
return Luau::visit(*this, tp->ty);
|
||||
}
|
||||
|
||||
bool operator()(const Unifiable::Free&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const Unifiable::Bound<TypeId>& bound) const
|
||||
{
|
||||
return hasGeneric(bound.boundTo);
|
||||
}
|
||||
|
||||
bool operator()(const Unifiable::Generic&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool operator()(const Unifiable::Error&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool operator()(const PrimitiveTypeVar&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const SingletonTypeVar&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const FunctionTypeVar& ftv) const
|
||||
{
|
||||
if (hasGeneric(ftv.argTypes))
|
||||
return true;
|
||||
return hasGeneric(ftv.retType);
|
||||
}
|
||||
|
||||
bool operator()(const TableTypeVar& ttv) const
|
||||
{
|
||||
if (ttv.state == TableState::Generic)
|
||||
return true;
|
||||
|
||||
if (ttv.indexer)
|
||||
{
|
||||
if (hasGeneric(ttv.indexer->indexType))
|
||||
return true;
|
||||
if (hasGeneric(ttv.indexer->indexResultType))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& [_name, prop] : ttv.props)
|
||||
{
|
||||
if (hasGeneric(prop.type))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const MetatableTypeVar& mtv) const
|
||||
{
|
||||
return hasGeneric(mtv.table) || hasGeneric(mtv.metatable);
|
||||
}
|
||||
|
||||
bool operator()(const ClassTypeVar& ctv) const
|
||||
{
|
||||
for (const auto& [name, prop] : ctv.props)
|
||||
{
|
||||
if (hasGeneric(prop.type))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ctv.parent)
|
||||
return hasGeneric(*ctv.parent);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const AnyTypeVar&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const UnionTypeVar& utv) const
|
||||
{
|
||||
for (TypeId tid : utv.options)
|
||||
if (hasGeneric(tid))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const IntersectionTypeVar& utv) const
|
||||
{
|
||||
for (TypeId tid : utv.parts)
|
||||
if (hasGeneric(tid))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const LazyTypeVar&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const Unifiable::Bound<TypePackId>& bound) const
|
||||
{
|
||||
return hasGeneric(bound.boundTo);
|
||||
}
|
||||
|
||||
bool operator()(const TypePack& pack) const
|
||||
{
|
||||
for (TypeId ty : pack.head)
|
||||
if (hasGeneric(ty))
|
||||
return true;
|
||||
|
||||
if (pack.tail)
|
||||
return hasGeneric(*pack.tail);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const VariadicTypePack& pack) const
|
||||
{
|
||||
return hasGeneric(pack.ty);
|
||||
}
|
||||
};
|
||||
|
||||
const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name)
|
||||
{
|
||||
while (cls)
|
||||
|
@ -1317,16 +837,6 @@ bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool hasGeneric(TypeId ty)
|
||||
{
|
||||
return Luau::visit(QVarFinder{}, ty->ty);
|
||||
}
|
||||
|
||||
bool hasGeneric(TypePackId tp)
|
||||
{
|
||||
return Luau::visit(QVarFinder{}, tp->ty);
|
||||
}
|
||||
|
||||
UnionTypeVarIterator::UnionTypeVarIterator(const UnionTypeVar* utv)
|
||||
{
|
||||
LUAU_ASSERT(utv);
|
||||
|
|
|
@ -14,20 +14,90 @@
|
|||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckOpts, false)
|
||||
LUAU_FASTFLAG(LuauShareTxnSeen);
|
||||
LUAU_FASTFLAGVARIABLE(LuauCacheUnifyTableResults, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false)
|
||||
LUAU_FASTFLAG(LuauSingletonTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false)
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAG(LuauProperTypeLevels);
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedUnionMismatchError, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedFunctionMismatchError, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct PromoteTypeLevels
|
||||
{
|
||||
TxnLog& log;
|
||||
TypeLevel minLevel;
|
||||
|
||||
explicit PromoteTypeLevels(TxnLog& log, TypeLevel minLevel)
|
||||
: log(log)
|
||||
, minLevel(minLevel)
|
||||
{}
|
||||
|
||||
template <typename TID, typename T>
|
||||
void promote(TID ty, T* t)
|
||||
{
|
||||
LUAU_ASSERT(t);
|
||||
if (minLevel.subsumesStrict(t->level))
|
||||
{
|
||||
log(ty);
|
||||
t->level = minLevel;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
void cycle(TID) {}
|
||||
|
||||
template<typename TID, typename T>
|
||||
bool operator()(TID, const T&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const FreeTypeVar&)
|
||||
{
|
||||
promote(ty, getMutable<FreeTypeVar>(ty));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const FunctionTypeVar&)
|
||||
{
|
||||
promote(ty, getMutable<FunctionTypeVar>(ty));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const TableTypeVar&)
|
||||
{
|
||||
promote(ty, getMutable<TableTypeVar>(ty));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(TypePackId tp, const FreeTypePack&)
|
||||
{
|
||||
promote(tp, getMutable<FreeTypePack>(tp));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void promoteTypeLevels(TxnLog& log, TypeLevel minLevel, TypeId ty)
|
||||
{
|
||||
PromoteTypeLevels ptl{log, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(ty, ptl, seen);
|
||||
}
|
||||
|
||||
void promoteTypeLevels(TxnLog& log, TypeLevel minLevel, TypePackId tp)
|
||||
{
|
||||
PromoteTypeLevels ptl{log, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(tp, ptl, seen);
|
||||
}
|
||||
|
||||
struct SkipCacheForType
|
||||
{
|
||||
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType)
|
||||
|
@ -130,44 +200,48 @@ static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
|
|||
return *it;
|
||||
}
|
||||
|
||||
// Used for tagged union matching heuristic, returns first singleton type field
|
||||
static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMatchTag(TypeId type)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauExtendedUnionMismatchError);
|
||||
|
||||
type = follow(type);
|
||||
|
||||
if (auto ttv = get<TableTypeVar>(type))
|
||||
{
|
||||
for (auto&& [name, prop] : ttv->props)
|
||||
{
|
||||
if (auto sing = get<SingletonTypeVar>(follow(prop.type)))
|
||||
return {{name, sing}};
|
||||
}
|
||||
}
|
||||
else if (auto mttv = get<MetatableTypeVar>(type))
|
||||
{
|
||||
return getTableMatchTag(mttv->table);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState)
|
||||
: types(types)
|
||||
, mode(mode)
|
||||
, globalScope(std::move(globalScope))
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, counters(&countersData)
|
||||
, counters_DEPRECATED(std::make_shared<UnifierCounters>())
|
||||
, sharedState(sharedState)
|
||||
{
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& ownedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters)
|
||||
: types(types)
|
||||
, mode(mode)
|
||||
, globalScope(std::move(globalScope))
|
||||
, log(ownedSeen)
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, counters(counters ? counters : &countersData)
|
||||
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
|
||||
, sharedState(sharedState)
|
||||
{
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters)
|
||||
Variance variance, UnifierSharedState& sharedState)
|
||||
: types(types)
|
||||
, mode(mode)
|
||||
, globalScope(std::move(globalScope))
|
||||
, log(sharedSeen)
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, counters(counters ? counters : &countersData)
|
||||
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
|
||||
, sharedState(sharedState)
|
||||
{
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
|
@ -175,26 +249,18 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<
|
|||
|
||||
void Unifier::tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
counters->iterationCount = 0;
|
||||
else
|
||||
counters_DEPRECATED->iterationCount = 0;
|
||||
sharedState.counters.iterationCount = 0;
|
||||
|
||||
tryUnify_(superTy, subTy, isFunctionCall, isIntersection);
|
||||
}
|
||||
|
||||
void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
|
||||
{
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
++counters->iterationCount;
|
||||
else
|
||||
++counters_DEPRECATED->iterationCount;
|
||||
++sharedState.counters.iterationCount;
|
||||
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 &&
|
||||
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
|
||||
{
|
||||
errors.push_back(TypeError{location, UnificationTooComplex{}});
|
||||
return;
|
||||
|
@ -244,9 +310,11 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
{
|
||||
occursCheck(superTy, subTy);
|
||||
|
||||
TypeLevel superLevel = l->level;
|
||||
|
||||
// Unification can't change the level of a generic.
|
||||
auto rightGeneric = get<GenericTypeVar>(subTy);
|
||||
if (rightGeneric && !rightGeneric->level.subsumes(l->level))
|
||||
if (rightGeneric && !rightGeneric->level.subsumes(superLevel))
|
||||
{
|
||||
// TODO: a more informative error message? CLI-39912
|
||||
errors.push_back(TypeError{location, GenericError{"Generic subtype escaping scope"}});
|
||||
|
@ -256,7 +324,9 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
// The occurrence check might have caused superTy no longer to be a free type
|
||||
if (!get<ErrorTypeVar>(superTy))
|
||||
{
|
||||
if (auto rightLevel = getMutableLevel(subTy))
|
||||
if (FFlag::LuauProperTypeLevels)
|
||||
promoteTypeLevels(log, superLevel, subTy);
|
||||
else if (auto rightLevel = getMutableLevel(subTy))
|
||||
{
|
||||
if (!rightLevel->subsumes(l->level))
|
||||
*rightLevel = l->level;
|
||||
|
@ -270,6 +340,8 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
}
|
||||
else if (r)
|
||||
{
|
||||
TypeLevel subLevel = r->level;
|
||||
|
||||
occursCheck(subTy, superTy);
|
||||
|
||||
// Unification can't change the level of a generic.
|
||||
|
@ -283,10 +355,16 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
|
||||
if (!get<ErrorTypeVar>(subTy))
|
||||
{
|
||||
if (auto leftLevel = getMutableLevel(superTy))
|
||||
if (FFlag::LuauProperTypeLevels)
|
||||
promoteTypeLevels(log, subLevel, superTy);
|
||||
|
||||
if (auto superLevel = getMutableLevel(superTy))
|
||||
{
|
||||
if (!leftLevel->subsumes(r->level))
|
||||
*leftLevel = r->level;
|
||||
if (!superLevel->subsumes(r->level))
|
||||
{
|
||||
log(superTy);
|
||||
*superLevel = r->level;
|
||||
}
|
||||
}
|
||||
|
||||
log(subTy);
|
||||
|
@ -302,7 +380,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
|
||||
return tryUnifyWithAny(subTy, superTy);
|
||||
|
||||
bool cacheEnabled = FFlag::LuauCacheUnifyTableResults && !isFunctionCall && !isIntersection;
|
||||
bool cacheEnabled = !isFunctionCall && !isIntersection;
|
||||
auto& cache = sharedState.cachedUnify;
|
||||
|
||||
// What if the types are immutable and we proved their relation before
|
||||
|
@ -357,7 +435,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
else if (failed)
|
||||
{
|
||||
if (FFlag::LuauExtendedTypeMismatchError && firstFailedOption)
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Not all union options are compatible", *firstFailedOption}});
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Not all union options are compatible.", *firstFailedOption}});
|
||||
else
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
}
|
||||
|
@ -368,28 +446,46 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
bool found = false;
|
||||
std::optional<TypeError> unificationTooComplex;
|
||||
|
||||
size_t failedOptionCount = 0;
|
||||
std::optional<TypeError> failedOption;
|
||||
|
||||
bool foundHeuristic = false;
|
||||
size_t startIndex = 0;
|
||||
|
||||
if (FFlag::LuauUnionHeuristic)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
const std::string* subName = getName(subTy);
|
||||
if (subName)
|
||||
if (const std::string* subName = getName(subTy))
|
||||
{
|
||||
for (size_t i = 0; i < uv->options.size(); ++i)
|
||||
{
|
||||
const std::string* optionName = getName(uv->options[i]);
|
||||
if (optionName && *optionName == *subName)
|
||||
{
|
||||
found = true;
|
||||
foundHeuristic = true;
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && cacheEnabled)
|
||||
if (FFlag::LuauExtendedUnionMismatchError)
|
||||
{
|
||||
if (auto subMatchTag = getTableMatchTag(subTy))
|
||||
{
|
||||
for (size_t i = 0; i < uv->options.size(); ++i)
|
||||
{
|
||||
auto optionMatchTag = getTableMatchTag(uv->options[i]);
|
||||
if (optionMatchTag && optionMatchTag->first == subMatchTag->first && *optionMatchTag->second == *subMatchTag->second)
|
||||
{
|
||||
foundHeuristic = true;
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundHeuristic && cacheEnabled)
|
||||
{
|
||||
for (size_t i = 0; i < uv->options.size(); ++i)
|
||||
{
|
||||
|
@ -420,15 +516,27 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
{
|
||||
unificationTooComplex = e;
|
||||
}
|
||||
else if (FFlag::LuauExtendedUnionMismatchError && !isNil(type))
|
||||
{
|
||||
failedOptionCount++;
|
||||
|
||||
if (!failedOption)
|
||||
failedOption = {innerState.errors.front()};
|
||||
}
|
||||
|
||||
innerState.log.rollback();
|
||||
}
|
||||
|
||||
if (unificationTooComplex)
|
||||
{
|
||||
errors.push_back(*unificationTooComplex);
|
||||
}
|
||||
else if (!found)
|
||||
{
|
||||
if (FFlag::LuauExtendedTypeMismatchError)
|
||||
if (FFlag::LuauExtendedUnionMismatchError && (failedOptionCount == 1 || foundHeuristic) && failedOption)
|
||||
errors.push_back(
|
||||
TypeError{location, TypeMismatch{superTy, subTy, "None of the union options are compatible. For example:", *failedOption}});
|
||||
else if (FFlag::LuauExtendedTypeMismatchError)
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "none of the union options are compatible"}});
|
||||
else
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
|
@ -461,7 +569,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
if (unificationTooComplex)
|
||||
errors.push_back(*unificationTooComplex);
|
||||
else if (firstFailedOption)
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible", *firstFailedOption}});
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible.", *firstFailedOption}});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -563,8 +671,6 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
|
|||
|
||||
void Unifier::cacheResult(TypeId superTy, TypeId subTy)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCacheUnifyTableResults);
|
||||
|
||||
bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
|
||||
|
||||
if (superTyInfo && *superTyInfo)
|
||||
|
@ -686,10 +792,7 @@ ErrorVec Unifier::canUnify(TypePackId superTy, TypePackId subTy, bool isFunction
|
|||
|
||||
void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
counters->iterationCount = 0;
|
||||
else
|
||||
counters_DEPRECATED->iterationCount = 0;
|
||||
sharedState.counters.iterationCount = 0;
|
||||
|
||||
tryUnify_(superTp, subTp, isFunctionCall);
|
||||
}
|
||||
|
@ -700,16 +803,11 @@ void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall
|
|||
*/
|
||||
void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
|
||||
{
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
++counters->iterationCount;
|
||||
else
|
||||
++counters_DEPRECATED->iterationCount;
|
||||
++sharedState.counters.iterationCount;
|
||||
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 &&
|
||||
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
|
||||
{
|
||||
errors.push_back(TypeError{location, UnificationTooComplex{}});
|
||||
return;
|
||||
|
@ -811,6 +909,10 @@ void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCal
|
|||
if (superIter.good() && subIter.good())
|
||||
{
|
||||
tryUnify_(*superIter, *subIter);
|
||||
|
||||
if (FFlag::LuauExtendedFunctionMismatchError && !errors.empty() && !firstPackErrorPos)
|
||||
firstPackErrorPos = loopCount;
|
||||
|
||||
superIter.advance();
|
||||
subIter.advance();
|
||||
continue;
|
||||
|
@ -893,13 +995,13 @@ void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCal
|
|||
|
||||
while (superIter.good())
|
||||
{
|
||||
tryUnify_(singletonTypes.errorRecoveryType(), *superIter);
|
||||
tryUnify_(getSingletonTypes().errorRecoveryType(), *superIter);
|
||||
superIter.advance();
|
||||
}
|
||||
|
||||
while (subIter.good())
|
||||
{
|
||||
tryUnify_(singletonTypes.errorRecoveryType(), *subIter);
|
||||
tryUnify_(getSingletonTypes().errorRecoveryType(), *subIter);
|
||||
subIter.advance();
|
||||
}
|
||||
|
||||
|
@ -957,14 +1059,22 @@ void Unifier::tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCal
|
|||
if (numGenerics != rf->generics.size())
|
||||
{
|
||||
numGenerics = std::min(lf->generics.size(), rf->generics.size());
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
|
||||
if (FFlag::LuauExtendedFunctionMismatchError)
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "different number of generic type parameters"}});
|
||||
else
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
}
|
||||
|
||||
size_t numGenericPacks = lf->genericPacks.size();
|
||||
if (numGenericPacks != rf->genericPacks.size())
|
||||
{
|
||||
numGenericPacks = std::min(lf->genericPacks.size(), rf->genericPacks.size());
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
|
||||
if (FFlag::LuauExtendedFunctionMismatchError)
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "different number of generic type pack parameters"}});
|
||||
else
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numGenerics; i++)
|
||||
|
@ -976,13 +1086,49 @@ void Unifier::tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCal
|
|||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
|
||||
ctx = CountMismatch::Arg;
|
||||
innerState.tryUnify_(rf->argTypes, lf->argTypes, isFunctionCall);
|
||||
if (FFlag::LuauExtendedFunctionMismatchError)
|
||||
{
|
||||
innerState.ctx = CountMismatch::Arg;
|
||||
innerState.tryUnify_(rf->argTypes, lf->argTypes, isFunctionCall);
|
||||
|
||||
ctx = CountMismatch::Result;
|
||||
innerState.tryUnify_(lf->retType, rf->retType);
|
||||
bool reported = !innerState.errors.empty();
|
||||
|
||||
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
||||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
errors.push_back(*e);
|
||||
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
||||
errors.push_back(
|
||||
TypeError{location, TypeMismatch{superTy, subTy, format("Argument #%d type is not compatible.", *innerState.firstPackErrorPos),
|
||||
innerState.errors.front()}});
|
||||
else if (!innerState.errors.empty())
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
||||
|
||||
innerState.ctx = CountMismatch::Result;
|
||||
innerState.tryUnify_(lf->retType, rf->retType);
|
||||
|
||||
if (!reported)
|
||||
{
|
||||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
errors.push_back(*e);
|
||||
else if (!innerState.errors.empty() && size(lf->retType) == 1 && finite(lf->retType))
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front()}});
|
||||
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
||||
errors.push_back(
|
||||
TypeError{location, TypeMismatch{superTy, subTy, format("Return #%d type is not compatible.", *innerState.firstPackErrorPos),
|
||||
innerState.errors.front()}});
|
||||
else if (!innerState.errors.empty())
|
||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx = CountMismatch::Arg;
|
||||
innerState.tryUnify_(rf->argTypes, lf->argTypes, isFunctionCall);
|
||||
|
||||
ctx = CountMismatch::Result;
|
||||
innerState.tryUnify_(lf->retType, rf->retType);
|
||||
|
||||
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
||||
}
|
||||
|
||||
log.concat(std::move(innerState.log));
|
||||
}
|
||||
|
@ -1034,7 +1180,7 @@ struct Resetter
|
|||
|
||||
void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
|
||||
{
|
||||
if (!FFlag::LuauTableSubtypingVariance)
|
||||
if (!FFlag::LuauTableSubtypingVariance2)
|
||||
return DEPRECATED_tryUnifyTables(left, right, isIntersection);
|
||||
|
||||
TableTypeVar* lt = getMutable<TableTypeVar>(left);
|
||||
|
@ -1173,7 +1319,7 @@ void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
|
|||
// TODO: hopefully readonly/writeonly properties will fix this.
|
||||
Property clone = prop;
|
||||
clone.type = deeplyOptional(clone.type);
|
||||
log(lt);
|
||||
log(left);
|
||||
lt->props[name] = clone;
|
||||
}
|
||||
else if (variance == Covariant)
|
||||
|
@ -1186,7 +1332,7 @@ void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
|
|||
}
|
||||
else if (lt->state == TableState::Free)
|
||||
{
|
||||
log(lt);
|
||||
log(left);
|
||||
lt->props[name] = prop;
|
||||
}
|
||||
else
|
||||
|
@ -1216,7 +1362,7 @@ void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
|
|||
// e.g. table.insert(t, 1) where t is a non-sealed table and doesn't have an indexer.
|
||||
// TODO: we only need to do this if the supertype's indexer is read/write
|
||||
// since that can add indexed elements.
|
||||
log(rt);
|
||||
log(right);
|
||||
rt->indexer = lt->indexer;
|
||||
}
|
||||
}
|
||||
|
@ -1225,7 +1371,7 @@ void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
|
|||
// Symmetric if we are invariant
|
||||
if (lt->state == TableState::Unsealed || lt->state == TableState::Free)
|
||||
{
|
||||
log(lt);
|
||||
log(left);
|
||||
lt->indexer = rt->indexer;
|
||||
}
|
||||
}
|
||||
|
@ -1281,15 +1427,15 @@ TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> see
|
|||
TableTypeVar* resultTtv = getMutable<TableTypeVar>(result);
|
||||
for (auto& [name, prop] : resultTtv->props)
|
||||
prop.type = deeplyOptional(prop.type, seen);
|
||||
return types->addType(UnionTypeVar{{singletonTypes.nilType, result}});
|
||||
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, result}});
|
||||
}
|
||||
else
|
||||
return types->addType(UnionTypeVar{{singletonTypes.nilType, ty}});
|
||||
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, ty}});
|
||||
}
|
||||
|
||||
void Unifier::DEPRECATED_tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance);
|
||||
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
|
||||
Resetter resetter{&variance};
|
||||
variance = Invariant;
|
||||
|
||||
|
@ -1507,7 +1653,7 @@ void Unifier::tryUnifySealedTables(TypeId left, TypeId right, bool isIntersectio
|
|||
}
|
||||
else if (lt->indexer)
|
||||
{
|
||||
innerState.tryUnify_(lt->indexer->indexType, singletonTypes.stringType);
|
||||
innerState.tryUnify_(lt->indexer->indexType, getSingletonTypes().stringType);
|
||||
// We already try to unify properties in both tables.
|
||||
// Skip those and just look for the ones remaining and see if they fit into the indexer.
|
||||
for (const auto& [name, type] : rt->props)
|
||||
|
@ -1676,7 +1822,7 @@ void Unifier::tryUnifyWithClass(TypeId superTy, TypeId subTy, bool reversed)
|
|||
ok = false;
|
||||
errors.push_back(TypeError{location, UnknownProperty{superTy, propName}});
|
||||
if (!FFlag::LuauExtendedClassMismatchError)
|
||||
tryUnify_(prop.type, singletonTypes.errorRecoveryType());
|
||||
tryUnify_(prop.type, getSingletonTypes().errorRecoveryType());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1727,39 +1873,8 @@ void Unifier::tryUnify(const TableIndexer& superIndexer, const TableIndexer& sub
|
|||
tryUnify_(superIndexer.indexResultType, subIndexer.indexResultType);
|
||||
}
|
||||
|
||||
static void queueTypePack_DEPRECATED(
|
||||
std::vector<TypeId>& queue, std::unordered_set<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTypecheckOpts);
|
||||
|
||||
while (true)
|
||||
{
|
||||
a = follow(a);
|
||||
|
||||
if (seenTypePacks.count(a))
|
||||
break;
|
||||
seenTypePacks.insert(a);
|
||||
|
||||
if (get<Unifiable::Free>(a))
|
||||
{
|
||||
state.log(a);
|
||||
*asMutable(a) = Unifiable::Bound{anyTypePack};
|
||||
}
|
||||
else if (auto tp = get<TypePack>(a))
|
||||
{
|
||||
queue.insert(queue.end(), tp->head.begin(), tp->head.end());
|
||||
if (tp->tail)
|
||||
a = *tp->tail;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
|
||||
|
||||
while (true)
|
||||
{
|
||||
a = follow(a);
|
||||
|
@ -1837,66 +1952,9 @@ void Unifier::tryUnifyVariadics(TypePackId superTp, TypePackId subTp, bool rever
|
|||
}
|
||||
}
|
||||
|
||||
static void tryUnifyWithAny_DEPRECATED(
|
||||
std::vector<TypeId>& queue, Unifier& state, std::unordered_set<TypePackId>& seenTypePacks, TypeId anyType, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTypecheckOpts);
|
||||
|
||||
std::unordered_set<TypeId> seen;
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
TypeId ty = follow(queue.back());
|
||||
queue.pop_back();
|
||||
if (seen.count(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
|
||||
if (get<FreeTypeVar>(ty))
|
||||
{
|
||||
state.log(ty);
|
||||
*asMutable(ty) = BoundTypeVar{anyType};
|
||||
}
|
||||
else if (auto fun = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->argTypes, anyTypePack);
|
||||
queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->retType, anyTypePack);
|
||||
}
|
||||
else if (auto table = get<TableTypeVar>(ty))
|
||||
{
|
||||
for (const auto& [_name, prop] : table->props)
|
||||
queue.push_back(prop.type);
|
||||
|
||||
if (table->indexer)
|
||||
{
|
||||
queue.push_back(table->indexer->indexType);
|
||||
queue.push_back(table->indexer->indexResultType);
|
||||
}
|
||||
}
|
||||
else if (auto mt = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
queue.push_back(mt->table);
|
||||
queue.push_back(mt->metatable);
|
||||
}
|
||||
else if (get<ClassTypeVar>(ty))
|
||||
{
|
||||
// ClassTypeVars never contain free typevars.
|
||||
}
|
||||
else if (auto union_ = get<UnionTypeVar>(ty))
|
||||
queue.insert(queue.end(), union_->options.begin(), union_->options.end());
|
||||
else if (auto intersection = get<IntersectionTypeVar>(ty))
|
||||
queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end());
|
||||
else
|
||||
{
|
||||
} // Primitives, any, errors, and generics are left untouched.
|
||||
}
|
||||
}
|
||||
|
||||
static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
|
||||
TypeId anyType, TypePackId anyTypePack)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
TypeId ty = follow(queue.back());
|
||||
|
@ -1949,83 +2007,36 @@ void Unifier::tryUnifyWithAny(TypeId any, TypeId ty)
|
|||
{
|
||||
LUAU_ASSERT(get<AnyTypeVar>(any) || get<ErrorTypeVar>(any));
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
// These types are not visited in general loop below
|
||||
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
|
||||
return;
|
||||
}
|
||||
// These types are not visited in general loop below
|
||||
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
|
||||
return;
|
||||
|
||||
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes.anyType}});
|
||||
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
|
||||
|
||||
const TypePackId anyTP = get<AnyTypeVar>(any) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
std::vector<TypeId> queue = {ty};
|
||||
std::vector<TypeId> queue = {ty};
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempSeenTy_DEPRECATED.clear();
|
||||
tempSeenTp_DEPRECATED.clear();
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, singletonTypes.anyType, anyTP);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unordered_set<TypePackId> seenTypePacks;
|
||||
std::vector<TypeId> queue = {ty};
|
||||
|
||||
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, singletonTypes.anyType, anyTP);
|
||||
}
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, getSingletonTypes().anyType, anyTP);
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
|
||||
{
|
||||
LUAU_ASSERT(get<Unifiable::Error>(any));
|
||||
|
||||
const TypeId anyTy = singletonTypes.errorRecoveryType();
|
||||
const TypeId anyTy = getSingletonTypes().errorRecoveryType();
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
std::vector<TypeId> queue;
|
||||
std::vector<TypeId> queue;
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any);
|
||||
queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any);
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempSeenTy_DEPRECATED.clear();
|
||||
tempSeenTp_DEPRECATED.clear();
|
||||
|
||||
queueTypePack(queue, tempSeenTp_DEPRECATED, *this, ty, any);
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, anyTy, any);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unordered_set<TypePackId> seenTypePacks;
|
||||
std::vector<TypeId> queue;
|
||||
|
||||
queueTypePack_DEPRECATED(queue, seenTypePacks, *this, ty, any);
|
||||
|
||||
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, anyTy, any);
|
||||
}
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any);
|
||||
}
|
||||
|
||||
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
|
||||
|
@ -2035,46 +2046,22 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
|
|||
|
||||
void Unifier::occursCheck(TypeId needle, TypeId haystack)
|
||||
{
|
||||
std::unordered_set<TypeId> seen_DEPRECATED;
|
||||
sharedState.tempSeenTy.clear();
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
sharedState.tempSeenTy.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTy, needle, haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
tempSeenTy_DEPRECATED.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, tempSeenTy_DEPRECATED, needle, haystack);
|
||||
}
|
||||
return occursCheck(sharedState.tempSeenTy, needle, haystack);
|
||||
}
|
||||
|
||||
void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
|
||||
void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
|
||||
{
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
needle = follow(needle);
|
||||
haystack = follow(haystack);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
|
||||
seen.insert(haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
|
||||
return;
|
||||
|
||||
seen_DEPRECATED.insert(haystack);
|
||||
}
|
||||
seen.insert(haystack);
|
||||
|
||||
if (get<Unifiable::Error>(needle))
|
||||
return;
|
||||
|
@ -2086,12 +2073,12 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
|
|||
{
|
||||
errors.push_back(TypeError{location, OccursCheckFailed{}});
|
||||
log(needle);
|
||||
*asMutable(needle) = *singletonTypes.errorRecoveryType();
|
||||
*asMutable(needle) = *getSingletonTypes().errorRecoveryType();
|
||||
return;
|
||||
}
|
||||
|
||||
auto check = [&](TypeId tv) {
|
||||
occursCheck(seen_DEPRECATED, seen, needle, tv);
|
||||
occursCheck(seen, needle, tv);
|
||||
};
|
||||
|
||||
if (get<FreeTypeVar>(haystack))
|
||||
|
@ -2121,43 +2108,20 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
|
|||
|
||||
void Unifier::occursCheck(TypePackId needle, TypePackId haystack)
|
||||
{
|
||||
std::unordered_set<TypePackId> seen_DEPRECATED;
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
if (FFlag::LuauCacheUnifyTableResults)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTp, needle, haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
tempSeenTp_DEPRECATED.clear();
|
||||
|
||||
return occursCheck(seen_DEPRECATED, tempSeenTp_DEPRECATED, needle, haystack);
|
||||
}
|
||||
return occursCheck(sharedState.tempSeenTp, needle, haystack);
|
||||
}
|
||||
|
||||
void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
||||
void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
||||
{
|
||||
needle = follow(needle);
|
||||
haystack = follow(haystack);
|
||||
|
||||
if (FFlag::LuauTypecheckOpts)
|
||||
{
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
if (seen.find(haystack))
|
||||
return;
|
||||
|
||||
seen.insert(haystack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
|
||||
return;
|
||||
|
||||
seen_DEPRECATED.insert(haystack);
|
||||
}
|
||||
seen.insert(haystack);
|
||||
|
||||
if (get<Unifiable::Error>(needle))
|
||||
return;
|
||||
|
@ -2165,8 +2129,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
|
|||
if (!get<Unifiable::Free>(needle))
|
||||
ice("Expected needle pack to be free");
|
||||
|
||||
RecursionLimiter _ra(
|
||||
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
while (!get<ErrorTypeVar>(haystack))
|
||||
{
|
||||
|
@ -2174,7 +2137,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
|
|||
{
|
||||
errors.push_back(TypeError{location, OccursCheckFailed{}});
|
||||
log(needle);
|
||||
*asMutable(needle) = *singletonTypes.errorRecoveryTypePack();
|
||||
*asMutable(needle) = *getSingletonTypes().errorRecoveryTypePack();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2186,8 +2149,8 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
|
|||
{
|
||||
if (auto f = get<FunctionTypeVar>(follow(ty)))
|
||||
{
|
||||
occursCheck(seen_DEPRECATED, seen, needle, f->argTypes);
|
||||
occursCheck(seen_DEPRECATED, seen, needle, f->retType);
|
||||
occursCheck(seen, needle, f->argTypes);
|
||||
occursCheck(seen, needle, f->retType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2204,10 +2167,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
|
|||
|
||||
Unifier Unifier::makeChildUnifier()
|
||||
{
|
||||
if (FFlag::LuauShareTxnSeen)
|
||||
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
|
||||
else
|
||||
return Unifier{types, mode, globalScope, log.ownedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
|
||||
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState};
|
||||
}
|
||||
|
||||
bool Unifier::isNonstrictMode() const
|
||||
|
@ -2231,7 +2191,7 @@ void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const s
|
|||
errors.push_back(*e);
|
||||
else if (!innerErrors.empty())
|
||||
errors.push_back(
|
||||
TypeError{location, TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible", prop.c_str()), innerErrors.front()}});
|
||||
TypeError{location, TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front()}});
|
||||
}
|
||||
|
||||
void Unifier::ice(const std::string& message, const Location& location)
|
||||
|
|
|
@ -10,11 +10,8 @@
|
|||
// See docs/SyntaxChanges.md for an explanation.
|
||||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeAliasPacks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseTypePackTypeParameters, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false)
|
||||
|
@ -161,7 +158,7 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
|
|||
{
|
||||
std::vector<std::string> hotcomments;
|
||||
|
||||
while (isComment(p.lexer.current()) || (FFlag::LuauCaptureBrokenCommentSpans && p.lexer.current().type == Lexeme::BrokenComment))
|
||||
while (isComment(p.lexer.current()) || p.lexer.current().type == Lexeme::BrokenComment)
|
||||
{
|
||||
const char* text = p.lexer.current().data;
|
||||
unsigned int length = p.lexer.current().length;
|
||||
|
@ -782,8 +779,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
|
|||
|
||||
AstType* type = parseTypeAnnotation();
|
||||
|
||||
return allocator.alloc<AstStatTypeAlias>(
|
||||
Location(start, type->location), name->name, generics, FFlag::LuauTypeAliasPacks ? genericPacks : AstArray<AstName>{}, type, exported);
|
||||
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, generics, genericPacks, type, exported);
|
||||
}
|
||||
|
||||
AstDeclaredClassProp Parser::parseDeclaredClassMethod()
|
||||
|
@ -1602,30 +1598,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
|
||||
}
|
||||
|
||||
if (FFlag::LuauParseTypePackTypeParameters)
|
||||
bool hasParameters = false;
|
||||
AstArray<AstTypeOrPack> parameters{};
|
||||
|
||||
if (lexer.current().type == '<')
|
||||
{
|
||||
bool hasParameters = false;
|
||||
AstArray<AstTypeOrPack> parameters{};
|
||||
|
||||
if (lexer.current().type == '<')
|
||||
{
|
||||
hasParameters = true;
|
||||
parameters = parseTypeParams();
|
||||
}
|
||||
|
||||
Location end = lexer.previousLocation();
|
||||
|
||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
|
||||
hasParameters = true;
|
||||
parameters = parseTypeParams();
|
||||
}
|
||||
else
|
||||
{
|
||||
AstArray<AstTypeOrPack> generics = parseTypeParams();
|
||||
|
||||
Location end = lexer.previousLocation();
|
||||
Location end = lexer.previousLocation();
|
||||
|
||||
// false in 'hasParameterList' as it is not used without FFlagLuauTypeAliasPacks
|
||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, false, generics), {}};
|
||||
}
|
||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
|
||||
}
|
||||
else if (lexer.current().type == '{')
|
||||
{
|
||||
|
@ -2414,37 +2398,24 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
|
|||
|
||||
while (true)
|
||||
{
|
||||
if (FFlag::LuauParseTypePackTypeParameters)
|
||||
if (shouldParseTypePackAnnotation(lexer))
|
||||
{
|
||||
if (shouldParseTypePackAnnotation(lexer))
|
||||
{
|
||||
auto typePack = parseTypePackAnnotation();
|
||||
auto typePack = parseTypePackAnnotation();
|
||||
|
||||
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
|
||||
parameters.push_back({{}, typePack});
|
||||
}
|
||||
else if (lexer.current().type == '(')
|
||||
{
|
||||
auto [type, typePack] = parseTypeOrPackAnnotation();
|
||||
parameters.push_back({{}, typePack});
|
||||
}
|
||||
else if (lexer.current().type == '(')
|
||||
{
|
||||
auto [type, typePack] = parseTypeOrPackAnnotation();
|
||||
|
||||
if (typePack)
|
||||
{
|
||||
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
|
||||
parameters.push_back({{}, typePack});
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.push_back({type, {}});
|
||||
}
|
||||
}
|
||||
else if (lexer.current().type == '>' && parameters.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (typePack)
|
||||
parameters.push_back({{}, typePack});
|
||||
else
|
||||
{
|
||||
parameters.push_back({parseTypeAnnotation(), {}});
|
||||
}
|
||||
parameters.push_back({type, {}});
|
||||
}
|
||||
else if (lexer.current().type == '>' && parameters.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2808,7 +2779,7 @@ const Lexeme& Parser::nextLexeme()
|
|||
const Lexeme& lexeme = lexer.next(/*skipComments*/ false);
|
||||
// Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme.
|
||||
// The parser will turn this into a proper syntax error.
|
||||
if (FFlag::LuauCaptureBrokenCommentSpans && lexeme.type == Lexeme::BrokenComment)
|
||||
if (lexeme.type == Lexeme::BrokenComment)
|
||||
commentLocations.push_back(Comment{lexeme.type, lexeme.location});
|
||||
if (isComment(lexeme))
|
||||
commentLocations.push_back(Comment{lexeme.type, lexeme.location});
|
||||
|
|
|
@ -11,26 +11,33 @@
|
|||
enum class ReportFormat
|
||||
{
|
||||
Default,
|
||||
Luacheck
|
||||
Luacheck,
|
||||
Gnu,
|
||||
};
|
||||
|
||||
static void report(ReportFormat format, const char* name, const Luau::Location& location, const char* type, const char* message)
|
||||
static void report(ReportFormat format, const char* name, const Luau::Location& loc, const char* type, const char* message)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ReportFormat::Default:
|
||||
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message);
|
||||
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, loc.begin.line + 1, loc.begin.column + 1, type, message);
|
||||
break;
|
||||
|
||||
case ReportFormat::Luacheck:
|
||||
{
|
||||
// Note: luacheck's end column is inclusive but our end column is exclusive
|
||||
// In addition, luacheck doesn't support multi-line messages, so if the error is multiline we'll fake end column as 100 and hope for the best
|
||||
int columnEnd = (location.begin.line == location.end.line) ? location.end.column : 100;
|
||||
int columnEnd = (loc.begin.line == loc.end.line) ? loc.end.column : 100;
|
||||
|
||||
fprintf(stdout, "%s:%d:%d-%d: (W0) %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, columnEnd, type, message);
|
||||
// Use stdout to match luacheck behavior
|
||||
fprintf(stdout, "%s:%d:%d-%d: (W0) %s: %s\n", name, loc.begin.line + 1, loc.begin.column + 1, columnEnd, type, message);
|
||||
break;
|
||||
}
|
||||
|
||||
case ReportFormat::Gnu:
|
||||
// Note: GNU end column is inclusive but our end column is exclusive
|
||||
fprintf(stderr, "%s:%d.%d-%d.%d: %s: %s\n", name, loc.begin.line + 1, loc.begin.column + 1, loc.end.line + 1, loc.end.column, type, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,6 +104,7 @@ static void displayHelp(const char* argv0)
|
|||
printf("\n");
|
||||
printf("Available options:\n");
|
||||
printf(" --formatter=plain: report analysis errors in Luacheck-compatible format\n");
|
||||
printf(" --formatter=gnu: report analysis errors in GNU-compatible format\n");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line)
|
||||
|
@ -121,7 +129,7 @@ struct CliFileResolver : Luau::FileResolver
|
|||
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
|
||||
{
|
||||
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau";
|
||||
if (!moduleExists(name))
|
||||
if (!readFile(name))
|
||||
{
|
||||
// fall back to .lua if a module with .luau doesn't exist
|
||||
name = std::string(expr->value.data, expr->value.size) + ".lua";
|
||||
|
@ -132,27 +140,6 @@ struct CliFileResolver : Luau::FileResolver
|
|||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool moduleExists(const Luau::ModuleName& name) const override
|
||||
{
|
||||
return !!readFile(name);
|
||||
}
|
||||
|
||||
|
||||
std::optional<Luau::ModuleName> fromAstFragment(Luau::AstExpr* expr) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Luau::ModuleName concat(const Luau::ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs + "/" + std::string(rhs);
|
||||
}
|
||||
|
||||
std::optional<Luau::ModuleName> getParentModuleName(const Luau::ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct CliConfigResolver : Luau::ConfigResolver
|
||||
|
@ -222,6 +209,8 @@ int main(int argc, char** argv)
|
|||
|
||||
if (strcmp(argv[i], "--formatter=plain") == 0)
|
||||
format = ReportFormat::Luacheck;
|
||||
else if (strcmp(argv[i], "--formatter=gnu") == 0)
|
||||
format = ReportFormat::Gnu;
|
||||
else if (strcmp(argv[i], "--annotate") == 0)
|
||||
annotate = true;
|
||||
}
|
||||
|
|
88
CLI/Coverage.cpp
Normal file
88
CLI/Coverage.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Coverage.h"
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Coverage
|
||||
{
|
||||
lua_State* L = nullptr;
|
||||
std::vector<int> functions;
|
||||
} gCoverage;
|
||||
|
||||
void coverageInit(lua_State* L)
|
||||
{
|
||||
gCoverage.L = lua_mainthread(L);
|
||||
}
|
||||
|
||||
bool coverageActive()
|
||||
{
|
||||
return gCoverage.L != nullptr;
|
||||
}
|
||||
|
||||
void coverageTrack(lua_State* L, int funcindex)
|
||||
{
|
||||
int ref = lua_ref(L, funcindex);
|
||||
gCoverage.functions.push_back(ref);
|
||||
}
|
||||
|
||||
static void coverageCallback(void* context, const char* function, int linedefined, int depth, const int* hits, size_t size)
|
||||
{
|
||||
FILE* f = static_cast<FILE*>(context);
|
||||
|
||||
std::string name;
|
||||
|
||||
if (depth == 0)
|
||||
name = "<main>";
|
||||
else if (function)
|
||||
name = std::string(function) + ":" + std::to_string(linedefined);
|
||||
else
|
||||
name = "<anonymous>:" + std::to_string(linedefined);
|
||||
|
||||
fprintf(f, "FN:%d,%s\n", linedefined, name.c_str());
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
if (hits[i] != -1)
|
||||
{
|
||||
fprintf(f, "FNDA:%d,%s\n", hits[i], name.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
if (hits[i] != -1)
|
||||
fprintf(f, "DA:%d,%d\n", int(i), hits[i]);
|
||||
}
|
||||
|
||||
void coverageDump(const char* path)
|
||||
{
|
||||
lua_State* L = gCoverage.L;
|
||||
|
||||
FILE* f = fopen(path, "w");
|
||||
if (!f)
|
||||
{
|
||||
fprintf(stderr, "Error opening coverage %s\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(f, "TN:\n");
|
||||
|
||||
for (int fref: gCoverage.functions)
|
||||
{
|
||||
lua_getref(L, fref);
|
||||
|
||||
lua_Debug ar = {};
|
||||
lua_getinfo(L, -1, "s", &ar);
|
||||
|
||||
fprintf(f, "SF:%s\n", ar.short_src);
|
||||
lua_getcoverage(L, -1, f, coverageCallback);
|
||||
fprintf(f, "end_of_record\n");
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
printf("Coverage dump written to %s (%d functions)\n", path, int(gCoverage.functions.size()));
|
||||
}
|
10
CLI/Coverage.h
Normal file
10
CLI/Coverage.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
struct lua_State;
|
||||
|
||||
void coverageInit(lua_State* L);
|
||||
bool coverageActive();
|
||||
|
||||
void coverageTrack(lua_State* L, int funcindex);
|
||||
void coverageDump(const char* path);
|
|
@ -67,6 +67,10 @@ std::optional<std::string> readFile(const std::string& name)
|
|||
if (read != size_t(length))
|
||||
return std::nullopt;
|
||||
|
||||
// Skip first line if it's a shebang
|
||||
if (length > 2 && result[0] == '#' && result[1] == '!')
|
||||
result.erase(0, result.find('\n'));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -110,12 +110,12 @@ void profilerStop()
|
|||
gProfiler.thread.join();
|
||||
}
|
||||
|
||||
void profilerDump(const char* name)
|
||||
void profilerDump(const char* path)
|
||||
{
|
||||
FILE* f = fopen(name, "wb");
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f)
|
||||
{
|
||||
fprintf(stderr, "Error opening profile %s\n", name);
|
||||
fprintf(stderr, "Error opening profile %s\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ void profilerDump(const char* name)
|
|||
|
||||
fclose(f);
|
||||
|
||||
printf("Profiler dump written to %s (total runtime %.3f seconds, %lld samples, %lld stacks)\n", name, double(total) / 1e6,
|
||||
printf("Profiler dump written to %s (total runtime %.3f seconds, %lld samples, %lld stacks)\n", path, double(total) / 1e6,
|
||||
static_cast<long long>(gProfiler.samples.load()), static_cast<long long>(gProfiler.data.size()));
|
||||
|
||||
uint64_t totalgc = 0;
|
||||
|
|
|
@ -5,4 +5,4 @@ struct lua_State;
|
|||
|
||||
void profilerStart(lua_State* L, int frequency);
|
||||
void profilerStop();
|
||||
void profilerDump(const char* name);
|
||||
void profilerDump(const char* path);
|
||||
|
|
36
CLI/Repl.cpp
36
CLI/Repl.cpp
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "FileUtils.h"
|
||||
#include "Profiler.h"
|
||||
#include "Coverage.h"
|
||||
|
||||
#include "linenoise.hpp"
|
||||
|
||||
|
@ -24,6 +25,16 @@ enum class CompileFormat
|
|||
Binary
|
||||
};
|
||||
|
||||
static Luau::CompileOptions copts()
|
||||
{
|
||||
Luau::CompileOptions result = {};
|
||||
result.optimizationLevel = 1;
|
||||
result.debugLevel = 1;
|
||||
result.coverageLevel = coverageActive() ? 2 : 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int lua_loadstring(lua_State* L)
|
||||
{
|
||||
size_t l = 0;
|
||||
|
@ -32,7 +43,7 @@ static int lua_loadstring(lua_State* L)
|
|||
|
||||
lua_setsafeenv(L, LUA_ENVIRONINDEX, false);
|
||||
|
||||
std::string bytecode = Luau::compile(std::string(s, l));
|
||||
std::string bytecode = Luau::compile(std::string(s, l), copts());
|
||||
if (luau_load(L, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
|
||||
return 1;
|
||||
|
||||
|
@ -79,9 +90,12 @@ static int lua_require(lua_State* L)
|
|||
luaL_sandboxthread(ML);
|
||||
|
||||
// now we can compile & run module on the new thread
|
||||
std::string bytecode = Luau::compile(*source);
|
||||
std::string bytecode = Luau::compile(*source, copts());
|
||||
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (coverageActive())
|
||||
coverageTrack(ML, -1);
|
||||
|
||||
int status = lua_resume(ML, L, 0);
|
||||
|
||||
if (status == 0)
|
||||
|
@ -149,7 +163,7 @@ static void setupState(lua_State* L)
|
|||
|
||||
static std::string runCode(lua_State* L, const std::string& source)
|
||||
{
|
||||
std::string bytecode = Luau::compile(source);
|
||||
std::string bytecode = Luau::compile(source, copts());
|
||||
|
||||
if (luau_load(L, "=stdin", bytecode.data(), bytecode.size(), 0) != 0)
|
||||
{
|
||||
|
@ -329,11 +343,14 @@ static bool runFile(const char* name, lua_State* GL)
|
|||
|
||||
std::string chunkname = "=" + std::string(name);
|
||||
|
||||
std::string bytecode = Luau::compile(*source);
|
||||
std::string bytecode = Luau::compile(*source, copts());
|
||||
int status = 0;
|
||||
|
||||
if (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (coverageActive())
|
||||
coverageTrack(L, -1);
|
||||
|
||||
status = lua_resume(L, NULL, 0);
|
||||
}
|
||||
else
|
||||
|
@ -437,6 +454,7 @@ static void displayHelp(const char* argv0)
|
|||
printf("\n");
|
||||
printf("Available options:\n");
|
||||
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
|
||||
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line)
|
||||
|
@ -495,6 +513,7 @@ int main(int argc, char** argv)
|
|||
setupState(L);
|
||||
|
||||
int profile = 0;
|
||||
bool coverage = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
|
@ -505,11 +524,16 @@ int main(int argc, char** argv)
|
|||
profile = 10000; // default to 10 KHz
|
||||
else if (strncmp(argv[i], "--profile=", 10) == 0)
|
||||
profile = atoi(argv[i] + 10);
|
||||
else if (strcmp(argv[i], "--coverage") == 0)
|
||||
coverage = true;
|
||||
}
|
||||
|
||||
if (profile)
|
||||
profilerStart(L, profile);
|
||||
|
||||
if (coverage)
|
||||
coverageInit(L);
|
||||
|
||||
std::vector<std::string> files = getSourceFiles(argc, argv);
|
||||
|
||||
int failed = 0;
|
||||
|
@ -523,7 +547,9 @@ int main(int argc, char** argv)
|
|||
profilerDump("profile.out");
|
||||
}
|
||||
|
||||
if (coverage)
|
||||
coverageDump("coverage.out");
|
||||
|
||||
return failed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,10 +26,9 @@ If you're thinking of adding a new feature to the language, library, analysis to
|
|||
Luau team has internal priorities and a roadmap that may or may not align with specific features, so before starting to work on a feature please submit an issue describing the missing feature that you'd like to add.
|
||||
|
||||
For features that result in observable change of language syntax or semantics, you'd need to [create an RFC](https://github.com/Roblox/luau/blob/master/rfcs/README.md) to make sure that the feature is needed and well designed.
|
||||
Similarly to the above, please create an issue first so that we can see if we should go through with an RFC process.
|
||||
|
||||
Finally, please note that Luau tries to carry a minimal feature set. All features must be evaluated not just for the benefits that they provide, but also for the downsides/costs in terms of language simplicity, maintainability, cross-feature interaction etc.
|
||||
As such, feature requests may not be accepted, or may get to an RFC stage and get rejected there - don't expect Luau to gain a feature just because another programming language has it.
|
||||
As such, feature requests may not be accepted even if a comprehensive RFC is written - don't expect Luau to gain a feature just because another programming language has it.
|
||||
|
||||
## Code style
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <bitset>
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreloadClosures, false)
|
||||
LUAU_FASTFLAG(LuauIfElseExpressionBaseSupport)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBit32CountBuiltin, false)
|
||||
|
||||
|
@ -321,13 +320,15 @@ struct Compiler
|
|||
compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i));
|
||||
}
|
||||
|
||||
setDebugLine(expr->func);
|
||||
setDebugLineEnd(expr->func);
|
||||
|
||||
if (expr->self)
|
||||
{
|
||||
AstExprIndexName* fi = expr->func->as<AstExprIndexName>();
|
||||
LUAU_ASSERT(fi);
|
||||
|
||||
setDebugLine(fi->indexLocation);
|
||||
|
||||
BytecodeBuilder::StringRef iname = sref(fi->index);
|
||||
int32_t cid = bytecode.addConstantString(iname);
|
||||
if (cid < 0)
|
||||
|
@ -460,20 +461,17 @@ struct Compiler
|
|||
|
||||
bool shared = false;
|
||||
|
||||
if (FFlag::LuauPreloadClosures)
|
||||
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
|
||||
// objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it
|
||||
// is used)
|
||||
if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed)
|
||||
{
|
||||
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
|
||||
// objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it
|
||||
// is used)
|
||||
if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed)
|
||||
{
|
||||
int32_t cid = bytecode.addConstantClosure(f->id);
|
||||
int32_t cid = bytecode.addConstantClosure(f->id);
|
||||
|
||||
if (cid >= 0 && cid < 32768)
|
||||
{
|
||||
bytecode.emitAD(LOP_DUPCLOSURE, target, cid);
|
||||
shared = true;
|
||||
}
|
||||
if (cid >= 0 && cid < 32768)
|
||||
{
|
||||
bytecode.emitAD(LOP_DUPCLOSURE, target, cid);
|
||||
shared = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1313,6 +1311,8 @@ struct Compiler
|
|||
RegScope rs(this);
|
||||
uint8_t reg = compileExprAuto(expr->expr, rs);
|
||||
|
||||
setDebugLine(expr->indexLocation);
|
||||
|
||||
BytecodeBuilder::StringRef iname = sref(expr->index);
|
||||
int32_t cid = bytecode.addConstantString(iname);
|
||||
if (cid < 0)
|
||||
|
@ -2710,6 +2710,12 @@ struct Compiler
|
|||
bytecode.setDebugLine(node->location.begin.line + 1);
|
||||
}
|
||||
|
||||
void setDebugLine(const Location& location)
|
||||
{
|
||||
if (options.debugLevel >= 1)
|
||||
bytecode.setDebugLine(location.begin.line + 1);
|
||||
}
|
||||
|
||||
void setDebugLineEnd(AstNode* node)
|
||||
{
|
||||
if (options.debugLevel >= 1)
|
||||
|
@ -3650,7 +3656,7 @@ struct Compiler
|
|||
{
|
||||
if (options.vectorLib)
|
||||
{
|
||||
if (builtin.object == options.vectorLib && builtin.method == options.vectorCtor)
|
||||
if (builtin.isMethod(options.vectorLib, options.vectorCtor))
|
||||
return LBF_VECTOR;
|
||||
}
|
||||
else
|
||||
|
|
12
Makefile
12
Makefile
|
@ -27,7 +27,7 @@ TESTS_SOURCES=$(wildcard tests/*.cpp)
|
|||
TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o)
|
||||
TESTS_TARGET=$(BUILD)/luau-tests
|
||||
|
||||
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Profiler.cpp CLI/Repl.cpp
|
||||
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp
|
||||
REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o)
|
||||
REPL_CLI_TARGET=$(BUILD)/luau
|
||||
|
||||
|
@ -35,7 +35,7 @@ ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Analyze.cpp
|
|||
ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
|
||||
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze
|
||||
|
||||
FUZZ_SOURCES=$(wildcard fuzz/*.cpp)
|
||||
FUZZ_SOURCES=$(wildcard fuzz/*.cpp) fuzz/luau.pb.cpp
|
||||
FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o)
|
||||
|
||||
TESTS_ARGS=
|
||||
|
@ -128,10 +128,10 @@ luau-size: luau
|
|||
|
||||
# executable target aliases
|
||||
luau: $(REPL_CLI_TARGET)
|
||||
cp $^ $@
|
||||
ln -fs $^ $@
|
||||
|
||||
luau-analyze: $(ANALYZE_CLI_TARGET)
|
||||
cp $^ $@
|
||||
ln -fs $^ $@
|
||||
|
||||
# executable targets
|
||||
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET)
|
||||
|
@ -167,8 +167,8 @@ fuzz/luau.pb.cpp: fuzz/luau.proto build/libprotobuf-mutator
|
|||
cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=.
|
||||
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
|
||||
|
||||
$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator
|
||||
$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator
|
||||
$(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp
|
||||
$(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp
|
||||
|
||||
build/libprotobuf-mutator:
|
||||
git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator
|
||||
|
|
|
@ -54,6 +54,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/include/Luau/Scope.h
|
||||
Analysis/include/Luau/Substitution.h
|
||||
Analysis/include/Luau/Symbol.h
|
||||
Analysis/include/Luau/ToDot.h
|
||||
Analysis/include/Luau/TopoSortStatements.h
|
||||
Analysis/include/Luau/ToString.h
|
||||
Analysis/include/Luau/Transpiler.h
|
||||
|
@ -86,6 +87,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/src/Scope.cpp
|
||||
Analysis/src/Substitution.cpp
|
||||
Analysis/src/Symbol.cpp
|
||||
Analysis/src/ToDot.cpp
|
||||
Analysis/src/TopoSortStatements.cpp
|
||||
Analysis/src/ToString.cpp
|
||||
Analysis/src/Transpiler.cpp
|
||||
|
@ -118,6 +120,7 @@ target_sources(Luau.VM PRIVATE
|
|||
VM/src/ldo.cpp
|
||||
VM/src/lfunc.cpp
|
||||
VM/src/lgc.cpp
|
||||
VM/src/lgcdebug.cpp
|
||||
VM/src/linit.cpp
|
||||
VM/src/lmathlib.cpp
|
||||
VM/src/lmem.cpp
|
||||
|
@ -130,6 +133,7 @@ target_sources(Luau.VM PRIVATE
|
|||
VM/src/ltable.cpp
|
||||
VM/src/ltablib.cpp
|
||||
VM/src/ltm.cpp
|
||||
VM/src/ludata.cpp
|
||||
VM/src/lutf8lib.cpp
|
||||
VM/src/lvmexecute.cpp
|
||||
VM/src/lvmload.cpp
|
||||
|
@ -149,12 +153,15 @@ target_sources(Luau.VM PRIVATE
|
|||
VM/src/lstring.h
|
||||
VM/src/ltable.h
|
||||
VM/src/ltm.h
|
||||
VM/src/ludata.h
|
||||
VM/src/lvm.h
|
||||
)
|
||||
|
||||
if(TARGET Luau.Repl.CLI)
|
||||
# Luau.Repl.CLI Sources
|
||||
target_sources(Luau.Repl.CLI PRIVATE
|
||||
CLI/Coverage.h
|
||||
CLI/Coverage.cpp
|
||||
CLI/FileUtils.h
|
||||
CLI/FileUtils.cpp
|
||||
CLI/Profiler.h
|
||||
|
@ -194,6 +201,7 @@ if(TARGET Luau.UnitTest)
|
|||
tests/RequireTracer.test.cpp
|
||||
tests/StringUtils.test.cpp
|
||||
tests/Symbol.test.cpp
|
||||
tests/ToDot.test.cpp
|
||||
tests/TopoSort.test.cpp
|
||||
tests/ToString.test.cpp
|
||||
tests/Transpiler.test.cpp
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#define LUA_ENVIRONINDEX (-10001)
|
||||
#define LUA_GLOBALSINDEX (-10002)
|
||||
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i))
|
||||
#define lua_ispseudo(i) ((i) <= LUA_REGISTRYINDEX)
|
||||
|
||||
/* thread status; 0 is OK */
|
||||
enum lua_Status
|
||||
|
@ -108,6 +109,7 @@ LUA_API int lua_isthreadreset(lua_State* L);
|
|||
/*
|
||||
** basic stack manipulation
|
||||
*/
|
||||
LUA_API int lua_absindex(lua_State* L, int idx);
|
||||
LUA_API int lua_gettop(lua_State* L);
|
||||
LUA_API void lua_settop(lua_State* L, int idx);
|
||||
LUA_API void lua_pushvalue(lua_State* L, int idx);
|
||||
|
@ -187,7 +189,7 @@ LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);
|
|||
LUA_API int lua_getreadonly(lua_State* L, int idx);
|
||||
LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled);
|
||||
|
||||
LUA_API void* lua_newuserdata(lua_State* L, size_t sz, int tag);
|
||||
LUA_API void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag);
|
||||
LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
|
||||
LUA_API int lua_getmetatable(lua_State* L, int objindex);
|
||||
LUA_API void lua_getfenv(lua_State* L, int idx);
|
||||
|
@ -231,6 +233,7 @@ enum lua_GCOp
|
|||
LUA_GCRESTART,
|
||||
LUA_GCCOLLECT,
|
||||
LUA_GCCOUNT,
|
||||
LUA_GCCOUNTB,
|
||||
LUA_GCISRUNNING,
|
||||
|
||||
// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
|
||||
|
@ -285,6 +288,7 @@ LUA_API void lua_unref(lua_State* L, int ref);
|
|||
#define lua_pop(L, n) lua_settop(L, -(n)-1)
|
||||
|
||||
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||
#define lua_newuserdata(L, s) lua_newuserdatatagged(L, s, 0)
|
||||
|
||||
#define lua_strlen(L, i) lua_objlen(L, (i))
|
||||
|
||||
|
@ -293,6 +297,7 @@ LUA_API void lua_unref(lua_State* L, int ref);
|
|||
#define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
|
||||
#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL)
|
||||
#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN)
|
||||
#define lua_isvector(L, n) (lua_type(L, (n)) == LUA_TVECTOR)
|
||||
#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD)
|
||||
#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE)
|
||||
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL)
|
||||
|
@ -329,6 +334,10 @@ LUA_API const char* lua_setupvalue(lua_State* L, int funcindex, int n);
|
|||
LUA_API void lua_singlestep(lua_State* L, int enabled);
|
||||
LUA_API void lua_breakpoint(lua_State* L, int funcindex, int line, int enabled);
|
||||
|
||||
typedef void (*lua_Coverage)(void* context, const char* function, int linedefined, int depth, const int* hits, size_t size);
|
||||
|
||||
LUA_API void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback);
|
||||
|
||||
/* Warning: this function is not thread-safe since it stores the result in a shared global array! Only use for debugging. */
|
||||
LUA_API const char* lua_debugtrace(lua_State* L);
|
||||
|
||||
|
|
|
@ -34,7 +34,10 @@
|
|||
#endif
|
||||
|
||||
/* Can be used to reconfigure visibility/exports for public APIs */
|
||||
#ifndef LUA_API
|
||||
#define LUA_API extern
|
||||
#endif
|
||||
|
||||
#define LUALIB_API LUA_API
|
||||
|
||||
/* Can be used to reconfigure visibility for internal APIs */
|
||||
|
@ -47,10 +50,14 @@
|
|||
#endif
|
||||
|
||||
/* Can be used to reconfigure internal error handling to use longjmp instead of C++ EH */
|
||||
#ifndef LUA_USE_LONGJMP
|
||||
#define LUA_USE_LONGJMP 0
|
||||
#endif
|
||||
|
||||
/* LUA_IDSIZE gives the maximum size for the description of the source */
|
||||
#ifndef LUA_IDSIZE
|
||||
#define LUA_IDSIZE 256
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ LUAI_GCGOAL defines the desired top heap size in relation to the live heap
|
||||
|
@ -59,7 +66,9 @@
|
|||
** mean larger GC pauses which mean slower collection.) You can also change
|
||||
** this value dynamically.
|
||||
*/
|
||||
#ifndef LUAI_GCGOAL
|
||||
#define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ LUAI_GCSTEPMUL / LUAI_GCSTEPSIZE define the default speed of garbage collection
|
||||
|
@ -69,38 +78,63 @@
|
|||
** CHANGE it if you want to change the granularity of the garbage
|
||||
** collection.
|
||||
*/
|
||||
#ifndef LUAI_GCSTEPMUL
|
||||
#define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */
|
||||
#endif
|
||||
|
||||
#ifndef LUAI_GCSTEPSIZE
|
||||
#define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */
|
||||
#endif
|
||||
|
||||
/* LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function */
|
||||
#ifndef LUA_MINSTACK
|
||||
#define LUA_MINSTACK 20
|
||||
#endif
|
||||
|
||||
/* LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use */
|
||||
#ifndef LUAI_MAXCSTACK
|
||||
#define LUAI_MAXCSTACK 8000
|
||||
#endif
|
||||
|
||||
/* LUAI_MAXCALLS limits the number of nested calls */
|
||||
#ifndef LUAI_MAXCALLS
|
||||
#define LUAI_MAXCALLS 20000
|
||||
#endif
|
||||
|
||||
/* LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size */
|
||||
#ifndef LUAI_MAXCCALLS
|
||||
#define LUAI_MAXCCALLS 200
|
||||
#endif
|
||||
|
||||
/* buffer size used for on-stack string operations; this limit depends on native stack size */
|
||||
#ifndef LUA_BUFFERSIZE
|
||||
#define LUA_BUFFERSIZE 512
|
||||
#endif
|
||||
|
||||
/* number of valid Lua userdata tags */
|
||||
#ifndef LUA_UTAG_LIMIT
|
||||
#define LUA_UTAG_LIMIT 128
|
||||
#endif
|
||||
|
||||
/* upper bound for number of size classes used by page allocator */
|
||||
#ifndef LUA_SIZECLASSES
|
||||
#define LUA_SIZECLASSES 32
|
||||
#endif
|
||||
|
||||
/* available number of separate memory categories */
|
||||
#ifndef LUA_MEMORY_CATEGORIES
|
||||
#define LUA_MEMORY_CATEGORIES 256
|
||||
#endif
|
||||
|
||||
/* minimum size for the string table (must be power of 2) */
|
||||
#ifndef LUA_MINSTRTABSIZE
|
||||
#define LUA_MINSTRTABSIZE 32
|
||||
#endif
|
||||
|
||||
/* maximum number of captures supported by pattern matching */
|
||||
#ifndef LUA_MAXCAPTURES
|
||||
#define LUA_MAXCAPTURES 32
|
||||
#endif
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def);
|
|||
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg);
|
||||
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def);
|
||||
|
||||
LUALIB_API const float* luaL_checkvector(lua_State* L, int narg);
|
||||
LUALIB_API const float* luaL_optvector(lua_State* L, int narg, const float* def);
|
||||
|
||||
LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg);
|
||||
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t);
|
||||
LUALIB_API void luaL_checkany(lua_State* L, int narg);
|
||||
|
|
216
VM/src/lapi.cpp
216
VM/src/lapi.cpp
|
@ -8,11 +8,14 @@
|
|||
#include "lfunc.h"
|
||||
#include "lgc.h"
|
||||
#include "ldo.h"
|
||||
#include "ludata.h"
|
||||
#include "lvm.h"
|
||||
#include "lnumutils.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"
|
||||
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
|
||||
"$URL: www.lua.org $\n";
|
||||
|
@ -41,36 +44,30 @@ static Table* getcurrenv(lua_State* L)
|
|||
}
|
||||
}
|
||||
|
||||
static LUAU_NOINLINE TValue* index2adrslow(lua_State* L, int idx)
|
||||
static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx)
|
||||
{
|
||||
api_check(L, idx <= 0);
|
||||
if (idx > LUA_REGISTRYINDEX)
|
||||
api_check(L, lua_ispseudo(idx));
|
||||
switch (idx)
|
||||
{ /* pseudo-indices */
|
||||
case LUA_REGISTRYINDEX:
|
||||
return registry(L);
|
||||
case LUA_ENVIRONINDEX:
|
||||
{
|
||||
api_check(L, idx != 0 && -idx <= L->top - L->base);
|
||||
return L->top + idx;
|
||||
sethvalue(L, &L->env, getcurrenv(L));
|
||||
return &L->env;
|
||||
}
|
||||
case LUA_GLOBALSINDEX:
|
||||
return gt(L);
|
||||
default:
|
||||
{
|
||||
Closure* func = curr_func(L);
|
||||
idx = LUA_GLOBALSINDEX - idx;
|
||||
return (idx <= func->nupvalues) ? &func->c.upvals[idx - 1] : cast_to(TValue*, luaO_nilobject);
|
||||
}
|
||||
}
|
||||
else
|
||||
switch (idx)
|
||||
{ /* pseudo-indices */
|
||||
case LUA_REGISTRYINDEX:
|
||||
return registry(L);
|
||||
case LUA_ENVIRONINDEX:
|
||||
{
|
||||
sethvalue(L, &L->env, getcurrenv(L));
|
||||
return &L->env;
|
||||
}
|
||||
case LUA_GLOBALSINDEX:
|
||||
return gt(L);
|
||||
default:
|
||||
{
|
||||
Closure* func = curr_func(L);
|
||||
idx = LUA_GLOBALSINDEX - idx;
|
||||
return (idx <= func->nupvalues) ? &func->c.upvals[idx - 1] : cast_to(TValue*, luaO_nilobject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static LUAU_FORCEINLINE TValue* index2adr(lua_State* L, int idx)
|
||||
static LUAU_FORCEINLINE TValue* index2addr(lua_State* L, int idx)
|
||||
{
|
||||
if (idx > 0)
|
||||
{
|
||||
|
@ -81,15 +78,20 @@ static LUAU_FORCEINLINE TValue* index2adr(lua_State* L, int idx)
|
|||
else
|
||||
return o;
|
||||
}
|
||||
else if (idx > LUA_REGISTRYINDEX)
|
||||
{
|
||||
api_check(L, idx != 0 && -idx <= L->top - L->base);
|
||||
return L->top + idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
return index2adrslow(L, idx);
|
||||
return pseudo2addr(L, idx);
|
||||
}
|
||||
}
|
||||
|
||||
const TValue* luaA_toobject(lua_State* L, int idx)
|
||||
{
|
||||
StkId p = index2adr(L, idx);
|
||||
StkId p = index2addr(L, idx);
|
||||
return (p == luaO_nilobject) ? NULL : p;
|
||||
}
|
||||
|
||||
|
@ -143,7 +145,7 @@ void lua_xpush(lua_State* from, lua_State* to, int idx)
|
|||
{
|
||||
api_check(from, from->global == to->global);
|
||||
luaC_checkthreadsleep(to);
|
||||
setobj2s(to, to->top, index2adr(from, idx));
|
||||
setobj2s(to, to->top, index2addr(from, idx));
|
||||
api_incr_top(to);
|
||||
return;
|
||||
}
|
||||
|
@ -170,6 +172,12 @@ lua_State* lua_mainthread(lua_State* L)
|
|||
** basic stack manipulation
|
||||
*/
|
||||
|
||||
int lua_absindex(lua_State* L, int idx)
|
||||
{
|
||||
api_check(L, (idx > 0 && idx <= L->top - L->base) || (idx < 0 && -idx <= L->top - L->base) || lua_ispseudo(idx));
|
||||
return idx > 0 || lua_ispseudo(idx) ? idx : cast_int(L->top - L->base) + idx + 1;
|
||||
}
|
||||
|
||||
int lua_gettop(lua_State* L)
|
||||
{
|
||||
return cast_int(L->top - L->base);
|
||||
|
@ -194,7 +202,7 @@ void lua_settop(lua_State* L, int idx)
|
|||
|
||||
void lua_remove(lua_State* L, int idx)
|
||||
{
|
||||
StkId p = index2adr(L, idx);
|
||||
StkId p = index2addr(L, idx);
|
||||
api_checkvalidindex(L, p);
|
||||
while (++p < L->top)
|
||||
setobjs2s(L, p - 1, p);
|
||||
|
@ -205,7 +213,7 @@ void lua_remove(lua_State* L, int idx)
|
|||
void lua_insert(lua_State* L, int idx)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId p = index2adr(L, idx);
|
||||
StkId p = index2addr(L, idx);
|
||||
api_checkvalidindex(L, p);
|
||||
for (StkId q = L->top; q > p; q--)
|
||||
setobjs2s(L, q, q - 1);
|
||||
|
@ -220,7 +228,7 @@ void lua_replace(lua_State* L, int idx)
|
|||
luaG_runerror(L, "no calling environment");
|
||||
api_checknelems(L, 1);
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
api_checkvalidindex(L, o);
|
||||
if (idx == LUA_ENVIRONINDEX)
|
||||
{
|
||||
|
@ -242,7 +250,7 @@ void lua_replace(lua_State* L, int idx)
|
|||
void lua_pushvalue(lua_State* L, int idx)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
setobj2s(L, L->top, o);
|
||||
api_incr_top(L);
|
||||
return;
|
||||
|
@ -254,7 +262,7 @@ void lua_pushvalue(lua_State* L, int idx)
|
|||
|
||||
int lua_type(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
return (o == luaO_nilobject) ? LUA_TNONE : ttype(o);
|
||||
}
|
||||
|
||||
|
@ -265,20 +273,20 @@ const char* lua_typename(lua_State* L, int t)
|
|||
|
||||
int lua_iscfunction(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
return iscfunction(o);
|
||||
}
|
||||
|
||||
int lua_isLfunction(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
return isLfunction(o);
|
||||
}
|
||||
|
||||
int lua_isnumber(lua_State* L, int idx)
|
||||
{
|
||||
TValue n;
|
||||
const TValue* o = index2adr(L, idx);
|
||||
const TValue* o = index2addr(L, idx);
|
||||
return tonumber(o, &n);
|
||||
}
|
||||
|
||||
|
@ -290,14 +298,14 @@ int lua_isstring(lua_State* L, int idx)
|
|||
|
||||
int lua_isuserdata(lua_State* L, int idx)
|
||||
{
|
||||
const TValue* o = index2adr(L, idx);
|
||||
const TValue* o = index2addr(L, idx);
|
||||
return (ttisuserdata(o) || ttislightuserdata(o));
|
||||
}
|
||||
|
||||
int lua_rawequal(lua_State* L, int index1, int index2)
|
||||
{
|
||||
StkId o1 = index2adr(L, index1);
|
||||
StkId o2 = index2adr(L, index2);
|
||||
StkId o1 = index2addr(L, index1);
|
||||
StkId o2 = index2addr(L, index2);
|
||||
return (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaO_rawequalObj(o1, o2);
|
||||
}
|
||||
|
||||
|
@ -305,8 +313,8 @@ int lua_equal(lua_State* L, int index1, int index2)
|
|||
{
|
||||
StkId o1, o2;
|
||||
int i;
|
||||
o1 = index2adr(L, index1);
|
||||
o2 = index2adr(L, index2);
|
||||
o1 = index2addr(L, index1);
|
||||
o2 = index2addr(L, index2);
|
||||
i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : equalobj(L, o1, o2);
|
||||
return i;
|
||||
}
|
||||
|
@ -315,8 +323,8 @@ int lua_lessthan(lua_State* L, int index1, int index2)
|
|||
{
|
||||
StkId o1, o2;
|
||||
int i;
|
||||
o1 = index2adr(L, index1);
|
||||
o2 = index2adr(L, index2);
|
||||
o1 = index2addr(L, index1);
|
||||
o2 = index2addr(L, index2);
|
||||
i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaV_lessthan(L, o1, o2);
|
||||
return i;
|
||||
}
|
||||
|
@ -324,7 +332,7 @@ int lua_lessthan(lua_State* L, int index1, int index2)
|
|||
double lua_tonumberx(lua_State* L, int idx, int* isnum)
|
||||
{
|
||||
TValue n;
|
||||
const TValue* o = index2adr(L, idx);
|
||||
const TValue* o = index2addr(L, idx);
|
||||
if (tonumber(o, &n))
|
||||
{
|
||||
if (isnum)
|
||||
|
@ -342,7 +350,7 @@ double lua_tonumberx(lua_State* L, int idx, int* isnum)
|
|||
int lua_tointegerx(lua_State* L, int idx, int* isnum)
|
||||
{
|
||||
TValue n;
|
||||
const TValue* o = index2adr(L, idx);
|
||||
const TValue* o = index2addr(L, idx);
|
||||
if (tonumber(o, &n))
|
||||
{
|
||||
int res;
|
||||
|
@ -363,7 +371,7 @@ int lua_tointegerx(lua_State* L, int idx, int* isnum)
|
|||
unsigned lua_tounsignedx(lua_State* L, int idx, int* isnum)
|
||||
{
|
||||
TValue n;
|
||||
const TValue* o = index2adr(L, idx);
|
||||
const TValue* o = index2addr(L, idx);
|
||||
if (tonumber(o, &n))
|
||||
{
|
||||
unsigned res;
|
||||
|
@ -383,13 +391,13 @@ unsigned lua_tounsignedx(lua_State* L, int idx, int* isnum)
|
|||
|
||||
int lua_toboolean(lua_State* L, int idx)
|
||||
{
|
||||
const TValue* o = index2adr(L, idx);
|
||||
const TValue* o = index2addr(L, idx);
|
||||
return !l_isfalse(o);
|
||||
}
|
||||
|
||||
const char* lua_tolstring(lua_State* L, int idx, size_t* len)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
if (!ttisstring(o))
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
|
@ -400,7 +408,7 @@ const char* lua_tolstring(lua_State* L, int idx, size_t* len)
|
|||
return NULL;
|
||||
}
|
||||
luaC_checkGC(L);
|
||||
o = index2adr(L, idx); /* previous call may reallocate the stack */
|
||||
o = index2addr(L, idx); /* previous call may reallocate the stack */
|
||||
}
|
||||
if (len != NULL)
|
||||
*len = tsvalue(o)->len;
|
||||
|
@ -409,7 +417,7 @@ const char* lua_tolstring(lua_State* L, int idx, size_t* len)
|
|||
|
||||
const char* lua_tostringatom(lua_State* L, int idx, int* atom)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
if (!ttisstring(o))
|
||||
return NULL;
|
||||
const TString* s = tsvalue(o);
|
||||
|
@ -430,7 +438,7 @@ const char* lua_namecallatom(lua_State* L, int* atom)
|
|||
|
||||
const float* lua_tovector(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
if (!ttisvector(o))
|
||||
{
|
||||
return NULL;
|
||||
|
@ -440,7 +448,7 @@ const float* lua_tovector(lua_State* L, int idx)
|
|||
|
||||
int lua_objlen(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
switch (ttype(o))
|
||||
{
|
||||
case LUA_TSTRING:
|
||||
|
@ -461,13 +469,13 @@ int lua_objlen(lua_State* L, int idx)
|
|||
|
||||
lua_CFunction lua_tocfunction(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
return (!iscfunction(o)) ? NULL : cast_to(lua_CFunction, clvalue(o)->c.f);
|
||||
}
|
||||
|
||||
void* lua_touserdata(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
switch (ttype(o))
|
||||
{
|
||||
case LUA_TUSERDATA:
|
||||
|
@ -481,13 +489,13 @@ void* lua_touserdata(lua_State* L, int idx)
|
|||
|
||||
void* lua_touserdatatagged(lua_State* L, int idx, int tag)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
return (ttisuserdata(o) && uvalue(o)->tag == tag) ? uvalue(o)->data : NULL;
|
||||
}
|
||||
|
||||
int lua_userdatatag(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
if (ttisuserdata(o))
|
||||
return uvalue(o)->tag;
|
||||
return -1;
|
||||
|
@ -495,13 +503,13 @@ int lua_userdatatag(lua_State* L, int idx)
|
|||
|
||||
lua_State* lua_tothread(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
return (!ttisthread(o)) ? NULL : thvalue(o);
|
||||
}
|
||||
|
||||
const void* lua_topointer(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2adr(L, idx);
|
||||
StkId o = index2addr(L, idx);
|
||||
switch (ttype(o))
|
||||
{
|
||||
case LUA_TTABLE:
|
||||
|
@ -649,7 +657,7 @@ int lua_pushthread(lua_State* L)
|
|||
void lua_gettable(lua_State* L, int idx)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId t = index2adr(L, idx);
|
||||
StkId t = index2addr(L, idx);
|
||||
api_checkvalidindex(L, t);
|
||||
luaV_gettable(L, t, L->top - 1, L->top - 1);
|
||||
return;
|
||||
|
@ -658,7 +666,7 @@ void lua_gettable(lua_State* L, int idx)
|
|||
void lua_getfield(lua_State* L, int idx, const char* k)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId t = index2adr(L, idx);
|
||||
StkId t = index2addr(L, idx);
|
||||
api_checkvalidindex(L, t);
|
||||
TValue key;
|
||||
setsvalue(L, &key, luaS_new(L, k));
|
||||
|
@ -670,7 +678,7 @@ void lua_getfield(lua_State* L, int idx, const char* k)
|
|||
void lua_rawgetfield(lua_State* L, int idx, const char* k)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId t = index2adr(L, idx);
|
||||
StkId t = index2addr(L, idx);
|
||||
api_check(L, ttistable(t));
|
||||
TValue key;
|
||||
setsvalue(L, &key, luaS_new(L, k));
|
||||
|
@ -682,7 +690,7 @@ void lua_rawgetfield(lua_State* L, int idx, const char* k)
|
|||
void lua_rawget(lua_State* L, int idx)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId t = index2adr(L, idx);
|
||||
StkId t = index2addr(L, idx);
|
||||
api_check(L, ttistable(t));
|
||||
setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
|
||||
return;
|
||||
|
@ -691,7 +699,7 @@ void lua_rawget(lua_State* L, int idx)
|
|||
void lua_rawgeti(lua_State* L, int idx, int n)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId t = index2adr(L, idx);
|
||||
StkId t = index2addr(L, idx);
|
||||
api_check(L, ttistable(t));
|
||||
setobj2s(L, L->top, luaH_getnum(hvalue(t), n));
|
||||
api_incr_top(L);
|
||||
|
@ -709,7 +717,7 @@ void lua_createtable(lua_State* L, int narray, int nrec)
|
|||
|
||||
void lua_setreadonly(lua_State* L, int objindex, int enabled)
|
||||
{
|
||||
const TValue* o = index2adr(L, objindex);
|
||||
const TValue* o = index2addr(L, objindex);
|
||||
api_check(L, ttistable(o));
|
||||
Table* t = hvalue(o);
|
||||
api_check(L, t != hvalue(registry(L)));
|
||||
|
@ -719,7 +727,7 @@ void lua_setreadonly(lua_State* L, int objindex, int enabled)
|
|||
|
||||
int lua_getreadonly(lua_State* L, int objindex)
|
||||
{
|
||||
const TValue* o = index2adr(L, objindex);
|
||||
const TValue* o = index2addr(L, objindex);
|
||||
api_check(L, ttistable(o));
|
||||
Table* t = hvalue(o);
|
||||
int res = t->readonly;
|
||||
|
@ -728,7 +736,7 @@ int lua_getreadonly(lua_State* L, int objindex)
|
|||
|
||||
void lua_setsafeenv(lua_State* L, int objindex, int enabled)
|
||||
{
|
||||
const TValue* o = index2adr(L, objindex);
|
||||
const TValue* o = index2addr(L, objindex);
|
||||
api_check(L, ttistable(o));
|
||||
Table* t = hvalue(o);
|
||||
t->safeenv = bool(enabled);
|
||||
|
@ -740,7 +748,7 @@ int lua_getmetatable(lua_State* L, int objindex)
|
|||
const TValue* obj;
|
||||
Table* mt = NULL;
|
||||
int res;
|
||||
obj = index2adr(L, objindex);
|
||||
obj = index2addr(L, objindex);
|
||||
switch (ttype(obj))
|
||||
{
|
||||
case LUA_TTABLE:
|
||||
|
@ -767,7 +775,7 @@ int lua_getmetatable(lua_State* L, int objindex)
|
|||
void lua_getfenv(lua_State* L, int idx)
|
||||
{
|
||||
StkId o;
|
||||
o = index2adr(L, idx);
|
||||
o = index2addr(L, idx);
|
||||
api_checkvalidindex(L, o);
|
||||
switch (ttype(o))
|
||||
{
|
||||
|
@ -793,7 +801,7 @@ void lua_settable(lua_State* L, int idx)
|
|||
{
|
||||
StkId t;
|
||||
api_checknelems(L, 2);
|
||||
t = index2adr(L, idx);
|
||||
t = index2addr(L, idx);
|
||||
api_checkvalidindex(L, t);
|
||||
luaV_settable(L, t, L->top - 2, L->top - 1);
|
||||
L->top -= 2; /* pop index and value */
|
||||
|
@ -805,7 +813,7 @@ void lua_setfield(lua_State* L, int idx, const char* k)
|
|||
StkId t;
|
||||
TValue key;
|
||||
api_checknelems(L, 1);
|
||||
t = index2adr(L, idx);
|
||||
t = index2addr(L, idx);
|
||||
api_checkvalidindex(L, t);
|
||||
setsvalue(L, &key, luaS_new(L, k));
|
||||
luaV_settable(L, t, &key, L->top - 1);
|
||||
|
@ -817,7 +825,7 @@ void lua_rawset(lua_State* L, int idx)
|
|||
{
|
||||
StkId t;
|
||||
api_checknelems(L, 2);
|
||||
t = index2adr(L, idx);
|
||||
t = index2addr(L, idx);
|
||||
api_check(L, ttistable(t));
|
||||
if (hvalue(t)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
|
@ -831,7 +839,7 @@ void lua_rawseti(lua_State* L, int idx, int n)
|
|||
{
|
||||
StkId o;
|
||||
api_checknelems(L, 1);
|
||||
o = index2adr(L, idx);
|
||||
o = index2addr(L, idx);
|
||||
api_check(L, ttistable(o));
|
||||
if (hvalue(o)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
|
@ -846,7 +854,7 @@ int lua_setmetatable(lua_State* L, int objindex)
|
|||
TValue* obj;
|
||||
Table* mt;
|
||||
api_checknelems(L, 1);
|
||||
obj = index2adr(L, objindex);
|
||||
obj = index2addr(L, objindex);
|
||||
api_checkvalidindex(L, obj);
|
||||
if (ttisnil(L->top - 1))
|
||||
mt = NULL;
|
||||
|
@ -888,7 +896,7 @@ int lua_setfenv(lua_State* L, int idx)
|
|||
StkId o;
|
||||
int res = 1;
|
||||
api_checknelems(L, 1);
|
||||
o = index2adr(L, idx);
|
||||
o = index2addr(L, idx);
|
||||
api_checkvalidindex(L, o);
|
||||
api_check(L, ttistable(L->top - 1));
|
||||
switch (ttype(o))
|
||||
|
@ -931,14 +939,21 @@ void lua_call(lua_State* L, int nargs, int nresults)
|
|||
checkresults(L, nargs, nresults);
|
||||
func = L->top - (nargs + 1);
|
||||
|
||||
int wasActive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
luaD_call(L, func, nresults);
|
||||
}
|
||||
else
|
||||
{
|
||||
int oldactive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
|
||||
luaD_call(L, func, nresults);
|
||||
luaD_call(L, func, nresults);
|
||||
|
||||
if (!wasActive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
adjustresults(L, nresults);
|
||||
return;
|
||||
|
@ -972,21 +987,28 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc)
|
|||
func = 0;
|
||||
else
|
||||
{
|
||||
StkId o = index2adr(L, errfunc);
|
||||
StkId o = index2addr(L, errfunc);
|
||||
api_checkvalidindex(L, o);
|
||||
func = savestack(L, o);
|
||||
}
|
||||
c.func = L->top - (nargs + 1); /* function to be called */
|
||||
c.nresults = nresults;
|
||||
|
||||
int wasActive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
|
||||
}
|
||||
else
|
||||
{
|
||||
int oldactive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
|
||||
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
|
||||
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
|
||||
|
||||
if (!wasActive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
adjustresults(L, nresults);
|
||||
return status;
|
||||
|
@ -1039,6 +1061,11 @@ int lua_gc(lua_State* L, int what, int data)
|
|||
res = cast_int(g->totalbytes >> 10);
|
||||
break;
|
||||
}
|
||||
case LUA_GCCOUNTB:
|
||||
{
|
||||
res = cast_int(g->totalbytes & 1023);
|
||||
break;
|
||||
}
|
||||
case LUA_GCISRUNNING:
|
||||
{
|
||||
res = (g->GCthreshold != SIZE_MAX);
|
||||
|
@ -1123,7 +1150,7 @@ l_noret lua_error(lua_State* L)
|
|||
int lua_next(lua_State* L, int idx)
|
||||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
StkId t = index2adr(L, idx);
|
||||
StkId t = index2addr(L, idx);
|
||||
api_check(L, ttistable(t));
|
||||
int more = luaH_next(L, hvalue(t), L->top - 1);
|
||||
if (more)
|
||||
|
@ -1155,12 +1182,12 @@ void lua_concat(lua_State* L, int n)
|
|||
return;
|
||||
}
|
||||
|
||||
void* lua_newuserdata(lua_State* L, size_t sz, int tag)
|
||||
void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag)
|
||||
{
|
||||
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
|
||||
luaC_checkGC(L);
|
||||
luaC_checkthreadsleep(L);
|
||||
Udata* u = luaS_newudata(L, sz, tag);
|
||||
Udata* u = luaU_newudata(L, sz, tag);
|
||||
setuvalue(L, L->top, u);
|
||||
api_incr_top(L);
|
||||
return u->data;
|
||||
|
@ -1170,7 +1197,7 @@ void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*))
|
|||
{
|
||||
luaC_checkGC(L);
|
||||
luaC_checkthreadsleep(L);
|
||||
Udata* u = luaS_newudata(L, sz + sizeof(dtor), UTAG_IDTOR);
|
||||
Udata* u = luaU_newudata(L, sz + sizeof(dtor), UTAG_IDTOR);
|
||||
memcpy(&u->data + sz, &dtor, sizeof(dtor));
|
||||
setuvalue(L, L->top, u);
|
||||
api_incr_top(L);
|
||||
|
@ -1205,7 +1232,7 @@ const char* lua_getupvalue(lua_State* L, int funcindex, int n)
|
|||
{
|
||||
luaC_checkthreadsleep(L);
|
||||
TValue* val;
|
||||
const char* name = aux_upvalue(index2adr(L, funcindex), n, &val);
|
||||
const char* name = aux_upvalue(index2addr(L, funcindex), n, &val);
|
||||
if (name)
|
||||
{
|
||||
setobj2s(L, L->top, val);
|
||||
|
@ -1219,7 +1246,7 @@ const char* lua_setupvalue(lua_State* L, int funcindex, int n)
|
|||
const char* name;
|
||||
TValue* val;
|
||||
StkId fi;
|
||||
fi = index2adr(L, funcindex);
|
||||
fi = index2addr(L, funcindex);
|
||||
api_checknelems(L, 1);
|
||||
name = aux_upvalue(fi, n, &val);
|
||||
if (name)
|
||||
|
@ -1240,9 +1267,10 @@ uintptr_t lua_encodepointer(lua_State* L, uintptr_t p)
|
|||
|
||||
int lua_ref(lua_State* L, int idx)
|
||||
{
|
||||
api_check(L, idx != LUA_REGISTRYINDEX); /* idx is a stack index for value */
|
||||
int ref = LUA_REFNIL;
|
||||
global_State* g = L->global;
|
||||
StkId p = index2adr(L, idx);
|
||||
StkId p = index2addr(L, idx);
|
||||
if (!ttisnil(p))
|
||||
{
|
||||
Table* reg = hvalue(registry(L));
|
||||
|
|
|
@ -30,7 +30,7 @@ static const char* currfuncname(lua_State* L)
|
|||
return debugname;
|
||||
}
|
||||
|
||||
LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
|
||||
l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
|
||||
{
|
||||
const char* fname = currfuncname(L);
|
||||
|
||||
|
@ -40,7 +40,7 @@ LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
|
|||
luaL_error(L, "invalid argument #%d (%s)", narg, extramsg);
|
||||
}
|
||||
|
||||
LUALIB_API l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname)
|
||||
l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname)
|
||||
{
|
||||
const char* fname = currfuncname(L);
|
||||
const TValue* obj = luaA_toobject(L, narg);
|
||||
|
@ -66,7 +66,7 @@ static l_noret tag_error(lua_State* L, int narg, int tag)
|
|||
luaL_typeerrorL(L, narg, lua_typename(L, tag));
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_where(lua_State* L, int level)
|
||||
void luaL_where(lua_State* L, int level)
|
||||
{
|
||||
lua_Debug ar;
|
||||
if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0)
|
||||
|
@ -77,7 +77,7 @@ LUALIB_API void luaL_where(lua_State* L, int level)
|
|||
lua_pushliteral(L, ""); /* else, no information available... */
|
||||
}
|
||||
|
||||
LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
|
||||
l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
|
||||
{
|
||||
va_list argp;
|
||||
va_start(argp, fmt);
|
||||
|
@ -90,7 +90,7 @@ LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
|
|||
|
||||
/* }====================================================== */
|
||||
|
||||
LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[])
|
||||
int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[])
|
||||
{
|
||||
const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
|
||||
int i;
|
||||
|
@ -101,7 +101,7 @@ LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const c
|
|||
luaL_argerrorL(L, narg, msg);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname)
|
||||
int luaL_newmetatable(lua_State* L, const char* tname)
|
||||
{
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */
|
||||
if (!lua_isnil(L, -1)) /* name already in use? */
|
||||
|
@ -113,7 +113,7 @@ LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname)
|
|||
return 1;
|
||||
}
|
||||
|
||||
LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname)
|
||||
void* luaL_checkudata(lua_State* L, int ud, const char* tname)
|
||||
{
|
||||
void* p = lua_touserdata(L, ud);
|
||||
if (p != NULL)
|
||||
|
@ -131,25 +131,25 @@ LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname)
|
|||
luaL_typeerrorL(L, ud, tname); /* else error */
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_checkstack(lua_State* L, int space, const char* mes)
|
||||
void luaL_checkstack(lua_State* L, int space, const char* mes)
|
||||
{
|
||||
if (!lua_checkstack(L, space))
|
||||
luaL_error(L, "stack overflow (%s)", mes);
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t)
|
||||
void luaL_checktype(lua_State* L, int narg, int t)
|
||||
{
|
||||
if (lua_type(L, narg) != t)
|
||||
tag_error(L, narg, t);
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_checkany(lua_State* L, int narg)
|
||||
void luaL_checkany(lua_State* L, int narg)
|
||||
{
|
||||
if (lua_type(L, narg) == LUA_TNONE)
|
||||
luaL_error(L, "missing argument #%d", narg);
|
||||
}
|
||||
|
||||
LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
|
||||
const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
|
||||
{
|
||||
const char* s = lua_tolstring(L, narg, len);
|
||||
if (!s)
|
||||
|
@ -157,7 +157,7 @@ LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
|
|||
return s;
|
||||
}
|
||||
|
||||
LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len)
|
||||
const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len)
|
||||
{
|
||||
if (lua_isnoneornil(L, narg))
|
||||
{
|
||||
|
@ -169,7 +169,7 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def,
|
|||
return luaL_checklstring(L, narg, len);
|
||||
}
|
||||
|
||||
LUALIB_API double luaL_checknumber(lua_State* L, int narg)
|
||||
double luaL_checknumber(lua_State* L, int narg)
|
||||
{
|
||||
int isnum;
|
||||
double d = lua_tonumberx(L, narg, &isnum);
|
||||
|
@ -178,12 +178,12 @@ LUALIB_API double luaL_checknumber(lua_State* L, int narg)
|
|||
return d;
|
||||
}
|
||||
|
||||
LUALIB_API double luaL_optnumber(lua_State* L, int narg, double def)
|
||||
double luaL_optnumber(lua_State* L, int narg, double def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checknumber, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_checkboolean(lua_State* L, int narg)
|
||||
int luaL_checkboolean(lua_State* L, int narg)
|
||||
{
|
||||
// This checks specifically for boolean values, ignoring
|
||||
// all other truthy/falsy values. If the desired result
|
||||
|
@ -194,12 +194,12 @@ LUALIB_API int luaL_checkboolean(lua_State* L, int narg)
|
|||
return lua_toboolean(L, narg);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def)
|
||||
int luaL_optboolean(lua_State* L, int narg, int def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checkboolean, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_checkinteger(lua_State* L, int narg)
|
||||
int luaL_checkinteger(lua_State* L, int narg)
|
||||
{
|
||||
int isnum;
|
||||
int d = lua_tointegerx(L, narg, &isnum);
|
||||
|
@ -208,12 +208,12 @@ LUALIB_API int luaL_checkinteger(lua_State* L, int narg)
|
|||
return d;
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_optinteger(lua_State* L, int narg, int def)
|
||||
int luaL_optinteger(lua_State* L, int narg, int def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checkinteger, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg)
|
||||
unsigned luaL_checkunsigned(lua_State* L, int narg)
|
||||
{
|
||||
int isnum;
|
||||
unsigned d = lua_tounsignedx(L, narg, &isnum);
|
||||
|
@ -222,12 +222,25 @@ LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg)
|
|||
return d;
|
||||
}
|
||||
|
||||
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def)
|
||||
unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checkunsigned, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event)
|
||||
const float* luaL_checkvector(lua_State* L, int narg)
|
||||
{
|
||||
const float* v = lua_tovector(L, narg);
|
||||
if (!v)
|
||||
tag_error(L, narg, LUA_TVECTOR);
|
||||
return v;
|
||||
}
|
||||
|
||||
const float* luaL_optvector(lua_State* L, int narg, const float* def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checkvector, narg, def);
|
||||
}
|
||||
|
||||
int luaL_getmetafield(lua_State* L, int obj, const char* event)
|
||||
{
|
||||
if (!lua_getmetatable(L, obj)) /* no metatable? */
|
||||
return 0;
|
||||
|
@ -245,7 +258,7 @@ LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event)
|
|||
}
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_callmeta(lua_State* L, int obj, const char* event)
|
||||
int luaL_callmeta(lua_State* L, int obj, const char* event)
|
||||
{
|
||||
obj = abs_index(L, obj);
|
||||
if (!luaL_getmetafield(L, obj, event)) /* no metafield? */
|
||||
|
@ -263,7 +276,7 @@ static int libsize(const luaL_Reg* l)
|
|||
return size;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l)
|
||||
void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l)
|
||||
{
|
||||
if (libname)
|
||||
{
|
||||
|
@ -289,7 +302,7 @@ LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg*
|
|||
}
|
||||
}
|
||||
|
||||
LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
|
||||
const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
|
||||
{
|
||||
const char* e;
|
||||
lua_pushvalue(L, idx);
|
||||
|
@ -340,7 +353,7 @@ static size_t getnextbuffersize(lua_State* L, size_t currentsize, size_t desired
|
|||
return newsize;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B)
|
||||
void luaL_buffinit(lua_State* L, luaL_Buffer* B)
|
||||
{
|
||||
// start with an internal buffer
|
||||
B->p = B->buffer;
|
||||
|
@ -350,14 +363,14 @@ LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B)
|
|||
B->storage = nullptr;
|
||||
}
|
||||
|
||||
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size)
|
||||
char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size)
|
||||
{
|
||||
luaL_buffinit(L, B);
|
||||
luaL_reservebuffer(B, size, -1);
|
||||
return B->p;
|
||||
}
|
||||
|
||||
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
|
||||
char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
|
||||
|
@ -388,13 +401,13 @@ LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int bo
|
|||
return B->p;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
|
||||
void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
|
||||
{
|
||||
if (size_t(B->end - B->p) < size)
|
||||
luaL_extendbuffer(B, size - (B->end - B->p), boxloc);
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
|
||||
void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
|
||||
{
|
||||
if (size_t(B->end - B->p) < len)
|
||||
luaL_extendbuffer(B, len - (B->end - B->p), -1);
|
||||
|
@ -403,7 +416,7 @@ LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
|
|||
B->p += len;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_addvalue(luaL_Buffer* B)
|
||||
void luaL_addvalue(luaL_Buffer* B)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
|
||||
|
@ -420,7 +433,7 @@ LUALIB_API void luaL_addvalue(luaL_Buffer* B)
|
|||
}
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_pushresult(luaL_Buffer* B)
|
||||
void luaL_pushresult(luaL_Buffer* B)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
|
||||
|
@ -444,7 +457,7 @@ LUALIB_API void luaL_pushresult(luaL_Buffer* B)
|
|||
}
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size)
|
||||
void luaL_pushresultsize(luaL_Buffer* B, size_t size)
|
||||
{
|
||||
B->p += size;
|
||||
luaL_pushresult(B);
|
||||
|
@ -452,7 +465,7 @@ LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size)
|
|||
|
||||
/* }====================================================== */
|
||||
|
||||
LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
||||
const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
||||
{
|
||||
if (luaL_callmeta(L, idx, "__tostring")) /* is there a metafield? */
|
||||
{
|
||||
|
|
|
@ -401,7 +401,7 @@ static int luaB_newproxy(lua_State* L)
|
|||
|
||||
bool needsmt = lua_toboolean(L, 1);
|
||||
|
||||
lua_newuserdata(L, 0, 0);
|
||||
lua_newuserdata(L, 0);
|
||||
|
||||
if (needsmt)
|
||||
{
|
||||
|
@ -441,7 +441,7 @@ static void auxopen(lua_State* L, const char* name, lua_CFunction f, lua_CFuncti
|
|||
lua_setfield(L, -2, name);
|
||||
}
|
||||
|
||||
LUALIB_API int luaopen_base(lua_State* L)
|
||||
int luaopen_base(lua_State* L)
|
||||
{
|
||||
/* set global _G */
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
|
|
|
@ -236,7 +236,7 @@ static const luaL_Reg bitlib[] = {
|
|||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_bit32(lua_State* L)
|
||||
int luaopen_bit32(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_BITLIBNAME, bitlib);
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ static const luaL_Reg co_funcs[] = {
|
|||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_coroutine(lua_State* L)
|
||||
int luaopen_coroutine(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_COLIBNAME, co_funcs);
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ static const luaL_Reg dblib[] = {
|
|||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_debug(lua_State* L)
|
||||
int luaopen_debug(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_DBLIBNAME, dblib);
|
||||
return 1;
|
||||
|
|
|
@ -370,6 +370,69 @@ void lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
|
|||
luaG_breakpoint(L, clvalue(func)->l.p, line, bool(enabled));
|
||||
}
|
||||
|
||||
static int getmaxline(Proto* p)
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
for (int i = 0; i < p->sizecode; ++i)
|
||||
{
|
||||
int line = luaG_getline(p, i);
|
||||
result = result < line ? line : result;
|
||||
}
|
||||
|
||||
for (int i = 0; i < p->sizep; ++i)
|
||||
{
|
||||
int psize = getmaxline(p->p[i]);
|
||||
result = result < psize ? psize : result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void getcoverage(Proto* p, int depth, int* buffer, size_t size, void* context, lua_Coverage callback)
|
||||
{
|
||||
memset(buffer, -1, size * sizeof(int));
|
||||
|
||||
for (int i = 0; i < p->sizecode; ++i)
|
||||
{
|
||||
Instruction insn = p->code[i];
|
||||
if (LUAU_INSN_OP(insn) != LOP_COVERAGE)
|
||||
continue;
|
||||
|
||||
int line = luaG_getline(p, i);
|
||||
int hits = LUAU_INSN_E(insn);
|
||||
|
||||
LUAU_ASSERT(size_t(line) < size);
|
||||
buffer[line] = buffer[line] < hits ? hits : buffer[line];
|
||||
}
|
||||
|
||||
const char* debugname = p->debugname ? getstr(p->debugname) : NULL;
|
||||
int linedefined = luaG_getline(p, 0);
|
||||
|
||||
callback(context, debugname, linedefined, depth, buffer, size);
|
||||
|
||||
for (int i = 0; i < p->sizep; ++i)
|
||||
getcoverage(p->p[i], depth + 1, buffer, size, context, callback);
|
||||
}
|
||||
|
||||
void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback)
|
||||
{
|
||||
const TValue* func = luaA_toobject(L, funcindex);
|
||||
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
|
||||
|
||||
Proto* p = clvalue(func)->l.p;
|
||||
|
||||
size_t size = getmaxline(p) + 1;
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
int* buffer = luaM_newarray(L, size, int, 0);
|
||||
|
||||
getcoverage(p, 0, buffer, size, context, callback);
|
||||
|
||||
luaM_freearray(L, buffer, size, int, 0);
|
||||
}
|
||||
|
||||
static size_t append(char* buf, size_t bufsize, size_t offset, const char* data)
|
||||
{
|
||||
size_t size = strlen(data);
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauExceptionMessageFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false)
|
||||
LUAU_FASTFLAG(LuauCoroutineClose)
|
||||
LUAU_FASTFLAGVARIABLE(LuauActivateBeforeExec, true)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
|
@ -74,35 +74,28 @@ public:
|
|||
|
||||
const char* what() const throw() override
|
||||
{
|
||||
if (FFlag::LuauExceptionMessageFix)
|
||||
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
|
||||
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
|
||||
{
|
||||
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
|
||||
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
|
||||
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
|
||||
if (const char* str = lua_tostring(L, -1))
|
||||
{
|
||||
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
|
||||
if (const char* str = lua_tostring(L, -1))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case LUA_ERRRUN:
|
||||
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
|
||||
case LUA_ERRSYNTAX:
|
||||
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
|
||||
case LUA_ERRMEM:
|
||||
return "lua_exception: " LUA_MEMERRMSG;
|
||||
case LUA_ERRERR:
|
||||
return "lua_exception: " LUA_ERRERRMSG;
|
||||
default:
|
||||
return "lua_exception: unexpected exception status";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
switch (status)
|
||||
{
|
||||
return lua_tostring(L, -1);
|
||||
case LUA_ERRRUN:
|
||||
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
|
||||
case LUA_ERRSYNTAX:
|
||||
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
|
||||
case LUA_ERRMEM:
|
||||
return "lua_exception: " LUA_MEMERRMSG;
|
||||
case LUA_ERRERR:
|
||||
return "lua_exception: " LUA_ERRERRMSG;
|
||||
default:
|
||||
return "lua_exception: unexpected exception status";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,7 +227,22 @@ void luaD_call(lua_State* L, StkId func, int nResults)
|
|||
if (luau_precall(L, func, nResults) == PCRLUA)
|
||||
{ /* is a Lua function? */
|
||||
L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */
|
||||
luau_execute(L); /* call it */
|
||||
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
int oldactive = luaC_threadactive(L);
|
||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
luaC_checkthreadsleep(L);
|
||||
|
||||
luau_execute(L); /* call it */
|
||||
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
luau_execute(L); /* call it */
|
||||
}
|
||||
}
|
||||
L->nCcalls--;
|
||||
luaC_checkGC(L);
|
||||
|
@ -527,10 +535,10 @@ static void restore_stack_limit(lua_State* L)
|
|||
|
||||
int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
|
||||
{
|
||||
int status;
|
||||
unsigned short oldnCcalls = L->nCcalls;
|
||||
ptrdiff_t old_ci = saveci(L, L->ci);
|
||||
status = luaD_rawrunprotected(L, func, u);
|
||||
int oldactive = luaC_threadactive(L);
|
||||
int status = luaD_rawrunprotected(L, func, u);
|
||||
if (status != 0)
|
||||
{
|
||||
// call user-defined error function (used in xpcall)
|
||||
|
@ -541,6 +549,13 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
|
|||
status = LUA_ERRERR;
|
||||
}
|
||||
|
||||
if (FFlag::LuauActivateBeforeExec)
|
||||
{
|
||||
// since the call failed with an error, we might have to reset the 'active' thread state
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
if (FFlag::LuauCcallRestoreFix)
|
||||
{
|
||||
// Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
|
||||
|
|
597
VM/src/lgc.cpp
597
VM/src/lgc.cpp
|
@ -8,12 +8,9 @@
|
|||
#include "lfunc.h"
|
||||
#include "lstring.h"
|
||||
#include "ldo.h"
|
||||
#include "ludata.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauRescanGrayAgainForwardBarrier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false)
|
||||
|
||||
LUAU_FASTFLAG(LuauArrayBoundary)
|
||||
|
||||
|
@ -61,10 +58,6 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
|
|||
case GCSpropagate:
|
||||
case GCSpropagateagain:
|
||||
g->gcstats.currcycle.marktime += seconds;
|
||||
|
||||
// atomic step had to be performed during the switch and it's tracked separately
|
||||
if (!FFlag::LuauSeparateAtomic && g->gcstate == GCSsweepstring)
|
||||
g->gcstats.currcycle.marktime -= g->gcstats.currcycle.atomictime;
|
||||
break;
|
||||
case GCSatomic:
|
||||
g->gcstats.currcycle.atomictime += seconds;
|
||||
|
@ -490,7 +483,7 @@ static void freeobj(lua_State* L, GCObject* o)
|
|||
luaS_free(L, gco2ts(o));
|
||||
break;
|
||||
case LUA_TUSERDATA:
|
||||
luaS_freeudata(L, gco2u(o));
|
||||
luaU_freeudata(L, gco2u(o));
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(0);
|
||||
|
@ -634,17 +627,9 @@ static size_t remarkupvals(global_State* g)
|
|||
static size_t atomic(lua_State* L)
|
||||
{
|
||||
global_State* g = L->global;
|
||||
LUAU_ASSERT(g->gcstate == GCSatomic);
|
||||
|
||||
size_t work = 0;
|
||||
|
||||
if (FFlag::LuauSeparateAtomic)
|
||||
{
|
||||
LUAU_ASSERT(g->gcstate == GCSatomic);
|
||||
}
|
||||
else
|
||||
{
|
||||
g->gcstate = GCSatomic;
|
||||
}
|
||||
|
||||
/* remark occasional upvalues of (maybe) dead threads */
|
||||
work += remarkupvals(g);
|
||||
/* traverse objects caught by write barrier and by 'remarkupvals' */
|
||||
|
@ -668,11 +653,6 @@ static size_t atomic(lua_State* L)
|
|||
g->sweepgc = &g->rootgc;
|
||||
g->gcstate = GCSsweepstring;
|
||||
|
||||
if (!FFlag::LuauSeparateAtomic)
|
||||
{
|
||||
GC_INTERRUPT(GCSatomic);
|
||||
}
|
||||
|
||||
return work;
|
||||
}
|
||||
|
||||
|
@ -718,22 +698,7 @@ static size_t gcstep(lua_State* L, size_t limit)
|
|||
|
||||
if (!g->gray) /* no more `gray' objects */
|
||||
{
|
||||
if (FFlag::LuauSeparateAtomic)
|
||||
{
|
||||
g->gcstate = GCSatomic;
|
||||
}
|
||||
else
|
||||
{
|
||||
double starttimestamp = lua_clock();
|
||||
|
||||
g->gcstats.currcycle.atomicstarttimestamp = starttimestamp;
|
||||
g->gcstats.currcycle.atomicstarttotalsizebytes = g->totalbytes;
|
||||
|
||||
atomic(L); /* finish mark phase */
|
||||
LUAU_ASSERT(g->gcstate == GCSsweepstring);
|
||||
|
||||
g->gcstats.currcycle.atomictime += lua_clock() - starttimestamp;
|
||||
}
|
||||
g->gcstate = GCSatomic;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -855,7 +820,7 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal)
|
|||
void luaC_step(lua_State* L, bool assist)
|
||||
{
|
||||
global_State* g = L->global;
|
||||
ptrdiff_t lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
|
||||
int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
|
||||
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
|
||||
size_t debt = g->totalbytes - g->GCthreshold;
|
||||
|
||||
|
@ -910,7 +875,7 @@ void luaC_fullgc(lua_State* L)
|
|||
if (g->gcstate == GCSpause)
|
||||
startGcCycleStats(g);
|
||||
|
||||
if (g->gcstate <= (FFlag::LuauSeparateAtomic ? GCSatomic : GCSpropagateagain))
|
||||
if (g->gcstate <= GCSatomic)
|
||||
{
|
||||
/* reset sweep marks to sweep all elements (returning them to white) */
|
||||
g->sweepstrgc = 0;
|
||||
|
@ -988,7 +953,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
|
|||
GCObject* o = obj2gco(t);
|
||||
|
||||
// in the second propagation stage, table assignment barrier works as a forward barrier
|
||||
if (FFlag::LuauRescanGrayAgainForwardBarrier && g->gcstate == GCSpropagateagain)
|
||||
if (g->gcstate == GCSpropagateagain)
|
||||
{
|
||||
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
|
||||
reallymarkobject(g, v);
|
||||
|
@ -1044,550 +1009,6 @@ void luaC_linkupval(lua_State* L, UpVal* uv)
|
|||
}
|
||||
}
|
||||
|
||||
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
|
||||
{
|
||||
LUAU_ASSERT(!isdead(g, t));
|
||||
|
||||
if (keepinvariant(g))
|
||||
{
|
||||
/* basic incremental invariant: black can't point to white */
|
||||
LUAU_ASSERT(!(isblack(f) && iswhite(t)));
|
||||
}
|
||||
}
|
||||
|
||||
static void validateref(global_State* g, GCObject* f, TValue* v)
|
||||
{
|
||||
if (iscollectable(v))
|
||||
{
|
||||
LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt);
|
||||
validateobjref(g, f, gcvalue(v));
|
||||
}
|
||||
}
|
||||
|
||||
static void validatetable(global_State* g, Table* h)
|
||||
{
|
||||
int sizenode = 1 << h->lsizenode;
|
||||
|
||||
if (FFlag::LuauArrayBoundary)
|
||||
LUAU_ASSERT(h->lastfree <= sizenode);
|
||||
else
|
||||
LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode);
|
||||
|
||||
if (h->metatable)
|
||||
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
|
||||
|
||||
for (int i = 0; i < h->sizearray; ++i)
|
||||
validateref(g, obj2gco(h), &h->array[i]);
|
||||
|
||||
for (int i = 0; i < sizenode; ++i)
|
||||
{
|
||||
LuaNode* n = &h->node[i];
|
||||
|
||||
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
|
||||
LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode);
|
||||
|
||||
if (!ttisnil(gval(n)))
|
||||
{
|
||||
TValue k = {};
|
||||
k.tt = gkey(n)->tt;
|
||||
k.value = gkey(n)->value;
|
||||
|
||||
validateref(g, obj2gco(h), &k);
|
||||
validateref(g, obj2gco(h), gval(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void validateclosure(global_State* g, Closure* cl)
|
||||
{
|
||||
validateobjref(g, obj2gco(cl), obj2gco(cl->env));
|
||||
|
||||
if (cl->isC)
|
||||
{
|
||||
for (int i = 0; i < cl->nupvalues; ++i)
|
||||
validateref(g, obj2gco(cl), &cl->c.upvals[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
|
||||
|
||||
validateobjref(g, obj2gco(cl), obj2gco(cl->l.p));
|
||||
|
||||
for (int i = 0; i < cl->nupvalues; ++i)
|
||||
validateref(g, obj2gco(cl), &cl->l.uprefs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void validatestack(global_State* g, lua_State* l)
|
||||
{
|
||||
validateref(g, obj2gco(l), gt(l));
|
||||
|
||||
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
|
||||
{
|
||||
LUAU_ASSERT(l->stack <= ci->base);
|
||||
LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top);
|
||||
LUAU_ASSERT(ci->top <= l->stack_last);
|
||||
}
|
||||
|
||||
// note: stack refs can violate gc invariant so we only check for liveness
|
||||
for (StkId o = l->stack; o < l->top; ++o)
|
||||
checkliveness(g, o);
|
||||
|
||||
if (l->namecall)
|
||||
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
|
||||
|
||||
for (GCObject* uv = l->openupval; uv; uv = uv->gch.next)
|
||||
{
|
||||
LUAU_ASSERT(uv->gch.tt == LUA_TUPVAL);
|
||||
LUAU_ASSERT(gco2uv(uv)->v != &gco2uv(uv)->u.value);
|
||||
}
|
||||
}
|
||||
|
||||
static void validateproto(global_State* g, Proto* f)
|
||||
{
|
||||
if (f->source)
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->source));
|
||||
|
||||
if (f->debugname)
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->debugname));
|
||||
|
||||
for (int i = 0; i < f->sizek; ++i)
|
||||
validateref(g, obj2gco(f), &f->k[i]);
|
||||
|
||||
for (int i = 0; i < f->sizeupvalues; ++i)
|
||||
if (f->upvalues[i])
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i]));
|
||||
|
||||
for (int i = 0; i < f->sizep; ++i)
|
||||
if (f->p[i])
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->p[i]));
|
||||
|
||||
for (int i = 0; i < f->sizelocvars; i++)
|
||||
if (f->locvars[i].varname)
|
||||
validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname));
|
||||
}
|
||||
|
||||
static void validateobj(global_State* g, GCObject* o)
|
||||
{
|
||||
/* dead objects can only occur during sweep */
|
||||
if (isdead(g, o))
|
||||
{
|
||||
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (o->gch.tt)
|
||||
{
|
||||
case LUA_TSTRING:
|
||||
break;
|
||||
|
||||
case LUA_TTABLE:
|
||||
validatetable(g, gco2h(o));
|
||||
break;
|
||||
|
||||
case LUA_TFUNCTION:
|
||||
validateclosure(g, gco2cl(o));
|
||||
break;
|
||||
|
||||
case LUA_TUSERDATA:
|
||||
if (gco2u(o)->metatable)
|
||||
validateobjref(g, o, obj2gco(gco2u(o)->metatable));
|
||||
break;
|
||||
|
||||
case LUA_TTHREAD:
|
||||
validatestack(g, gco2th(o));
|
||||
break;
|
||||
|
||||
case LUA_TPROTO:
|
||||
validateproto(g, gco2p(o));
|
||||
break;
|
||||
|
||||
case LUA_TUPVAL:
|
||||
validateref(g, o, gco2uv(o)->v);
|
||||
break;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"unexpected object type");
|
||||
}
|
||||
}
|
||||
|
||||
static void validatelist(global_State* g, GCObject* o)
|
||||
{
|
||||
while (o)
|
||||
{
|
||||
validateobj(g, o);
|
||||
|
||||
o = o->gch.next;
|
||||
}
|
||||
}
|
||||
|
||||
static void validategraylist(global_State* g, GCObject* o)
|
||||
{
|
||||
if (!keepinvariant(g))
|
||||
return;
|
||||
|
||||
while (o)
|
||||
{
|
||||
LUAU_ASSERT(isgray(o));
|
||||
|
||||
switch (o->gch.tt)
|
||||
{
|
||||
case LUA_TTABLE:
|
||||
o = gco2h(o)->gclist;
|
||||
break;
|
||||
case LUA_TFUNCTION:
|
||||
o = gco2cl(o)->gclist;
|
||||
break;
|
||||
case LUA_TTHREAD:
|
||||
o = gco2th(o)->gclist;
|
||||
break;
|
||||
case LUA_TPROTO:
|
||||
o = gco2p(o)->gclist;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"unknown object in gray list");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void luaC_validate(lua_State* L)
|
||||
{
|
||||
global_State* g = L->global;
|
||||
|
||||
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
|
||||
checkliveness(g, &g->registry);
|
||||
|
||||
for (int i = 0; i < LUA_T_COUNT; ++i)
|
||||
if (g->mt[i])
|
||||
LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i])));
|
||||
|
||||
validategraylist(g, g->weak);
|
||||
validategraylist(g, g->gray);
|
||||
validategraylist(g, g->grayagain);
|
||||
|
||||
for (int i = 0; i < g->strt.size; ++i)
|
||||
validatelist(g, g->strt.hash[i]);
|
||||
|
||||
validatelist(g, g->rootgc);
|
||||
validatelist(g, g->strbufgc);
|
||||
|
||||
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
|
||||
{
|
||||
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
||||
LUAU_ASSERT(uv->v != &uv->u.value);
|
||||
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool safejson(char ch)
|
||||
{
|
||||
return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"';
|
||||
}
|
||||
|
||||
static void dumpref(FILE* f, GCObject* o)
|
||||
{
|
||||
fprintf(f, "\"%p\"", o);
|
||||
}
|
||||
|
||||
static void dumprefs(FILE* f, TValue* data, size_t size)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
if (iscollectable(&data[i]))
|
||||
{
|
||||
if (!first)
|
||||
fputc(',', f);
|
||||
first = false;
|
||||
|
||||
dumpref(f, gcvalue(&data[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpstringdata(FILE* f, const char* data, size_t len)
|
||||
{
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
fputc(safejson(data[i]) ? data[i] : '?', f);
|
||||
}
|
||||
|
||||
static void dumpstring(FILE* f, TString* ts)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len)));
|
||||
dumpstringdata(f, ts->data, ts->len);
|
||||
fprintf(f, "\"}");
|
||||
}
|
||||
|
||||
static void dumptable(FILE* f, Table* h)
|
||||
{
|
||||
size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
|
||||
|
||||
fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size));
|
||||
|
||||
if (h->node != &luaH_dummynode)
|
||||
{
|
||||
fprintf(f, ",\"pairs\":[");
|
||||
|
||||
bool first = true;
|
||||
|
||||
for (int i = 0; i < sizenode(h); ++i)
|
||||
{
|
||||
const LuaNode& n = h->node[i];
|
||||
|
||||
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
|
||||
{
|
||||
if (!first)
|
||||
fputc(',', f);
|
||||
first = false;
|
||||
|
||||
if (iscollectable(&n.key))
|
||||
dumpref(f, gcvalue(&n.key));
|
||||
else
|
||||
fprintf(f, "null");
|
||||
|
||||
fputc(',', f);
|
||||
|
||||
if (iscollectable(&n.val))
|
||||
dumpref(f, gcvalue(&n.val));
|
||||
else
|
||||
fprintf(f, "null");
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(f, "]");
|
||||
}
|
||||
if (h->sizearray)
|
||||
{
|
||||
fprintf(f, ",\"array\":[");
|
||||
dumprefs(f, h->array, h->sizearray);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
if (h->metatable)
|
||||
{
|
||||
fprintf(f, ",\"metatable\":");
|
||||
dumpref(f, obj2gco(h->metatable));
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpclosure(FILE* f, Closure* cl)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat,
|
||||
cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues)));
|
||||
|
||||
fprintf(f, ",\"env\":");
|
||||
dumpref(f, obj2gco(cl->env));
|
||||
if (cl->isC)
|
||||
{
|
||||
if (cl->nupvalues)
|
||||
{
|
||||
fprintf(f, ",\"upvalues\":[");
|
||||
dumprefs(f, cl->c.upvals, cl->nupvalues);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(f, ",\"proto\":");
|
||||
dumpref(f, obj2gco(cl->l.p));
|
||||
if (cl->nupvalues)
|
||||
{
|
||||
fprintf(f, ",\"upvalues\":[");
|
||||
dumprefs(f, cl->l.uprefs, cl->nupvalues);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpudata(FILE* f, Udata* u)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag);
|
||||
|
||||
if (u->metatable)
|
||||
{
|
||||
fprintf(f, ",\"metatable\":");
|
||||
dumpref(f, obj2gco(u->metatable));
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpthread(FILE* f, lua_State* th)
|
||||
{
|
||||
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
||||
|
||||
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
|
||||
|
||||
if (iscollectable(&th->l_gt))
|
||||
{
|
||||
fprintf(f, ",\"env\":");
|
||||
dumpref(f, gcvalue(&th->l_gt));
|
||||
}
|
||||
|
||||
Closure* tcl = 0;
|
||||
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
|
||||
{
|
||||
if (ttisfunction(ci->func))
|
||||
{
|
||||
tcl = clvalue(ci->func);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tcl && !tcl->isC && tcl->l.p->source)
|
||||
{
|
||||
Proto* p = tcl->l.p;
|
||||
|
||||
fprintf(f, ",\"source\":\"");
|
||||
dumpstringdata(f, p->source->data, p->source->len);
|
||||
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
|
||||
}
|
||||
|
||||
if (th->top > th->stack)
|
||||
{
|
||||
fprintf(f, ",\"stack\":[");
|
||||
dumprefs(f, th->stack, th->top - th->stack);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpproto(FILE* f, Proto* p)
|
||||
{
|
||||
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
|
||||
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
|
||||
|
||||
fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size));
|
||||
|
||||
if (p->source)
|
||||
{
|
||||
fprintf(f, ",\"source\":\"");
|
||||
dumpstringdata(f, p->source->data, p->source->len);
|
||||
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
|
||||
}
|
||||
|
||||
if (p->sizek)
|
||||
{
|
||||
fprintf(f, ",\"constants\":[");
|
||||
dumprefs(f, p->k, p->sizek);
|
||||
fprintf(f, "]");
|
||||
}
|
||||
|
||||
if (p->sizep)
|
||||
{
|
||||
fprintf(f, ",\"protos\":[");
|
||||
for (int i = 0; i < p->sizep; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
fputc(',', f);
|
||||
dumpref(f, obj2gco(p->p[i]));
|
||||
}
|
||||
fprintf(f, "]");
|
||||
}
|
||||
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpupval(FILE* f, UpVal* uv)
|
||||
{
|
||||
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal)));
|
||||
|
||||
if (iscollectable(uv->v))
|
||||
{
|
||||
fprintf(f, ",\"object\":");
|
||||
dumpref(f, gcvalue(uv->v));
|
||||
}
|
||||
fprintf(f, "}");
|
||||
}
|
||||
|
||||
static void dumpobj(FILE* f, GCObject* o)
|
||||
{
|
||||
switch (o->gch.tt)
|
||||
{
|
||||
case LUA_TSTRING:
|
||||
return dumpstring(f, gco2ts(o));
|
||||
|
||||
case LUA_TTABLE:
|
||||
return dumptable(f, gco2h(o));
|
||||
|
||||
case LUA_TFUNCTION:
|
||||
return dumpclosure(f, gco2cl(o));
|
||||
|
||||
case LUA_TUSERDATA:
|
||||
return dumpudata(f, gco2u(o));
|
||||
|
||||
case LUA_TTHREAD:
|
||||
return dumpthread(f, gco2th(o));
|
||||
|
||||
case LUA_TPROTO:
|
||||
return dumpproto(f, gco2p(o));
|
||||
|
||||
case LUA_TUPVAL:
|
||||
return dumpupval(f, gco2uv(o));
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dumplist(FILE* f, GCObject* o)
|
||||
{
|
||||
while (o)
|
||||
{
|
||||
dumpref(f, o);
|
||||
fputc(':', f);
|
||||
dumpobj(f, o);
|
||||
fputc(',', f);
|
||||
fputc('\n', f);
|
||||
|
||||
// thread has additional list containing collectable objects that are not present in rootgc
|
||||
if (o->gch.tt == LUA_TTHREAD)
|
||||
dumplist(f, gco2th(o)->openupval);
|
||||
|
||||
o = o->gch.next;
|
||||
}
|
||||
}
|
||||
|
||||
void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat))
|
||||
{
|
||||
global_State* g = L->global;
|
||||
FILE* f = static_cast<FILE*>(file);
|
||||
|
||||
fprintf(f, "{\"objects\":{\n");
|
||||
dumplist(f, g->rootgc);
|
||||
dumplist(f, g->strbufgc);
|
||||
for (int i = 0; i < g->strt.size; ++i)
|
||||
dumplist(f, g->strt.hash[i]);
|
||||
|
||||
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
|
||||
fprintf(f, "},\"roots\":{\n");
|
||||
fprintf(f, "\"mainthread\":");
|
||||
dumpref(f, obj2gco(g->mainthread));
|
||||
fprintf(f, ",\"registry\":");
|
||||
dumpref(f, gcvalue(&g->registry));
|
||||
|
||||
fprintf(f, "},\"stats\":{\n");
|
||||
|
||||
fprintf(f, "\"size\":%d,\n", int(g->totalbytes));
|
||||
|
||||
fprintf(f, "\"categories\":{\n");
|
||||
for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++)
|
||||
{
|
||||
if (size_t bytes = g->memcatbytes[i])
|
||||
{
|
||||
if (categoryName)
|
||||
fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes));
|
||||
else
|
||||
fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes));
|
||||
}
|
||||
}
|
||||
fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing ,
|
||||
fprintf(f, "}\n");
|
||||
fprintf(f, "}}\n");
|
||||
}
|
||||
|
||||
// measure the allocation rate in bytes/sec
|
||||
// returns -1 if allocation rate cannot be measured
|
||||
int64_t luaC_allocationrate(lua_State* L)
|
||||
|
@ -1595,7 +1016,7 @@ int64_t luaC_allocationrate(lua_State* L)
|
|||
global_State* g = L->global;
|
||||
const double durationthreshold = 1e-3; // avoid measuring intervals smaller than 1ms
|
||||
|
||||
if (g->gcstate <= (FFlag::LuauSeparateAtomic ? GCSatomic : GCSpropagateagain))
|
||||
if (g->gcstate <= GCSatomic)
|
||||
{
|
||||
double duration = lua_clock() - g->gcstats.lastcycle.endtimestamp;
|
||||
|
||||
|
|
559
VM/src/lgcdebug.cpp
Normal file
559
VM/src/lgcdebug.cpp
Normal file
|
@ -0,0 +1,559 @@
|
|||
// 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 "ludata.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");
|
||||
}
|
|
@ -17,7 +17,7 @@ static const luaL_Reg lualibs[] = {
|
|||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API void luaL_openlibs(lua_State* L)
|
||||
void luaL_openlibs(lua_State* L)
|
||||
{
|
||||
const luaL_Reg* lib = lualibs;
|
||||
for (; lib->func; lib++)
|
||||
|
@ -28,7 +28,7 @@ LUALIB_API void luaL_openlibs(lua_State* L)
|
|||
}
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_sandbox(lua_State* L)
|
||||
void luaL_sandbox(lua_State* L)
|
||||
{
|
||||
// set all libraries to read-only
|
||||
lua_pushnil(L);
|
||||
|
@ -44,14 +44,14 @@ LUALIB_API void luaL_sandbox(lua_State* L)
|
|||
lua_pushliteral(L, "");
|
||||
lua_getmetatable(L, -1);
|
||||
lua_setreadonly(L, -1, true);
|
||||
lua_pop(L, 1);
|
||||
lua_pop(L, 2);
|
||||
|
||||
// set globals to readonly and activate safeenv since the env is immutable
|
||||
lua_setreadonly(L, LUA_GLOBALSINDEX, true);
|
||||
lua_setsafeenv(L, LUA_GLOBALSINDEX, true);
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_sandboxthread(lua_State* L)
|
||||
void luaL_sandboxthread(lua_State* L)
|
||||
{
|
||||
// create new global table that proxies reads to original table
|
||||
lua_newtable(L);
|
||||
|
@ -81,7 +81,7 @@ static void* l_alloc(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsi
|
|||
return realloc(ptr, nsize);
|
||||
}
|
||||
|
||||
LUALIB_API lua_State* luaL_newstate(void)
|
||||
lua_State* luaL_newstate(void)
|
||||
{
|
||||
return lua_newstate(l_alloc, NULL);
|
||||
}
|
||||
|
|
|
@ -385,8 +385,7 @@ static int math_sign(lua_State* L)
|
|||
|
||||
static int math_round(lua_State* L)
|
||||
{
|
||||
double v = luaL_checknumber(L, 1);
|
||||
lua_pushnumber(L, round(v));
|
||||
lua_pushnumber(L, round(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -429,7 +428,7 @@ static const luaL_Reg mathlib[] = {
|
|||
/*
|
||||
** Open math library
|
||||
*/
|
||||
LUALIB_API int luaopen_math(lua_State* L)
|
||||
int luaopen_math(lua_State* L)
|
||||
{
|
||||
uint64_t seed = uintptr_t(L);
|
||||
seed ^= time(NULL);
|
||||
|
|
|
@ -78,15 +78,7 @@ typedef struct lua_TValue
|
|||
#define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th)
|
||||
#define upvalue(o) check_exp(ttisupval(o), &(o)->value.gc->uv)
|
||||
|
||||
// beware bit magic: a value is false if it's nil or boolean false
|
||||
// baseline implementation: (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0))
|
||||
// we'd like a branchless version of this which helps with performance, and a very fast version
|
||||
// so our strategy is to always read the boolean value (not using bvalue(o) because that asserts when type isn't boolean)
|
||||
// we then combine it with type to produce 0/1 as follows:
|
||||
// - when type is nil (0), & makes the result 0
|
||||
// - when type is boolean (1), we effectively only look at the bottom bit, so result is 0 iff boolean value is 0
|
||||
// - when type is different, it must have some of the top bits set - we keep all top bits of boolean value so the result is non-0
|
||||
#define l_isfalse(o) (!(((o)->value.b | ~1) & ttype(o)))
|
||||
#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0))
|
||||
|
||||
/*
|
||||
** for internal debug only
|
||||
|
|
|
@ -186,7 +186,7 @@ static const luaL_Reg syslib[] = {
|
|||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_os(lua_State* L)
|
||||
int luaopen_os(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_OSLIBNAME, syslib);
|
||||
return 1;
|
||||
|
|
|
@ -206,32 +206,3 @@ void luaS_free(lua_State* L, TString* ts)
|
|||
L->global->strt.nuse--;
|
||||
luaM_free(L, ts, sizestring(ts->len), ts->memcat);
|
||||
}
|
||||
|
||||
Udata* luaS_newudata(lua_State* L, size_t s, int tag)
|
||||
{
|
||||
if (s > INT_MAX - sizeof(Udata))
|
||||
luaM_toobig(L);
|
||||
Udata* u = luaM_new(L, Udata, sizeudata(s), L->activememcat);
|
||||
luaC_link(L, u, LUA_TUSERDATA);
|
||||
u->len = int(s);
|
||||
u->metatable = NULL;
|
||||
LUAU_ASSERT(tag >= 0 && tag <= 255);
|
||||
u->tag = uint8_t(tag);
|
||||
return u;
|
||||
}
|
||||
|
||||
void luaS_freeudata(lua_State* L, Udata* u)
|
||||
{
|
||||
LUAU_ASSERT(u->tag < LUA_UTAG_LIMIT || u->tag == UTAG_IDTOR);
|
||||
|
||||
void (*dtor)(void*) = nullptr;
|
||||
if (u->tag == UTAG_IDTOR)
|
||||
memcpy(&dtor, &u->data + u->len - sizeof(dtor), sizeof(dtor));
|
||||
else if (u->tag)
|
||||
dtor = L->global->udatagc[u->tag];
|
||||
|
||||
if (dtor)
|
||||
dtor(u->data);
|
||||
|
||||
luaM_free(L, u, sizeudata(u->len), u->memcat);
|
||||
}
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
/* string size limit */
|
||||
#define MAXSSIZE (1 << 30)
|
||||
|
||||
/* special tag value is used for user data with inline dtors */
|
||||
#define UTAG_IDTOR LUA_UTAG_LIMIT
|
||||
|
||||
#define sizestring(len) (offsetof(TString, data) + len + 1)
|
||||
#define sizeudata(len) (offsetof(Udata, data) + len)
|
||||
|
||||
#define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s)))
|
||||
#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, (sizeof(s) / sizeof(char)) - 1))
|
||||
|
@ -26,8 +22,5 @@ LUAI_FUNC void luaS_resize(lua_State* L, int newsize);
|
|||
LUAI_FUNC TString* luaS_newlstr(lua_State* L, const char* str, size_t l);
|
||||
LUAI_FUNC void luaS_free(lua_State* L, TString* ts);
|
||||
|
||||
LUAI_FUNC Udata* luaS_newudata(lua_State* L, size_t s, int tag);
|
||||
LUAI_FUNC void luaS_freeudata(lua_State* L, Udata* u);
|
||||
|
||||
LUAI_FUNC TString* luaS_bufstart(lua_State* L, size_t size);
|
||||
LUAI_FUNC TString* luaS_buffinish(lua_State* L, TString* ts);
|
||||
|
|
|
@ -1657,7 +1657,7 @@ static void createmetatable(lua_State* L)
|
|||
/*
|
||||
** Open string library
|
||||
*/
|
||||
LUALIB_API int luaopen_string(lua_State* L)
|
||||
int luaopen_string(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_STRLIBNAME, strlib);
|
||||
createmetatable(L);
|
||||
|
|
|
@ -527,7 +527,7 @@ static const luaL_Reg tab_funcs[] = {
|
|||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_table(lua_State* L)
|
||||
int luaopen_table(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_TABLIBNAME, tab_funcs);
|
||||
|
||||
|
|
37
VM/src/ludata.cpp
Normal file
37
VM/src/ludata.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
// 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 "ludata.h"
|
||||
|
||||
#include "lgc.h"
|
||||
#include "lmem.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
Udata* luaU_newudata(lua_State* L, size_t s, int tag)
|
||||
{
|
||||
if (s > INT_MAX - sizeof(Udata))
|
||||
luaM_toobig(L);
|
||||
Udata* u = luaM_new(L, Udata, sizeudata(s), L->activememcat);
|
||||
luaC_link(L, u, LUA_TUSERDATA);
|
||||
u->len = int(s);
|
||||
u->metatable = NULL;
|
||||
LUAU_ASSERT(tag >= 0 && tag <= 255);
|
||||
u->tag = uint8_t(tag);
|
||||
return u;
|
||||
}
|
||||
|
||||
void luaU_freeudata(lua_State* L, Udata* u)
|
||||
{
|
||||
LUAU_ASSERT(u->tag < LUA_UTAG_LIMIT || u->tag == UTAG_IDTOR);
|
||||
|
||||
void (*dtor)(void*) = nullptr;
|
||||
if (u->tag == UTAG_IDTOR)
|
||||
memcpy(&dtor, &u->data + u->len - sizeof(dtor), sizeof(dtor));
|
||||
else if (u->tag)
|
||||
dtor = L->global->udatagc[u->tag];
|
||||
|
||||
if (dtor)
|
||||
dtor(u->data);
|
||||
|
||||
luaM_free(L, u, sizeudata(u->len), u->memcat);
|
||||
}
|
13
VM/src/ludata.h
Normal file
13
VM/src/ludata.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
// 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
|
||||
#pragma once
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
/* special tag value is used for user data with inline dtors */
|
||||
#define UTAG_IDTOR LUA_UTAG_LIMIT
|
||||
|
||||
#define sizeudata(len) (offsetof(Udata, data) + len)
|
||||
|
||||
LUAI_FUNC Udata* luaU_newudata(lua_State* L, size_t s, int tag);
|
||||
LUAI_FUNC void luaU_freeudata(lua_State* L, Udata* u);
|
|
@ -283,7 +283,7 @@ static const luaL_Reg funcs[] = {
|
|||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_utf8(lua_State* L)
|
||||
int luaopen_utf8(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_UTF8LIBNAME, funcs);
|
||||
|
||||
|
|
|
@ -63,7 +63,8 @@
|
|||
#define VM_KV(i) (LUAU_ASSERT(unsigned(i) < unsigned(cl->l.p->sizek)), &k[i])
|
||||
#define VM_UV(i) (LUAU_ASSERT(unsigned(i) < unsigned(cl->nupvalues)), &cl->l.uprefs[i])
|
||||
|
||||
#define VM_PATCH_C(pc, slot) ((uint8_t*)(pc))[3] = uint8_t(slot)
|
||||
#define VM_PATCH_C(pc, slot) *const_cast<Instruction*>(pc) = ((uint8_t(slot) << 24) | (0x00ffffffu & *(pc)))
|
||||
#define VM_PATCH_E(pc, slot) *const_cast<Instruction*>(pc) = ((uint32_t(slot) << 8) | (0x000000ffu & *(pc)))
|
||||
|
||||
// NOTE: If debugging the Luau code, disable this macro to prevent timeouts from
|
||||
// occurring when tracing code in Visual Studio / XCode
|
||||
|
@ -120,7 +121,7 @@
|
|||
*/
|
||||
#if VM_USE_CGOTO
|
||||
#define VM_CASE(op) CASE_##op:
|
||||
#define VM_NEXT() goto*(SingleStep ? &&dispatch : kDispatchTable[*(uint8_t*)pc])
|
||||
#define VM_NEXT() goto*(SingleStep ? &&dispatch : kDispatchTable[LUAU_INSN_OP(*pc)])
|
||||
#define VM_CONTINUE(op) goto* kDispatchTable[uint8_t(op)]
|
||||
#else
|
||||
#define VM_CASE(op) case op:
|
||||
|
@ -325,7 +326,7 @@ static void luau_execute(lua_State* L)
|
|||
// ... and singlestep logic :)
|
||||
if (SingleStep)
|
||||
{
|
||||
if (L->global->cb.debugstep && !luau_skipstep(*(uint8_t*)pc))
|
||||
if (L->global->cb.debugstep && !luau_skipstep(LUAU_INSN_OP(*pc)))
|
||||
{
|
||||
VM_PROTECT(luau_callhook(L, L->global->cb.debugstep, NULL));
|
||||
|
||||
|
@ -335,13 +336,12 @@ static void luau_execute(lua_State* L)
|
|||
}
|
||||
|
||||
#if VM_USE_CGOTO
|
||||
VM_CONTINUE(*(uint8_t*)pc);
|
||||
VM_CONTINUE(LUAU_INSN_OP(*pc));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !VM_USE_CGOTO
|
||||
// Note: this assumes that LUAU_INSN_OP() decodes the first byte (aka least significant byte in the little endian encoding)
|
||||
size_t dispatchOp = *(uint8_t*)pc;
|
||||
size_t dispatchOp = LUAU_INSN_OP(*pc);
|
||||
|
||||
dispatchContinue:
|
||||
switch (dispatchOp)
|
||||
|
@ -2577,7 +2577,7 @@ static void luau_execute(lua_State* L)
|
|||
|
||||
// update hits with saturated add and patch the instruction in place
|
||||
hits = (hits < (1 << 23) - 1) ? hits + 1 : hits;
|
||||
((uint32_t*)pc)[-1] = LOP_COVERAGE | (uint32_t(hits) << 8);
|
||||
VM_PATCH_E(pc - 1, hits);
|
||||
|
||||
VM_NEXT();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "lgc.h"
|
||||
#include "lmem.h"
|
||||
#include "lbytecode.h"
|
||||
#include "lapi.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
@ -162,9 +163,8 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
|||
size_t GCthreshold = L->global->GCthreshold;
|
||||
L->global->GCthreshold = SIZE_MAX;
|
||||
|
||||
// env is 0 for current environment and a stack relative index otherwise
|
||||
LUAU_ASSERT(env <= 0 && L->top - L->base >= -env);
|
||||
Table* envt = (env == 0) ? hvalue(gt(L)) : hvalue(L->top + env);
|
||||
// env is 0 for current environment and a stack index otherwise
|
||||
Table* envt = (env == 0) ? hvalue(gt(L)) : hvalue(luaA_toobject(L, env));
|
||||
|
||||
TString* source = luaS_new(L, chunkname);
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ const float* luaV_tovector(const TValue* obj)
|
|||
static void callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1, const TValue* p2)
|
||||
{
|
||||
ptrdiff_t result = savestack(L, res);
|
||||
// RBOLOX: using stack room beyond top is technically safe here, but for very complicated reasons:
|
||||
// using stack room beyond top is technically safe here, but for very complicated reasons:
|
||||
// * The stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
|
||||
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
|
||||
// stack and checkstack may invalidate those pointers
|
||||
|
@ -74,7 +74,7 @@ static void callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1
|
|||
|
||||
static void callTM(lua_State* L, const TValue* f, const TValue* p1, const TValue* p2, const TValue* p3)
|
||||
{
|
||||
// RBOLOX: using stack room beyond top is technically safe here, but for very complicated reasons:
|
||||
// using stack room beyond top is technically safe here, but for very complicated reasons:
|
||||
// * The stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
|
||||
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
|
||||
// stack and checkstack may invalidate those pointers
|
||||
|
|
|
@ -451,15 +451,16 @@ function raytraceScene()
|
|||
end
|
||||
|
||||
function arrayToCanvasCommands(pixels)
|
||||
local s = '<!DOCTYPE html><html><head><title>Test</title></head><body><canvas id="renderCanvas" width="' .. size .. 'px" height="' .. size .. 'px"></canvas><scr' .. 'ipt>\nvar pixels = [';
|
||||
local s = {};
|
||||
table.insert(s, '<!DOCTYPE html><html><head><title>Test</title></head><body><canvas id="renderCanvas" width="' .. size .. 'px" height="' .. size .. 'px"></canvas><scr' .. 'ipt>\nvar pixels = [');
|
||||
for y = 0,size-1 do
|
||||
s = s .. "[";
|
||||
table.insert(s, "[");
|
||||
for x = 0,size-1 do
|
||||
s = s .. "[" .. math.floor(pixels[y + 1][x + 1][1] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][2] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][3] * 255) .. "],";
|
||||
table.insert(s, "[" .. math.floor(pixels[y + 1][x + 1][1] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][2] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][3] * 255) .. "],");
|
||||
end
|
||||
s = s .. "],";
|
||||
table.insert(s, "],");
|
||||
end
|
||||
s = s .. '];\n var canvas = document.getElementById("renderCanvas").getContext("2d");\n\
|
||||
table.insert(s, '];\n var canvas = document.getElementById("renderCanvas").getContext("2d");\n\
|
||||
\n\
|
||||
\n\
|
||||
var size = ' .. size .. ';\n\
|
||||
|
@ -479,9 +480,9 @@ for (var y = 0; y < size; y++) {\n\
|
|||
canvas.setFillColor(l[0], l[1], l[2], 1);\n\
|
||||
canvas.fillRect(x, y, 1, 1);\n\
|
||||
}\n\
|
||||
}</scr' .. 'ipt></body></html>';
|
||||
}</script></body></html>');
|
||||
|
||||
return s;
|
||||
return table.concat(s);
|
||||
end
|
||||
|
||||
testOutput = arrayToCanvasCommands(raytraceScene());
|
||||
|
|
|
@ -29,3 +29,5 @@ pages:
|
|||
url: /profile
|
||||
- title: Library
|
||||
url: /library
|
||||
- title: Grammar
|
||||
url: /grammar
|
||||
|
|
82
docs/_pages/grammar.md
Normal file
82
docs/_pages/grammar.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
permalink: /grammar
|
||||
title: Grammar
|
||||
toc: true
|
||||
---
|
||||
|
||||
This is the complete syntax grammar for Luau in EBNF. More information about the terminal nodes String and Number
|
||||
is available in the [syntax section](syntax).
|
||||
|
||||
> Note: this grammar is currently missing type pack syntax for generic arguments
|
||||
|
||||
```ebnf
|
||||
chunk ::= {stat [`;']} [laststat [`;']]
|
||||
block ::= chunk
|
||||
stat ::= varlist `=' explist |
|
||||
var compoundop exp |
|
||||
functioncall |
|
||||
do block end |
|
||||
while exp do block end |
|
||||
repeat block until exp |
|
||||
if exp then block {elseif exp then block} [else block] end |
|
||||
for binding `=' exp `,' exp [`,' exp] do block end |
|
||||
for bindinglist in explist do block end |
|
||||
function funcname funcbody |
|
||||
local function NAME funcbody |
|
||||
local bindinglist [`=' explist] |
|
||||
[export] type NAME [`<' GenericTypeList `>'] `=' Type
|
||||
|
||||
laststat ::= return [explist] | break | continue
|
||||
|
||||
funcname ::= NAME {`.' NAME} [`:' NAME]
|
||||
funcbody ::= `(' [parlist] `)' [`:' ReturnType] block end
|
||||
parlist ::= bindinglist [`,' `...'] | `...'
|
||||
|
||||
explist ::= {exp `,'} exp
|
||||
namelist ::= NAME {`,' NAME}
|
||||
|
||||
binding ::= NAME [`:' TypeAnnotation]
|
||||
bindinglist ::= binding [`,' bindinglist] (* equivalent of Lua 5.1 `namelist`, except with optional type annotations *)
|
||||
|
||||
var ::= NAME | prefixexp `[' exp `]' | prefixexp `.' Name
|
||||
varlist ::= var {`,' var}
|
||||
prefixexp ::= var | functioncall | `(' exp `)'
|
||||
functioncall ::= prefixexp funcargs | prefixexp `:' NAME funcargs
|
||||
|
||||
exp ::= (asexp | unop exp) { binop exp }
|
||||
ifelseexp ::= if exp then exp {elseif exp then exp} else exp
|
||||
asexp ::= simpleexp [`::' Type]
|
||||
simpleexp ::= NUMBER | STRING | nil | true | false | `...' | tableconstructor | function body | prefixexp | ifelseexp
|
||||
funcargs ::= `(' [explist] `)' | tableconstructor | STRING
|
||||
|
||||
tableconstructor ::= `{' [fieldlist] `}'
|
||||
fieldlist ::= field {fieldsep field} [fieldsep]
|
||||
field ::= `[' exp `]' `=' exp | NAME `=' exp | exp
|
||||
fieldsep ::= `,' | `;'
|
||||
|
||||
compoundop :: `+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='
|
||||
binop ::= `+' | `-' | `*' | `/' | `^' | `%' | `..' | `<' | `<=' | `>' | `>=' | `==' | `~=' | and | or
|
||||
unop ::= `-' | not | `#'
|
||||
|
||||
SimpleType ::=
|
||||
nil |
|
||||
NAME[`.' NAME] [ `<' TypeList `>' ] |
|
||||
`typeof' `(' exp `)' |
|
||||
TableType |
|
||||
FunctionType
|
||||
|
||||
Type ::=
|
||||
SimpleType [`?`] |
|
||||
SimpleType [`|` Type] |
|
||||
SimpleType [`&` Type]
|
||||
|
||||
GenericTypeList ::= NAME [`...'] {`,' NAME [`...']}
|
||||
TypeList ::= Type [`,' TypeList] | ...Type
|
||||
ReturnType ::= Type | `(' TypeList `)'
|
||||
TableIndexer ::= `[' Type `]' `:' Type
|
||||
TableProp ::= NAME `:' Type
|
||||
TablePropOrIndexer ::= TableProp | TableIndexer
|
||||
PropList ::= TablePropOrIndexer {fieldsep TablePropOrIndexer} [fieldsep]
|
||||
TableType ::= `{' PropList `}'
|
||||
FunctionType ::= [`<' GenericTypeList `>'] `(' [TypeList] `)' `->` ReturnType
|
||||
```
|
|
@ -624,6 +624,12 @@ function coroutine.resume(co: thread, args: ...any): (boolean, ...any)
|
|||
|
||||
Resumes the coroutine and passes the arguments along to the suspension point. When the coroutine yields or finishes, returns `true` and all values returned at the suspension point. If an error is raised during coroutine resumption, this function returns `false` and the error object, similarly to `pcall`.
|
||||
|
||||
```
|
||||
function coroutine.close(co: thread): (boolean, any?)
|
||||
```
|
||||
|
||||
Closes the coroutine which puts coroutine in the dead state. The coroutine must be dead or suspended - in particular it can't be currently running. If the coroutine that's being closed was in an error state, returns `false` along with an error object; otherwise returns `true`. After closing, the coroutine can't be resumed and the coroutine stack becomes empty.
|
||||
|
||||
## bit32 library
|
||||
|
||||
All functions in the `bit32` library treat input numbers as 32-bit unsigned integers in `[0..4294967295]` range. The bit positions start at 0 where 0 corresponds to the least significant bit.
|
||||
|
@ -700,6 +706,18 @@ function bit32.rshift(n: number, i: number): number
|
|||
|
||||
Shifts `n` to the right by `i` bits (if `i` is negative, a left shift is performed instead).
|
||||
|
||||
```
|
||||
function bit32.countlz(n: number): number
|
||||
```
|
||||
|
||||
Returns the number of consecutive zero bits in the 32-bit representation of `n` starting from the left-most (most significant) bit. Returns 32 if `n` is zero.
|
||||
|
||||
```
|
||||
function bit32.countrz(n: number): number
|
||||
```
|
||||
|
||||
Returns the number of consecutive zero bits in the 32-bit representation of `n` starting from the right-most (least significant) bit. Returns 32 if `n` is zero.
|
||||
|
||||
## utf8 library
|
||||
|
||||
Strings in Luau can contain arbitrary bytes; however, in many applications strings representing text contain UTF8 encoded data by convention, that can be inspected and manipulated using `utf8` library.
|
||||
|
|
|
@ -134,6 +134,12 @@ Lua implements upvalues as garbage collected objects that can point directly at
|
|||
|
||||
Note that "immutable" in this case only refers to the variable itself - if the variable isn't assigned to it can be captured by value, even if it's a table that has its contents change.
|
||||
|
||||
## Closure caching
|
||||
|
||||
With optimized upvalue storage, creating new closures (function objects) is more efficient but still requires allocating a new object every time. This can be problematic for cases when functions are passed to algorithms like `table.sort` or functions like `pcall`, as it results in excessive allocation traffic which then leads to more work for garbage collector.
|
||||
|
||||
To make closure creation cheaper, Luau compiler implements closure caching - when multiple executions of the same function expression are guaranteed to result in the function object that is semantically identical, the compiler may cache the closure and always return the same object. This changes the function identity which may affect code that uses function objects as table keys, but preserves the calling semantics - compiler will only do this if calling the original (cached) function behaves the same way as a newly created function would. The heuristics used for this optimization are subject to change; currently, the compiler will cache closures that have no upvalues, or all upvalues are immutable (see previous section) and are declared at the module scope, as the module scope is (almost always) evaluated only once.
|
||||
|
||||
## Fast memory allocator
|
||||
|
||||
Similarly to LuaJIT, but unlike vanilla Lua, Luau implements a custom allocator that is highly specialized and tuned to the common allocation workloads we see. The allocator design is inspired by classic pool allocators as well as the excellent `mimalloc`, but through careful domain-specific tuning it beats all general purpose allocators we've tested, including `rpmalloc`, `mimalloc`, `jemalloc`, `ptmalloc` and `tcmalloc`.
|
||||
|
|
|
@ -287,6 +287,66 @@ In type annotations, this is written as `...T`:
|
|||
type F = (...number) -> ...string
|
||||
```
|
||||
|
||||
## Type packs
|
||||
|
||||
Multiple function return values as well as the function variadic parameter use a type pack to represent a list of types.
|
||||
|
||||
When a type alias is defined, generic type pack parameters can be used after the type parameters:
|
||||
|
||||
```lua
|
||||
type Signal<T, U...> = { f: (T, U...) -> (), data: T }
|
||||
```
|
||||
|
||||
> Keep in mind that `...T` is a variadic type pack (many elements of the same type `T`), while `U...` is a generic type pack that can contain zero or more types and they don't have to be the same.
|
||||
|
||||
It is also possible for a generic function to reference a generic type pack from the generics list:
|
||||
|
||||
```lua
|
||||
local function call<T, U...>(s: Signal<T, U...>, ...: U...)
|
||||
s.f(s.data, ...)
|
||||
end
|
||||
```
|
||||
|
||||
Generic types with type packs can be instantiated by providing a type pack:
|
||||
|
||||
```lua
|
||||
local signal: Signal<string, (number, number, boolean)> = --
|
||||
|
||||
call(signal, 1, 2, false)
|
||||
```
|
||||
|
||||
There are also other ways to instantiate types with generic type pack parameters:
|
||||
|
||||
```lua
|
||||
type A<T, U...> = (T) -> U...
|
||||
|
||||
type B = A<number, ...string> -- with a variadic type pack
|
||||
type C<S...> = A<number, S...> -- with a generic type pack
|
||||
type D = A<number, ()> -- with an empty type pack
|
||||
```
|
||||
|
||||
Trailing type pack argument can also be provided without parentheses by specifying variadic type arguments:
|
||||
|
||||
```lua
|
||||
type List<Head, Rest...> = (Head, Rest...) -> ()
|
||||
|
||||
type B = List<number> -- Rest... is ()
|
||||
type C = List<number, string, boolean> -- Rest is (string, boolean)
|
||||
|
||||
type Returns<T...> = () -> T...
|
||||
|
||||
-- When there are no type parameters, the list can be left empty
|
||||
type D = Returns<> -- T... is ()
|
||||
```
|
||||
|
||||
Type pack parameters are not limited to a single one, as many as required can be specified:
|
||||
|
||||
```lua
|
||||
type Callback<Args..., Rets...> = { f: (Args...) -> Rets... }
|
||||
|
||||
type A = Callback<(number, string), ...number>
|
||||
```
|
||||
|
||||
## Typing idiomatic OOP
|
||||
|
||||
One common pattern we see throughout Roblox is this OOP idiom. A downside with this pattern is that it does not automatically create a type binding for an instance of that class, so one has to write `type Account = typeof(Account.new("", 0))`.
|
||||
|
|
77
docs/_posts/2021-11-29-luau-recap-november-2021.md
Normal file
77
docs/_posts/2021-11-29-luau-recap-november-2021.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
layout: single
|
||||
title: "Luau Recap: November 2021"
|
||||
---
|
||||
|
||||
Luau is our new language that you can read more about at [https://luau-lang.org](https://luau-lang.org).
|
||||
|
||||
[Cross-posted to the [Roblox Developer Forum](https://devforum.roblox.com/t/luau-recap-november-2021/).]
|
||||
|
||||
## Type packs in type aliases
|
||||
|
||||
Type packs are the construct Luau uses to represent a sequence of types. We've had syntax for generic type packs for a while now, and it sees use in generic functions, but it hasn't been available in type aliases. That has changed, and it is now syntactically legal to write the following type alias:
|
||||
```lua
|
||||
type X<A...> = () -> A...
|
||||
type Y = X<number, string>
|
||||
```
|
||||
|
||||
We've also added support for explicit type packs. Previously, it was impossible to instantiate a generic with two or more type pack parameters, because it wasn't clear where the first pack ended and the second one began. We have introduced a new syntax for this use case:
|
||||
```
|
||||
type Fn<P..., R...> = (P...) -> R...
|
||||
type X = Fn<(number, string), (string, number)>
|
||||
```
|
||||
|
||||
For more information, check out [the documentation](https://luau-lang.org/typecheck#type-packs) or [the RFC](https://github.com/Roblox/luau/blob/f86d4c6995418e489a55be0100159009492778ff/rfcs/syntax-type-alias-type-packs.md) for this feature.
|
||||
|
||||
## Luau is open-source!
|
||||
|
||||
We announced this in early November but it deserves repeating: Luau is now an open-source project! You can use Luau outside of Roblox, subject to MIT License, and - importantly - we accept contributions.
|
||||
|
||||
Many changes contributed by community, both Roblox and external, have been merged since we've made Luau open source. Of note are two visible changes that shipped on Roblox platform:
|
||||
|
||||
- The type error "Expected to return X values, but Y values are returned here" actually had X and Y swapped! This is now fixed.
|
||||
- Luau compiler dutifully computed the length of the string when using `#` operator on a string literal; this is now fixed and `#"foo"` compiles to 3.
|
||||
|
||||
You might think that C++ is a scary language and you can't contribute to Luau. If so, you'd be happy to know that the contents of https://luau-lang.org, where we host our documentation, is also hosted on GitHub in the same repository (https://github.com/Roblox/luau/tree/master/docs) and that we'd love the community to contribute improvements to documentation among other changes! For example see [issues in this list](https://github.com/Roblox/luau/issues?q=is%3Aissue+is%3Aopen+label%3A%22pr+welcome%22) that start with "Documentation", but all other changes and additions to documentation are also welcome.
|
||||
|
||||
## Library improvements
|
||||
|
||||
```lua
|
||||
function bit32.countlz(n: number): number
|
||||
function bit32.countrz(n: number): number
|
||||
```
|
||||
Given a number, returns the number of preceding left or trailing right-hand bits that are `0`.
|
||||
|
||||
See [the RFC for these functions](https://github.com/Roblox/luau/blob/f86d4c6995418e489a55be0100159009492778ff/rfcs/function-bit32-countlz-countrz.md) for more information.
|
||||
|
||||
## Type checking improvements
|
||||
|
||||
We have enabled a rewrite of how Luau handles `require` tracing. This has two main effects: firstly, in strict mode, `require` statements that Luau can't resolve will trigger type errors; secondly, Luau now understands the `FindFirstAncestor` method in `require` expressions.
|
||||
|
||||
Luau now warns when the index to `table.move` is 0, as this is non-idiomatic and performs poorly. If this behavior is intentional, wrap the index in parentheses to suppress the warning.
|
||||
|
||||
Luau now provides additional context in table and class type mismatch errors.
|
||||
|
||||
## Performance improvements
|
||||
|
||||
We have enabled several changes that aim to avoid allocating a new closure object in cases where it's not necessary to. This is helpful in cases where many closures are being allocated; in our benchmark suite, the two benchmarks that allocate a large number of closures improved by 15% and 5%, respectively.
|
||||
|
||||
When checking union types, we now try possibilities whose synthetic names match. This will speed up type checking unions in cases where synthetic names are populated.
|
||||
|
||||
We have also enabled an optimization that shares state in a hot path on the type checker. This will improve type checking performance.
|
||||
|
||||
The Luau VM now attempts to cache the length of tables' array portion. This change showed a small performance improvement in benchmarks, and should speed up `#` expressions.
|
||||
|
||||
The Luau type checker now caches a specific category of table unification results. This can improve type checking performance significantly when the same set of types is used frequently.
|
||||
|
||||
When Luau is not retaining type graphs, the type checker now discards more of a module's type surface after type checking it. This improves memory usage significantly.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
We've fixed a bug where on ARM systems (mobile), packing negative numbers using unsigned formats in `string.pack` would produce the wrong result.
|
||||
|
||||
We've fixed an issue with type aliases that reuse generic type names that caused them to be instantiated incorrectly.
|
||||
|
||||
We've corrected a subtle bug that could cause free types to leak into a table type when a free table is bound to that table.
|
||||
|
||||
We've fixed an issue that could cause Luau to report an infinitely recursive type error when the type was not infinitely recursive.
|
|
@ -38,7 +38,7 @@ feature_row3:
|
|||
-
|
||||
title: Analysis
|
||||
excerpt: >
|
||||
To make it easier to write correct code, Luau comes with a set of analysis tools that can surface common mistakes. These consist of a linter and a type checker, colloquially known as script analysis, and can be used from [Roblox Studio](https://developer.roblox.com/en-us/articles/The-Script-Analysis-Tool). The linting passes are [described here](lint), and the type checking user guide can [be found here](typecheck).
|
||||
To make it easier to write correct code, Luau comes with a set of analysis tools that can surface common mistakes. These consist of a linter and a type checker, colloquially known as script analysis, and are integrated into `luau-analyze` command line executable. The linting passes are [described here](lint), and the type checking user guide can [be found here](typecheck).
|
||||
|
||||
-
|
||||
title: Performance
|
||||
|
|
|
@ -23,9 +23,11 @@ const bool kFuzzCompiler = true;
|
|||
const bool kFuzzLinter = true;
|
||||
const bool kFuzzTypeck = true;
|
||||
const bool kFuzzVM = true;
|
||||
const bool kFuzzTypes = true;
|
||||
const bool kFuzzTranspile = true;
|
||||
|
||||
// Should we generate type annotations?
|
||||
const bool kFuzzTypes = true;
|
||||
|
||||
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
|
||||
|
||||
std::string protoprint(const luau::StatBlock& stat, bool types);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
Add bit32.countlz (count left zeroes) and bit32.countrz (count right zeroes) to accelerate bit scanning
|
||||
|
||||
**Status**: Implemented
|
||||
|
||||
## Motivation
|
||||
|
||||
All CPUs have instructions to determine the position of first/last set bit in an integer. These instructions have a variety of uses, the popular ones being:
|
||||
|
|
|
@ -48,9 +48,12 @@ type A<T, U = V, V = T> = ... -- not allowed
|
|||
|
||||
Default type parameter values are also allowed for type packs:
|
||||
```lua
|
||||
type A<T, U... = ...string> -- ok, variadic type pack
|
||||
type C<T, U... = string> -- ok, type pack with one element
|
||||
type D<T..., U... = T...> -- ok
|
||||
type A<T, U... = ...string> -- ok, variadic type pack
|
||||
type B<T, U... = ()> -- ok, type pack with no elements
|
||||
type C<T, U... = (string)> -- ok, type pack with one element
|
||||
type D<T, U... = (string, number)> -- ok, type pack with two elements
|
||||
type E<T, U... = (string, ...number)> -- ok, variadic type pack with a different first element
|
||||
type F<T..., U... = T...> -- ok, same type pack as T...
|
||||
```
|
||||
|
||||
---
|
||||
|
@ -68,7 +71,7 @@ If all type parameters have a default type value, it is now possible to referenc
|
|||
type All<T = string, U = number> = ...
|
||||
|
||||
local a: All -- ok
|
||||
local b: All<> -- not allowed
|
||||
local b: All<> -- ok as well
|
||||
```
|
||||
|
||||
If type is exported from a module, default type parameter values will still be available when module is imported.
|
||||
|
|
142
rfcs/syntax-string-interpolation.md
Normal file
142
rfcs/syntax-string-interpolation.md
Normal file
|
@ -0,0 +1,142 @@
|
|||
# String interpolation
|
||||
|
||||
## Summary
|
||||
|
||||
New string interpolation syntax.
|
||||
|
||||
## Motivation
|
||||
|
||||
The problems with `string.format` are many.
|
||||
|
||||
1. Must be exact about the types and its corresponding value.
|
||||
2. Using `%d` is the idiomatic default for most people, but this loses precision.
|
||||
* `%d` casts the number into `long long`, which has a lower max value than `double` and does not support decimals.
|
||||
* `%f` by default will format to the millionths, e.g. `5.5` is `5.500000`.
|
||||
* `%g` by default will format up to the hundred thousandths, e.g. `5.5` is `5.5` and `5.5312389` is `5.53123`. It will also convert the number to scientific notation when it encounters a number equal to or greater than 10^6.
|
||||
* To not lose too much precision, you need to use `%s`, but even so the type checker assumes you actually wanted strings.
|
||||
3. No support for `boolean`. You must use `%s` **and** call `tostring`.
|
||||
4. No support for values implementing the `__tostring` metamethod.
|
||||
5. Using `%` is in itself a dangerous operation within `string.format`.
|
||||
* `"Your health is %d% so you need to heal up."` causes a runtime error because `% so` is actually parsed as `(%s)o` and now requires a corresponding string.
|
||||
6. Having to use parentheses around string literals just to call a method of it.
|
||||
|
||||
## Design
|
||||
|
||||
To fix all of those issues, we need to do a few things.
|
||||
|
||||
1. A new string interpolation expression (fixes #5, #6)
|
||||
2. Extend `string.format` to accept values of arbitrary types (fixes #1, #2, #3, #4)
|
||||
|
||||
Because we care about backward compatibility, we need some new syntax in order to not change the meaning of existing strings. There are a few components of this new expression:
|
||||
|
||||
1. A string chunk (`` `...{ ``, `}...{`, and `` }...` ``) where `...` is a range of 0 to many characters.
|
||||
* `\` escapes `` ` ``, `{`, and itself `\`.
|
||||
* Restriction: the string interpolation literal must have at least one value to interpolate. We do not need 3 ways to express a single line string literal.
|
||||
* The pairs must be on the same line (unless a `\` escapes the newline) but expressions needn't be on the same line.
|
||||
2. An expression between the braces. This is the value that will be interpolated into the string.
|
||||
3. Formatting specification may follow after the expression, delimited by an unambiguous character.
|
||||
* Restriction: the formatting specification must be constant at parse time.
|
||||
* In the absence of an explicit formatting specification, the `%*` token will be used.
|
||||
* For now, we explicitly reject any formatting specification syntax. A future extension may be introduced to extend the syntax with an optional specification.
|
||||
|
||||
To put the above into formal EBNF grammar:
|
||||
|
||||
```
|
||||
stringinterp ::= <INTERP_BEGIN> exp {<INTERP_MID> exp} <INTERP_END>
|
||||
```
|
||||
|
||||
Which, in actual Luau code, will look like the following:
|
||||
|
||||
```
|
||||
local world = "world"
|
||||
print(`Hello {world}!`)
|
||||
--> Hello world!
|
||||
|
||||
local combo = {5, 2, 8, 9}
|
||||
print(`The lock combinations are: {table.concat(combo, ", ")}`)
|
||||
--> The lock combinations are: 5, 2, 8, 9
|
||||
|
||||
local set1 = Set.new({0, 1, 3})
|
||||
local set2 = Set.new({0, 5, 4})
|
||||
print(`{set1} ∪ {set2} = {Set.union(set1, set2)}`)
|
||||
--> {0, 1, 3} ∪ {0, 5, 4} = {0, 1, 3, 4, 5}
|
||||
|
||||
-- For illustrative purposes. These are illegal specifically because they don't interpolate anything.
|
||||
print(`Some example escaping the braces \{like so}`)
|
||||
print(`backslash \ that escapes the space is not a part of the string...`)
|
||||
print(`backslash \\ will escape the second backslash...`)
|
||||
print(`Some text that also includes \`...`)
|
||||
--> Some example escaping the braces {like so}
|
||||
--> backslash that escapes the space is not a part of the string...
|
||||
--> backslash \ will escape the second backslash...
|
||||
--> Some text that also includes `...
|
||||
```
|
||||
|
||||
As for how newlines are handled, they are handled the same as other string literals. Any text between the `{}` delimiters are not considered part of the string, hence newlines are OK. The main thing is that one opening pair will scan until either a closing pair is encountered, or an unescaped newline.
|
||||
|
||||
```
|
||||
local name = "Luau"
|
||||
|
||||
print(`Welcome to {
|
||||
name
|
||||
}!`)
|
||||
--> Welcome to Luau!
|
||||
|
||||
print(`Welcome to \
|
||||
{name}!`)
|
||||
--> Welcome to
|
||||
-- Luau!
|
||||
```
|
||||
|
||||
This expression will not be allowed to come after a `prefixexp`. I believe this is fully additive, so a future RFC may allow this. So for now, we explicitly reject the following:
|
||||
|
||||
```
|
||||
local name = "world"
|
||||
print`Hello {name}`
|
||||
```
|
||||
|
||||
Since the string interpolation expression is going to be lowered into a `string.format` call, we'll also need to extend `string.format`. The bare minimum to support the lowering is to add a new token whose definition is to perform a `tostring` call. `%*` is currently an invalid token, so this is a backward compatible extension. This RFC shall define `%*` to have the same behavior as if `tostring` was called.
|
||||
|
||||
```lua
|
||||
print(string.format("%* %*", 1, 2))
|
||||
--> 1 2
|
||||
```
|
||||
|
||||
The offset must always be within bound of the numbers of values passed to `string.format`.
|
||||
|
||||
```lua
|
||||
local function return_one_thing() return "hi" end
|
||||
local function return_two_nils() return nil, nil end
|
||||
|
||||
print(string.format("%*", return_one_thing()))
|
||||
--> "hi"
|
||||
|
||||
print(string.format("%*", Set.new({1, 2, 3})))
|
||||
--> {1, 2, 3}
|
||||
|
||||
print(string.format("%* %*", return_two_nils()))
|
||||
--> nil nil
|
||||
|
||||
print(string.format("%* %* %*", return_two_nils()))
|
||||
--> error: value #3 is missing, got 2
|
||||
```
|
||||
|
||||
## Drawbacks
|
||||
|
||||
If we want to use backticks for other purposes, it may introduce some potential ambiguity. One option to solve that is to only ever produce string interpolation tokens from the context of an expression. This is messy but doable because the parser and the lexer are already implemented to work in tandem. The other option is to pick a different delimiter syntax to keep backticks available for use in the future.
|
||||
|
||||
If we were to naively compile the expression into a `string.format` call, then implementation details would be observable if you write `` `Your health is {hp}% so you need to heal up.` ``. When lowering the expression, we would need to implicitly insert a `%` character anytime one shows up in a string interpolation token. Otherwise attempting to run this will produce a runtime error where the `%s` token is missing its corresponding string value.
|
||||
|
||||
## Alternatives
|
||||
|
||||
Rather than coming up with a new syntax (which doesn't help issue #5 and #6) and extending `string.format` to accept an extra token, we could just make `%s` call `tostring` and be done. However, doing so would cause programs to be more lenient and the type checker would have no way to infer strings from a `string.format` call. To preserve that, we would need a different token anyway.
|
||||
|
||||
Language | Syntax | Conclusion
|
||||
----------:|:----------------------|:-----------
|
||||
Python | `f'Hello {name}'` | Rejected because it's ambiguous with function call syntax.
|
||||
Swift | `"Hello \(name)"` | Rejected because it changes the meaning of existing strings.
|
||||
Ruby | `"Hello #{name}"` | Rejected because it changes the meaning of existing strings.
|
||||
JavaScript | `` `Hello ${name}` `` | Viable option as long as we don't intend to use backticks for other purposes.
|
||||
C# | `$"Hello {name}"` | Viable option and guarantees no ambiguities with future syntax.
|
||||
|
||||
This leaves us with only two syntax that already exists in other programming languages. The current proposal are for backticks, so the only backward compatible alternative are `$""` literals. We don't necessarily need to use `$` symbol here, but if we were to choose a different symbol, `#` cannot be used. I picked backticks because it doesn't require us to add a stack of closing delimiters in the lexer to make sure each nested string interpolation literals are correctly closed with its opening pair. You only have to count them.
|
|
@ -1,5 +1,7 @@
|
|||
# Type alias type packs
|
||||
|
||||
**Status**: Implemented
|
||||
|
||||
## Summary
|
||||
|
||||
Provide semantics for referencing type packs inside the body of a type alias declaration
|
||||
|
|
|
@ -78,3 +78,26 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_fn")
|
|||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("AstQuery");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type")
|
||||
{
|
||||
ScopedFastFlag luauTailArgumentTypeInfo{"LuauTailArgumentTypeInfo", true};
|
||||
|
||||
check(R"(
|
||||
local function foo() return 2 end
|
||||
local function bar(a: number) return -a end
|
||||
bar(foo())
|
||||
)");
|
||||
|
||||
auto oty = findTypeAtPosition(Position(3, 7));
|
||||
REQUIRE(oty);
|
||||
CHECK_EQ("number", toString(*oty));
|
||||
|
||||
auto expectedOty = findExpectedTypeAtPosition(Position(3, 7));
|
||||
REQUIRE(expectedOty);
|
||||
CHECK_EQ("number", toString(*expectedOty));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -513,8 +513,6 @@ TEST_CASE_FIXTURE(ACFixture, "dont_offer_any_suggestions_from_the_end_of_a_comme
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "dont_offer_any_suggestions_from_within_a_broken_comment")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCaptureBrokenCommentSpans", true};
|
||||
|
||||
check(R"(
|
||||
--[[ @1
|
||||
)");
|
||||
|
@ -526,8 +524,6 @@ TEST_CASE_FIXTURE(ACFixture, "dont_offer_any_suggestions_from_within_a_broken_co
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "dont_offer_any_suggestions_from_within_a_broken_comment_at_the_very_end_of_the_file")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCaptureBrokenCommentSpans", true};
|
||||
|
||||
check("--[[@1");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
@ -1935,6 +1931,39 @@ return target(b@1
|
|||
CHECK(ac.entryMap["bar2"].typeCorrect == TypeCorrectKind::None);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses")
|
||||
{
|
||||
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
|
||||
ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true);
|
||||
|
||||
check(R"(
|
||||
local function bar(a: number) return -a end
|
||||
local abc = b@1
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
CHECK(ac.entryMap.count("bar"));
|
||||
CHECK(ac.entryMap["bar"].parens == ParenthesesRecommendation::CursorInside);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "function_result_passed_to_function_has_parentheses")
|
||||
{
|
||||
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
|
||||
ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true);
|
||||
|
||||
check(R"(
|
||||
local function foo() return 1 end
|
||||
local function bar(a: number) return -a end
|
||||
local abc = bar(@1)
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
CHECK(ac.entryMap.count("foo"));
|
||||
CHECK(ac.entryMap["foo"].parens == ParenthesesRecommendation::CursorAfter);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "type_correct_sealed_table")
|
||||
{
|
||||
check(R"(
|
||||
|
@ -2210,8 +2239,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource")
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocompleteSource_require")
|
||||
{
|
||||
ScopedFastFlag luauResolveModuleNameWithoutACurrentModule("LuauResolveModuleNameWithoutACurrentModule", true);
|
||||
|
||||
std::string_view source = R"(
|
||||
local a = require(w -- Line 1
|
||||
-- | Column 27
|
||||
|
@ -2287,8 +2314,6 @@ until
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "if_then_else_elseif_completions")
|
||||
{
|
||||
ScopedFastFlag sff{"ElseElseIfCompletionImprovements", true};
|
||||
|
||||
check(R"(
|
||||
local elsewhere = false
|
||||
|
||||
|
@ -2585,9 +2610,6 @@ a = if temp then even elseif true then temp else e@9
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
check(R"(
|
||||
type A<T...> = () -> T...
|
||||
local a: A<(number, s@1>
|
||||
|
@ -2599,4 +2621,55 @@ local a: A<(number, s@1>
|
|||
CHECK(ac.entryMap.count("string"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_first_function_arg_expected_type")
|
||||
{
|
||||
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
|
||||
ScopedFastFlag luauAutocompleteFirstArg("LuauAutocompleteFirstArg", true);
|
||||
|
||||
check(R"(
|
||||
local function foo1() return 1 end
|
||||
local function foo2() return "1" end
|
||||
|
||||
local function bar0() return "got" .. a end
|
||||
local function bar1(a: number) return "got " .. a end
|
||||
local function bar2(a: number, b: string) return "got " .. a .. b end
|
||||
|
||||
local t = {}
|
||||
function t:bar1(a: number) return "got " .. a end
|
||||
|
||||
local r1 = bar0(@1)
|
||||
local r2 = bar1(@2)
|
||||
local r3 = bar2(@3)
|
||||
local r4 = t:bar1(@4)
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count("foo1"));
|
||||
CHECK(ac.entryMap["foo1"].typeCorrect == TypeCorrectKind::None);
|
||||
REQUIRE(ac.entryMap.count("foo2"));
|
||||
CHECK(ac.entryMap["foo2"].typeCorrect == TypeCorrectKind::None);
|
||||
|
||||
ac = autocomplete('2');
|
||||
|
||||
REQUIRE(ac.entryMap.count("foo1"));
|
||||
CHECK(ac.entryMap["foo1"].typeCorrect == TypeCorrectKind::CorrectFunctionResult);
|
||||
REQUIRE(ac.entryMap.count("foo2"));
|
||||
CHECK(ac.entryMap["foo2"].typeCorrect == TypeCorrectKind::None);
|
||||
|
||||
ac = autocomplete('3');
|
||||
|
||||
REQUIRE(ac.entryMap.count("foo1"));
|
||||
CHECK(ac.entryMap["foo1"].typeCorrect == TypeCorrectKind::CorrectFunctionResult);
|
||||
REQUIRE(ac.entryMap.count("foo2"));
|
||||
CHECK(ac.entryMap["foo2"].typeCorrect == TypeCorrectKind::None);
|
||||
|
||||
ac = autocomplete('4');
|
||||
|
||||
REQUIRE(ac.entryMap.count("foo1"));
|
||||
CHECK(ac.entryMap["foo1"].typeCorrect == TypeCorrectKind::CorrectFunctionResult);
|
||||
REQUIRE(ac.entryMap.count("foo2"));
|
||||
CHECK(ac.entryMap["foo2"].typeCorrect == TypeCorrectKind::None);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -10,9 +10,6 @@
|
|||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
LUAU_FASTFLAG(LuauPreloadClosures)
|
||||
LUAU_FASTFLAG(LuauGenericSpecialGlobals)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
static std::string compileFunction(const char* source, uint32_t id)
|
||||
|
@ -74,20 +71,10 @@ TEST_CASE("BasicFunction")
|
|||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
||||
Luau::compileOrThrow(bcb, "local function foo(a, b) return b end");
|
||||
|
||||
if (FFlag::LuauPreloadClosures)
|
||||
{
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
|
||||
DUPCLOSURE R0 K0
|
||||
RETURN R0 0
|
||||
)");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
|
||||
NEWCLOSURE R0 P0
|
||||
RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
RETURN R1 1
|
||||
|
@ -1057,6 +1044,18 @@ RETURN R0 1
|
|||
CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"(
|
||||
LOADN R0 20
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// codegen for a true constant condition with non-constant expressions
|
||||
CHECK_EQ("\n" + compileFunction0("return if true then {} else error()"), R"(
|
||||
NEWTABLE R0 0 0
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// codegen for a false constant condition with non-constant expressions
|
||||
CHECK_EQ("\n" + compileFunction0("return if false then error() else {}"), R"(
|
||||
NEWTABLE R0 0 0
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// codegen for a false (in this case 'nil') constant condition
|
||||
|
@ -2360,6 +2359,58 @@ Foo:Bar(
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugLineInfoCallChain")
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
|
||||
Luau::compileOrThrow(bcb, R"(
|
||||
local Foo = ...
|
||||
|
||||
Foo
|
||||
:Bar(1)
|
||||
:Baz(2)
|
||||
.Qux(3)
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
2: GETVARARGS R0 1
|
||||
5: LOADN R4 1
|
||||
5: NAMECALL R2 R0 K0
|
||||
5: CALL R2 2 1
|
||||
6: LOADN R4 2
|
||||
6: NAMECALL R2 R2 K1
|
||||
6: CALL R2 2 1
|
||||
7: GETTABLEKS R1 R2 K2
|
||||
7: LOADN R2 3
|
||||
7: CALL R1 1 0
|
||||
8: RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugLineInfoFastCall")
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
|
||||
Luau::compileOrThrow(bcb, R"(
|
||||
local Foo, Bar = ...
|
||||
|
||||
return
|
||||
math.max(
|
||||
Foo,
|
||||
Bar)
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
2: GETVARARGS R0 2
|
||||
5: FASTCALL2 18 R0 R1 +5
|
||||
5: MOVE R3 R0
|
||||
5: MOVE R4 R1
|
||||
5: GETIMPORT R2 2
|
||||
5: CALL R2 2 -1
|
||||
5: RETURN R2 -1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugSource")
|
||||
{
|
||||
const char* source = R"(
|
||||
|
@ -2795,47 +2846,35 @@ CAPTURE UPVAL U1
|
|||
RETURN R0 1
|
||||
)");
|
||||
|
||||
if (FFlag::LuauPreloadClosures)
|
||||
{
|
||||
// recursive capture
|
||||
CHECK_EQ("\n" + compileFunction("local function foo() return foo() end", 1), R"(
|
||||
// recursive capture
|
||||
CHECK_EQ("\n" + compileFunction("local function foo() return foo() end", 1), R"(
|
||||
DUPCLOSURE R0 K0
|
||||
CAPTURE VAL R0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
// multi-level recursive capture
|
||||
CHECK_EQ("\n" + compileFunction("local function foo() return function() return foo() end end", 1), R"(
|
||||
// multi-level recursive capture
|
||||
CHECK_EQ("\n" + compileFunction("local function foo() return function() return foo() end end", 1), R"(
|
||||
DUPCLOSURE R0 K0
|
||||
CAPTURE UPVAL U0
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
// multi-level recursive capture where function isn't top-level
|
||||
// note: this should probably be optimized to DUPCLOSURE but doing that requires a different upval tracking flow in the compiler
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
// multi-level recursive capture where function isn't top-level
|
||||
// note: this should probably be optimized to DUPCLOSURE but doing that requires a different upval tracking flow in the compiler
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local function foo()
|
||||
local function bar()
|
||||
return function() return bar() end
|
||||
end
|
||||
end
|
||||
)",
|
||||
1),
|
||||
R"(
|
||||
1),
|
||||
R"(
|
||||
NEWCLOSURE R0 P0
|
||||
CAPTURE UPVAL U0
|
||||
RETURN R0 1
|
||||
)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// recursive capture
|
||||
CHECK_EQ("\n" + compileFunction("local function foo() return foo() end", 1), R"(
|
||||
NEWCLOSURE R0 P0
|
||||
CAPTURE VAL R0
|
||||
RETURN R0 0
|
||||
)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("OutOfLocals")
|
||||
|
@ -3440,8 +3479,6 @@ local t = {
|
|||
|
||||
TEST_CASE("ConstantClosure")
|
||||
{
|
||||
ScopedFastFlag sff("LuauPreloadClosures", true);
|
||||
|
||||
// closures without upvalues are created when bytecode is loaded
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
return function() end
|
||||
|
@ -3506,8 +3543,6 @@ RETURN R0 1
|
|||
|
||||
TEST_CASE("SharedClosure")
|
||||
{
|
||||
ScopedFastFlag sff1("LuauPreloadClosures", true);
|
||||
|
||||
// closures can be shared even if functions refer to upvalues, as long as upvalues are top-level
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local val = ...
|
||||
|
@ -3742,4 +3777,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();
|
||||
|
|
|
@ -78,38 +78,31 @@ static int lua_vector(lua_State* L)
|
|||
|
||||
static int lua_vector_dot(lua_State* L)
|
||||
{
|
||||
const float* a = lua_tovector(L, 1);
|
||||
const float* b = lua_tovector(L, 2);
|
||||
const float* a = luaL_checkvector(L, 1);
|
||||
const float* b = luaL_checkvector(L, 2);
|
||||
|
||||
if (a && b)
|
||||
{
|
||||
lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw std::runtime_error("invalid arguments to vector:Dot");
|
||||
lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int lua_vector_index(lua_State* L)
|
||||
{
|
||||
const float* v = luaL_checkvector(L, 1);
|
||||
const char* name = luaL_checkstring(L, 2);
|
||||
|
||||
if (const float* v = lua_tovector(L, 1))
|
||||
if (strcmp(name, "Magnitude") == 0)
|
||||
{
|
||||
if (strcmp(name, "Magnitude") == 0)
|
||||
{
|
||||
lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(name, "Dot") == 0)
|
||||
{
|
||||
lua_pushcfunction(L, lua_vector_dot, "Dot");
|
||||
return 1;
|
||||
}
|
||||
lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw std::runtime_error(Luau::format("%s is not a valid member of vector", name));
|
||||
if (strcmp(name, "Dot") == 0)
|
||||
{
|
||||
lua_pushcfunction(L, lua_vector_dot, "Dot");
|
||||
return 1;
|
||||
}
|
||||
|
||||
luaL_error(L, "%s is not a valid member of vector", name);
|
||||
}
|
||||
|
||||
static int lua_vector_namecall(lua_State* L)
|
||||
|
@ -120,7 +113,7 @@ static int lua_vector_namecall(lua_State* L)
|
|||
return lua_vector_dot(L);
|
||||
}
|
||||
|
||||
throw std::runtime_error(Luau::format("%s is not a valid method of vector", luaL_checkstring(L, 1)));
|
||||
luaL_error(L, "%s is not a valid method of vector", luaL_checkstring(L, 1));
|
||||
}
|
||||
|
||||
int lua_silence(lua_State* L)
|
||||
|
@ -130,8 +123,8 @@ int lua_silence(lua_State* L)
|
|||
|
||||
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
|
||||
|
||||
static StateRef runConformance(
|
||||
const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr, lua_State* initialLuaState = nullptr)
|
||||
static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr,
|
||||
lua_State* initialLuaState = nullptr, lua_CompileOptions* copts = nullptr)
|
||||
{
|
||||
std::string path = __FILE__;
|
||||
path.erase(path.find_last_of("\\/"));
|
||||
|
@ -187,13 +180,8 @@ static StateRef runConformance(
|
|||
|
||||
std::string chunkname = "=" + std::string(name);
|
||||
|
||||
lua_CompileOptions copts = {};
|
||||
copts.optimizationLevel = 1; // default
|
||||
copts.debugLevel = 2; // for debugger tests
|
||||
copts.vectorCtor = "vector"; // for vector tests
|
||||
|
||||
size_t bytecodeSize = 0;
|
||||
char* bytecode = luau_compile(source.data(), source.size(), &copts, &bytecodeSize);
|
||||
char* bytecode = luau_compile(source.data(), source.size(), copts, &bytecodeSize);
|
||||
int result = luau_load(L, chunkname.c_str(), bytecode, bytecodeSize, 0);
|
||||
free(bytecode);
|
||||
|
||||
|
@ -380,29 +368,37 @@ TEST_CASE("Vector")
|
|||
{
|
||||
ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true};
|
||||
|
||||
runConformance("vector.lua", [](lua_State* L) {
|
||||
lua_pushcfunction(L, lua_vector, "vector");
|
||||
lua_setglobal(L, "vector");
|
||||
lua_CompileOptions copts = {};
|
||||
copts.optimizationLevel = 1;
|
||||
copts.debugLevel = 1;
|
||||
copts.vectorCtor = "vector";
|
||||
|
||||
runConformance(
|
||||
"vector.lua",
|
||||
[](lua_State* L) {
|
||||
lua_pushcfunction(L, lua_vector, "vector");
|
||||
lua_setglobal(L, "vector");
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
#else
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
|
||||
#endif
|
||||
luaL_newmetatable(L, "vector");
|
||||
luaL_newmetatable(L, "vector");
|
||||
|
||||
lua_pushstring(L, "__index");
|
||||
lua_pushcfunction(L, lua_vector_index, nullptr);
|
||||
lua_settable(L, -3);
|
||||
lua_pushstring(L, "__index");
|
||||
lua_pushcfunction(L, lua_vector_index, nullptr);
|
||||
lua_settable(L, -3);
|
||||
|
||||
lua_pushstring(L, "__namecall");
|
||||
lua_pushcfunction(L, lua_vector_namecall, nullptr);
|
||||
lua_settable(L, -3);
|
||||
lua_pushstring(L, "__namecall");
|
||||
lua_pushcfunction(L, lua_vector_namecall, nullptr);
|
||||
lua_settable(L, -3);
|
||||
|
||||
lua_setreadonly(L, -1, true);
|
||||
lua_setmetatable(L, -2);
|
||||
lua_pop(L, 1);
|
||||
});
|
||||
lua_setreadonly(L, -1, true);
|
||||
lua_setmetatable(L, -2);
|
||||
lua_pop(L, 1);
|
||||
},
|
||||
nullptr, nullptr, &copts);
|
||||
}
|
||||
|
||||
static void populateRTTI(lua_State* L, Luau::TypeId type)
|
||||
|
@ -506,6 +502,10 @@ TEST_CASE("Debugger")
|
|||
breakhits = 0;
|
||||
interruptedthread = nullptr;
|
||||
|
||||
lua_CompileOptions copts = {};
|
||||
copts.optimizationLevel = 1;
|
||||
copts.debugLevel = 2;
|
||||
|
||||
runConformance(
|
||||
"debugger.lua",
|
||||
[](lua_State* L) {
|
||||
|
@ -515,6 +515,9 @@ TEST_CASE("Debugger")
|
|||
cb->debugbreak = [](lua_State* L, lua_Debug* ar) {
|
||||
breakhits++;
|
||||
|
||||
// make sure we can trace the stack for every breakpoint we hit
|
||||
lua_debugtrace(L);
|
||||
|
||||
// for every breakpoint, we break on the first invocation and continue on second
|
||||
// this allows us to easily step off breakpoints
|
||||
// (real implementaiton may require singlestepping)
|
||||
|
@ -618,7 +621,8 @@ TEST_CASE("Debugger")
|
|||
lua_resume(interruptedthread, nullptr, 0);
|
||||
interruptedthread = nullptr;
|
||||
}
|
||||
});
|
||||
},
|
||||
nullptr, &copts);
|
||||
|
||||
CHECK(breakhits == 10); // 2 hits per breakpoint
|
||||
}
|
||||
|
@ -710,21 +714,52 @@ TEST_CASE("ApiFunctionCalls")
|
|||
StateRef globalState = runConformance("apicalls.lua");
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_call(L, 2, 1);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
// lua_call
|
||||
{
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_call(L, 2, 1);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_pcall(L, 2, 1, 0);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
// lua_pcall
|
||||
{
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "add");
|
||||
lua_pushnumber(L, 40);
|
||||
lua_pushnumber(L, 2);
|
||||
lua_pcall(L, 2, 1, 0);
|
||||
CHECK(lua_isnumber(L, -1));
|
||||
CHECK(lua_tonumber(L, -1) == 42);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// lua_equal with a sleeping thread wake up
|
||||
{
|
||||
ScopedFastFlag luauActivateBeforeExec("LuauActivateBeforeExec", true);
|
||||
|
||||
lua_State* L2 = lua_newthread(L);
|
||||
|
||||
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
|
||||
lua_pushnumber(L2, 42);
|
||||
lua_pcall(L2, 1, 1, 0);
|
||||
|
||||
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
|
||||
lua_pushnumber(L2, 42);
|
||||
lua_pcall(L2, 1, 1, 0);
|
||||
|
||||
// Reset GC
|
||||
lua_gc(L2, LUA_GCCOLLECT, 0);
|
||||
|
||||
// Try to mark 'L2' as sleeping
|
||||
// Can't control GC precisely, even in tests
|
||||
lua_gc(L2, LUA_GCSTEP, 8);
|
||||
|
||||
CHECK(lua_equal(L2, -1, -2) == 1);
|
||||
lua_pop(L2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
static bool endsWith(const std::string& str, const std::string& suffix)
|
||||
|
@ -738,8 +773,6 @@ static bool endsWith(const std::string& str, const std::string& suffix)
|
|||
#if !LUA_USE_LONGJMP
|
||||
TEST_CASE("ExceptionObject")
|
||||
{
|
||||
ScopedFastFlag sff("LuauExceptionMessageFix", true);
|
||||
|
||||
struct ExceptionResult
|
||||
{
|
||||
bool exceptionGenerated;
|
||||
|
@ -838,4 +871,46 @@ TEST_CASE("TagMethodError")
|
|||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Coverage")
|
||||
{
|
||||
lua_CompileOptions copts = {};
|
||||
copts.optimizationLevel = 1;
|
||||
copts.debugLevel = 1;
|
||||
copts.coverageLevel = 2;
|
||||
|
||||
runConformance(
|
||||
"coverage.lua",
|
||||
[](lua_State* L) {
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) -> int {
|
||||
luaL_argexpected(L, lua_isLfunction(L, 1), 1, "function");
|
||||
|
||||
lua_newtable(L);
|
||||
lua_getcoverage(L, 1, L, [](void* context, const char* function, int linedefined, int depth, const int* hits, size_t size) {
|
||||
lua_State* L = static_cast<lua_State*>(context);
|
||||
|
||||
lua_newtable(L);
|
||||
|
||||
lua_pushstring(L, function);
|
||||
lua_setfield(L, -2, "name");
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
if (hits[i] != -1)
|
||||
{
|
||||
lua_pushinteger(L, hits[i]);
|
||||
lua_rawseti(L, -2, int(i));
|
||||
}
|
||||
|
||||
lua_rawseti(L, -2, lua_objlen(L, -2) + 1);
|
||||
});
|
||||
|
||||
return 1;
|
||||
},
|
||||
"getcoverage");
|
||||
lua_setglobal(L, "getcoverage");
|
||||
},
|
||||
nullptr, nullptr, &copts);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -19,19 +19,6 @@ static const char* mainModuleName = "MainModule";
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
std::optional<ModuleName> TestFileResolver::fromAstFragment(AstExpr* expr) const
|
||||
{
|
||||
auto g = expr->as<AstExprGlobal>();
|
||||
if (!g)
|
||||
return std::nullopt;
|
||||
|
||||
std::string_view value = g->name.value;
|
||||
if (value == "game" || value == "Game" || value == "workspace" || value == "Workspace" || value == "script" || value == "Script")
|
||||
return ModuleName(value);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* context, AstExpr* expr)
|
||||
{
|
||||
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
|
||||
|
@ -81,24 +68,6 @@ std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* cont
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
ModuleName TestFileResolver::concat(const ModuleName& lhs, std::string_view rhs) const
|
||||
{
|
||||
return lhs + "/" + ModuleName(rhs);
|
||||
}
|
||||
|
||||
std::optional<ModuleName> TestFileResolver::getParentModuleName(const ModuleName& name) const
|
||||
{
|
||||
std::string_view view = name;
|
||||
const size_t lastSeparatorIndex = view.find_last_of('/');
|
||||
|
||||
if (lastSeparatorIndex != std::string_view::npos)
|
||||
{
|
||||
return ModuleName(view.substr(0, lastSeparatorIndex));
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string TestFileResolver::getHumanReadableModuleName(const ModuleName& name) const
|
||||
{
|
||||
return name;
|
||||
|
@ -324,6 +293,13 @@ std::optional<TypeId> Fixture::findTypeAtPosition(Position position)
|
|||
return Luau::findTypeAtPosition(*module, *sourceModule, position);
|
||||
}
|
||||
|
||||
std::optional<TypeId> Fixture::findExpectedTypeAtPosition(Position position)
|
||||
{
|
||||
ModulePtr module = getMainModule();
|
||||
SourceModule* sourceModule = getMainSourceModule();
|
||||
return Luau::findExpectedTypeAtPosition(*module, *sourceModule, position);
|
||||
}
|
||||
|
||||
TypeId Fixture::requireTypeAtPosition(Position position)
|
||||
{
|
||||
auto ty = findTypeAtPosition(position);
|
||||
|
|
|
@ -64,12 +64,8 @@ struct TestFileResolver
|
|||
return SourceCode{it->second, sourceType};
|
||||
}
|
||||
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override;
|
||||
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override;
|
||||
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override;
|
||||
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override;
|
||||
|
||||
std::string getHumanReadableModuleName(const ModuleName& name) const override;
|
||||
|
||||
std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const override;
|
||||
|
@ -126,6 +122,7 @@ struct Fixture
|
|||
|
||||
std::optional<TypeId> findTypeAtPosition(Position position);
|
||||
TypeId requireTypeAtPosition(Position position);
|
||||
std::optional<TypeId> findExpectedTypeAtPosition(Position position);
|
||||
|
||||
std::optional<TypeId> lookupType(const std::string& name);
|
||||
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
|
||||
|
|
|
@ -46,18 +46,6 @@ NaiveModuleResolver naiveModuleResolver;
|
|||
|
||||
struct NaiveFileResolver : NullFileResolver
|
||||
{
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
|
||||
{
|
||||
AstExprGlobal* g = expr->as<AstExprGlobal>();
|
||||
if (g && g->name == "Modules")
|
||||
return "Modules";
|
||||
|
||||
if (g && g->name == "game")
|
||||
return "game";
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override
|
||||
{
|
||||
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
|
||||
|
@ -86,11 +74,6 @@ struct NaiveFileResolver : NullFileResolver
|
|||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs + "/" + ModuleName(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -1469,6 +1469,22 @@ _ = true and true or false -- no warning since this is is a common pattern used
|
|||
CHECK_EQ(result.warnings[6].location.begin.line + 1, 19);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsExpr")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
local correct, opaque = ...
|
||||
|
||||
if correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
|
||||
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
|
||||
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", false)}) then
|
||||
end
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 1);
|
||||
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 4");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line + 1, 5);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateLocal")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
|
|
|
@ -44,9 +44,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive")
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
// numberType is persistent. We leave it as-is.
|
||||
TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks);
|
||||
TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks, cloneState);
|
||||
CHECK_EQ(newNumber, typeChecker.numberType);
|
||||
}
|
||||
|
||||
|
@ -56,12 +57,13 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive")
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
// Create a new number type that isn't persistent
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldNumber = typeChecker.globalTypes.addType(PrimitiveTypeVar{PrimitiveTypeVar::Number});
|
||||
freeze(typeChecker.globalTypes);
|
||||
TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks);
|
||||
TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
CHECK_NE(newNumber, oldNumber);
|
||||
CHECK_EQ(*oldNumber, *newNumber);
|
||||
|
@ -89,9 +91,10 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
TypeArena dest;
|
||||
TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks);
|
||||
TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(counterCopy);
|
||||
REQUIRE(ttv != nullptr);
|
||||
|
@ -142,11 +145,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union")
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldUnion = typeChecker.globalTypes.addType(UnionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
|
||||
freeze(typeChecker.globalTypes);
|
||||
TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks);
|
||||
TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
CHECK_NE(newUnion, oldUnion);
|
||||
CHECK_EQ("number | string", toString(newUnion));
|
||||
|
@ -159,11 +163,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection")
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldIntersection = typeChecker.globalTypes.addType(IntersectionTypeVar{{typeChecker.numberType, typeChecker.stringType}});
|
||||
freeze(typeChecker.globalTypes);
|
||||
TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks);
|
||||
TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
CHECK_NE(newIntersection, oldIntersection);
|
||||
CHECK_EQ("number & string", toString(newIntersection));
|
||||
|
@ -188,8 +193,9 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
|||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks);
|
||||
TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks, cloneState);
|
||||
const ClassTypeVar* ctv = get<ClassTypeVar>(cloned);
|
||||
REQUIRE(ctv != nullptr);
|
||||
|
||||
|
@ -211,16 +217,16 @@ TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types")
|
|||
TypeArena dest;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
bool encounteredFreeType = false;
|
||||
TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, cloneState);
|
||||
CHECK_EQ("any", toString(clonedTy));
|
||||
CHECK(encounteredFreeType);
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
|
||||
encounteredFreeType = false;
|
||||
TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
cloneState = {};
|
||||
TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, cloneState);
|
||||
CHECK_EQ("...any", toString(clonedTp));
|
||||
CHECK(encounteredFreeType);
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
||||
|
@ -232,12 +238,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
|||
TypeArena dest;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
bool encounteredFreeType = false;
|
||||
TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, cloneState);
|
||||
const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned);
|
||||
CHECK_EQ(clonedTtv->state, TableState::Sealed);
|
||||
CHECK(encounteredFreeType);
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_self_property")
|
||||
|
@ -267,4 +273,34 @@ TEST_CASE_FIXTURE(Fixture, "clone_self_property")
|
|||
"dot or pass 1 extra nil to suppress this warning");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
{
|
||||
#if defined(_DEBUG) || defined(_NOOPT)
|
||||
int limit = 250;
|
||||
#else
|
||||
int limit = 400;
|
||||
#endif
|
||||
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
|
||||
|
||||
TypeArena src;
|
||||
|
||||
TypeId table = src.addType(TableTypeVar{});
|
||||
TypeId nested = table;
|
||||
|
||||
for (int i = 0; i < limit + 100; i++)
|
||||
{
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(nested);
|
||||
|
||||
ttv->props["a"].type = src.addType(TableTypeVar{});
|
||||
nested = ttv->props["a"].type;
|
||||
}
|
||||
|
||||
TypeArena dest;
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
CHECK_THROWS_AS(clone(table, dest, seenTypes, seenTypePacks, cloneState), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -2303,8 +2303,6 @@ TEST_CASE_FIXTURE(Fixture, "capture_comments")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "capture_broken_comment_at_the_start_of_the_file")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCaptureBrokenCommentSpans", true};
|
||||
|
||||
ParseOptions options;
|
||||
options.captureComments = true;
|
||||
|
||||
|
@ -2319,8 +2317,6 @@ TEST_CASE_FIXTURE(Fixture, "capture_broken_comment_at_the_start_of_the_file")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "capture_broken_comment")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCaptureBrokenCommentSpans", true};
|
||||
|
||||
ParseOptions options;
|
||||
options.captureComments = true;
|
||||
|
||||
|
@ -2518,8 +2514,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_if_else_expression")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_type_pack_type_parameters")
|
||||
{
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
AstStat* stat = parse(R"(
|
||||
type Packed<T...> = () -> T...
|
||||
|
||||
|
|
366
tests/ToDot.test.cpp
Normal file
366
tests/ToDot.test.cpp
Normal file
|
@ -0,0 +1,366 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/ToDot.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
struct ToDotClassFixture : Fixture
|
||||
{
|
||||
ToDotClassFixture()
|
||||
{
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
TypeId baseClassMetaType = arena.addType(TableTypeVar{});
|
||||
|
||||
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}});
|
||||
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
||||
{"BaseField", {typeChecker.numberType}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
|
||||
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}});
|
||||
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
|
||||
{"ChildField", {typeChecker.stringType}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("ToDot");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "primitive")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: nil
|
||||
local b: number
|
||||
local c: any
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_NE("nil", toDot(requireType("a")));
|
||||
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="number"];
|
||||
})",
|
||||
toDot(requireType("b")));
|
||||
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="any"];
|
||||
})",
|
||||
toDot(requireType("c")));
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
opts.duplicatePrimitives = false;
|
||||
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="PrimitiveTypeVar number"];
|
||||
})",
|
||||
toDot(requireType("b"), opts));
|
||||
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="AnyTypeVar 1"];
|
||||
})",
|
||||
toDot(requireType("c"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a = 444
|
||||
local b = a
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
std::optional<TypeId> ty = getType("b");
|
||||
REQUIRE(bool(ty));
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="BoundTypeVar 1"];
|
||||
n1 -> n2;
|
||||
n2 [label="number"];
|
||||
})",
|
||||
toDot(*ty, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function")
|
||||
{
|
||||
ScopedFastFlag luauQuantifyInPlace2{"LuauQuantifyInPlace2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(a, ...: string) return a end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="FunctionTypeVar 1"];
|
||||
n1 -> n2 [label="arg"];
|
||||
n2 [label="TypePack 2"];
|
||||
n2 -> n3;
|
||||
n3 [label="GenericTypeVar 3"];
|
||||
n2 -> n4 [label="tail"];
|
||||
n4 [label="VariadicTypePack 4"];
|
||||
n4 -> n5;
|
||||
n5 [label="string"];
|
||||
n1 -> n6 [label="ret"];
|
||||
n6 [label="BoundTypePack 6"];
|
||||
n6 -> n7;
|
||||
n7 [label="TypePack 7"];
|
||||
n7 -> n3;
|
||||
})",
|
||||
toDot(requireType("f"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: string | number
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="UnionTypeVar 1"];
|
||||
n1 -> n2;
|
||||
n2 [label="string"];
|
||||
n1 -> n3;
|
||||
n3 [label="number"];
|
||||
})",
|
||||
toDot(requireType("a"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: string & number -- uninhabited
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="IntersectionTypeVar 1"];
|
||||
n1 -> n2;
|
||||
n2 [label="string"];
|
||||
n1 -> n3;
|
||||
n3 [label="number"];
|
||||
})",
|
||||
toDot(requireType("a"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A<T, U...> = { x: T, y: (U...) -> (), [string]: any }
|
||||
local a: A<number, ...string>
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="TableTypeVar A"];
|
||||
n1 -> n2 [label="x"];
|
||||
n2 [label="number"];
|
||||
n1 -> n3 [label="y"];
|
||||
n3 [label="FunctionTypeVar 3"];
|
||||
n3 -> n4 [label="arg"];
|
||||
n4 [label="VariadicTypePack 4"];
|
||||
n4 -> n5;
|
||||
n5 [label="string"];
|
||||
n3 -> n6 [label="ret"];
|
||||
n6 [label="TypePack 6"];
|
||||
n1 -> n7 [label="[index]"];
|
||||
n7 [label="string"];
|
||||
n1 -> n8 [label="[value]"];
|
||||
n8 [label="any"];
|
||||
n1 -> n9 [label="typeParam"];
|
||||
n9 [label="number"];
|
||||
n1 -> n4 [label="typePackParam"];
|
||||
})",
|
||||
toDot(requireType("a"), opts));
|
||||
|
||||
// Extra coverage with pointers (unstable values)
|
||||
(void)toDot(requireType("a"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "metatable")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: typeof(setmetatable({}, {}))
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="MetatableTypeVar 1"];
|
||||
n1 -> n2 [label="table"];
|
||||
n2 [label="TableTypeVar 2"];
|
||||
n1 -> n3 [label="metatable"];
|
||||
n3 [label="TableTypeVar 3"];
|
||||
})",
|
||||
toDot(requireType("a"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free")
|
||||
{
|
||||
TypeVar type{TypeVariant{FreeTypeVar{TypeLevel{0, 0}}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="FreeTypeVar 1"];
|
||||
})",
|
||||
toDot(&type, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error")
|
||||
{
|
||||
TypeVar type{TypeVariant{ErrorTypeVar{}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="ErrorTypeVar 1"];
|
||||
})",
|
||||
toDot(&type, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic")
|
||||
{
|
||||
TypeVar type{TypeVariant{GenericTypeVar{"T"}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="GenericTypeVar T"];
|
||||
})",
|
||||
toDot(&type, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ToDotClassFixture, "class")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: ChildClass
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="ClassTypeVar ChildClass"];
|
||||
n1 -> n2 [label="ChildField"];
|
||||
n2 [label="string"];
|
||||
n1 -> n3 [label="[parent]"];
|
||||
n3 [label="ClassTypeVar BaseClass"];
|
||||
n3 -> n4 [label="BaseField"];
|
||||
n4 [label="number"];
|
||||
n3 -> n5 [label="[metatable]"];
|
||||
n5 [label="TableTypeVar 5"];
|
||||
})",
|
||||
toDot(requireType("a"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_pack")
|
||||
{
|
||||
TypePackVar pack{TypePackVariant{FreeTypePack{TypeLevel{0, 0}}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="FreeTypePack 1"];
|
||||
})",
|
||||
toDot(&pack, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_pack")
|
||||
{
|
||||
TypePackVar pack{TypePackVariant{Unifiable::Error{}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="ErrorTypePack 1"];
|
||||
})",
|
||||
toDot(&pack, opts));
|
||||
|
||||
// Extra coverage with pointers (unstable values)
|
||||
(void)toDot(&pack);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_pack")
|
||||
{
|
||||
TypePackVar pack1{TypePackVariant{GenericTypePack{}}};
|
||||
TypePackVar pack2{TypePackVariant{GenericTypePack{"T"}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="GenericTypePack 1"];
|
||||
})",
|
||||
toDot(&pack1, opts));
|
||||
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="GenericTypePack T"];
|
||||
})",
|
||||
toDot(&pack2, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_pack")
|
||||
{
|
||||
TypePackVar pack{TypePackVariant{TypePack{{typeChecker.numberType}, {}}}};
|
||||
TypePackVar bound{TypePackVariant{BoundTypePack{&pack}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="BoundTypePack 1"];
|
||||
n1 -> n2;
|
||||
n2 [label="TypePack 2"];
|
||||
n2 -> n3;
|
||||
n3 [label="number"];
|
||||
})",
|
||||
toDot(&bound, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a = {x=2}
|
||||
local b
|
||||
b.x = 2
|
||||
b = a
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
std::optional<TypeId> ty = getType("b");
|
||||
REQUIRE(bool(ty));
|
||||
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="TableTypeVar 1"];
|
||||
n1 -> n2 [label="boundTo"];
|
||||
n2 [label="TableTypeVar a"];
|
||||
n2 -> n3 [label="x"];
|
||||
n3 [label="number"];
|
||||
})",
|
||||
toDot(*ty, opts));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
|
@ -445,9 +445,6 @@ local a: Import.Type
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_packs")
|
||||
{
|
||||
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
|
||||
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
|
||||
|
||||
std::string code = R"(
|
||||
type Packed<T...> = (T...)->(T...)
|
||||
local a: Packed<>
|
||||
|
|
|
@ -537,8 +537,6 @@ TEST_CASE_FIXTURE(Fixture, "free_variables_from_typeof_in_aliases")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "non_recursive_aliases_that_reuse_a_generic_name")
|
||||
{
|
||||
ScopedFastFlag sff1{"LuauSubstitutionDontReplaceIgnoredTypes", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Array<T> = { [number]: T }
|
||||
type Tuple<T, V> = Array<T | V>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue