Merge branch 'master' into web-repl-improvements

This commit is contained in:
Pelanyo Kamara 2021-12-14 22:00:17 +00:00 committed by GitHub
commit deb21154d3
Signed by: DevComp
GPG key ID: 4AEE18F83AFDEB23
116 changed files with 4658 additions and 3056 deletions

View file

@ -67,13 +67,18 @@ jobs:
coverage: coverage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: install - name: install
run: | run: |
sudo apt install llvm sudo apt install llvm
- name: make coverage - name: make coverage
run: | run: |
CXX=clang++-10 make -j2 config=coverage coverage CXX=clang++-10 make -j2 config=coverage coverage
- name: debug coverage
run: |
git status
git log -5
echo SHA: $GITHUB_SHA
- name: upload coverage - name: upload coverage
uses: coverallsapp/github-action@master uses: coverallsapp/github-action@master
with: with:

View file

@ -277,11 +277,20 @@ struct MissingUnionProperty
bool operator==(const MissingUnionProperty& rhs) const; 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, using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire, DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError,
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning, CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty>; DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
TypesAreUnrelated>;
struct TypeError struct TypeError
{ {

View file

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

View file

@ -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 ModuleHasCyclicDependency& error);
std::ostream& operator<<(std::ostream& lhs, const DuplicateGenericParameter& 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 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 TableState& tv);
std::ostream& operator<<(std::ostream& lhs, const TypeVar& tv); std::ostream& operator<<(std::ostream& lhs, const TypeVar& tv);

View file

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

View file

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

View file

@ -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 // 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 // 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); std::string dump(TypeId ty);
void dump(TypePackId ty); std::string dump(TypePackId ty);
std::string generateName(size_t n); std::string generateName(size_t n);

View file

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

View file

@ -156,13 +156,14 @@ struct TypeChecker
// Returns both the type of the lvalue and its binding (if the caller wants to mutate the binding). // Returns both the type of the lvalue and its binding (if the caller wants to mutate the binding).
// Note: the binding may be null. // 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 AstExpr& expr);
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprLocal& 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 AstExprGlobal& expr);
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr); std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr);
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& 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::pair<TypeId, ScopePtr> checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
std::optional<Location> originalNameLoc, std::optional<TypeId> expectedType); std::optional<Location> originalNameLoc, std::optional<TypeId> expectedType);
void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function); 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); 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::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, 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); 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, bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
const std::vector<OverloadErrorEntry>& errors); const std::vector<OverloadErrorEntry>& errors);
@ -277,7 +278,7 @@ public:
[[noreturn]] void ice(const std::string& message); [[noreturn]] void ice(const std::string& message);
ScopePtr childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel = 0); 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. // Wrapper for merge(l, r, toUnion) but without the lambda junk.
void merge(RefinementMap& l, const RefinementMap& r); void merge(RefinementMap& l, const RefinementMap& r);
@ -297,7 +298,6 @@ private:
private: private:
Unifier mkUnifier(const Location& location); Unifier mkUnifier(const Location& location);
Unifier mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location);
// These functions are only safe to call when we are in the process of typechecking a module. // These functions are only safe to call when we are in the process of typechecking a module.

View file

@ -499,6 +499,7 @@ struct SingletonTypes
const TypePackId anyTypePack; const TypePackId anyTypePack;
SingletonTypes(); SingletonTypes();
~SingletonTypes();
SingletonTypes(const SingletonTypes&) = delete; SingletonTypes(const SingletonTypes&) = delete;
void operator=(const SingletonTypes&) = delete; void operator=(const SingletonTypes&) = delete;
@ -509,38 +510,22 @@ struct SingletonTypes
private: private:
std::unique_ptr<struct TypeArena> arena; std::unique_ptr<struct TypeArena> arena;
bool debugFreezeArena = false;
TypeId makeStringMetatable(); TypeId makeStringMetatable();
}; };
extern SingletonTypes singletonTypes; SingletonTypes& getSingletonTypes();
void persist(TypeId ty); void persist(TypeId ty);
void persist(TypePackId tp); void persist(TypePackId tp);
struct ToDotOptions
{
bool showPointers = true; // Show pointer value in the node label
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
};
std::string toDot(TypeId ty, const ToDotOptions& opts);
std::string toDot(TypePackId tp, const ToDotOptions& opts);
std::string toDot(TypeId ty);
std::string toDot(TypePackId tp);
void dumpDot(TypeId ty);
void dumpDot(TypePackId tp);
const TypeLevel* getLevel(TypeId ty); const TypeLevel* getLevel(TypeId ty);
TypeLevel* getMutableLevel(TypeId ty); TypeLevel* getMutableLevel(TypeId ty);
const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name); const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name);
bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent); bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent);
bool hasGeneric(TypeId ty);
bool hasGeneric(TypePackId tp);
TypeVar* asMutable(TypeId ty); TypeVar* asMutable(TypeId ty);
template<typename T> template<typename T>

View file

@ -24,7 +24,7 @@ struct TypeLevel
int level = 0; int level = 0;
int subLevel = 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 bool subsumes(const TypeLevel& rhs) const
{ {
if (level < rhs.level) if (level < rhs.level)
@ -38,6 +38,15 @@ struct TypeLevel
return false; 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 incr() const
{ {
TypeLevel result; TypeLevel result;

View file

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

View file

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

View file

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

View file

@ -12,9 +12,10 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
LUAU_FASTFLAGVARIABLE(ElseElseIfCompletionImprovements, false);
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport) LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteFirstArg, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = { static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -190,7 +191,48 @@ static ParenthesesRecommendation getParenRecommendation(TypeId id, const std::ve
return ParenthesesRecommendation::None; 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); ty = follow(ty);
@ -203,8 +245,9 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
{ {
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, nullptr); CloneState cloneState;
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, nullptr); expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
auto errors = unifier.canUnify(expectedType, actualType); auto errors = unifier.canUnify(expectedType, actualType);
return errors.empty(); return errors.empty();
@ -219,38 +262,75 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
} }
}; };
auto expr = node->asExpr(); TypeId expectedType;
if (!expr)
return TypeCorrectKind::None;
auto it = module.astExpectedTypes.find(expr); if (FFlag::LuauAutocompleteFirstArg)
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 (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail))) auto typeAtPosition = findExpectedTypeAt(module, node, position);
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
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 enum class PropIndexType
@ -309,8 +389,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
if (result.count(name) == 0 && name != Parser::errorName) if (result.count(name) == 0 && name != Parser::errorName)
{ {
Luau::TypeId type = Luau::follow(prop.type); Luau::TypeId type = Luau::follow(prop.type);
TypeCorrectKind typeCorrect = TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct
indexType == PropIndexType::Key ? TypeCorrectKind::Correct : checkTypeCorrectKind(module, typeArena, nodes.back(), type); : checkTypeCorrectKind(module, typeArena, nodes.back(), {{}, {}}, type);
ParenthesesRecommendation parens = ParenthesesRecommendation parens =
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect); indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
@ -668,17 +748,31 @@ std::optional<const T*> returnFirstNonnullOptionOfType(const UnionTypeVar* utv)
return ret; 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(); TypeId expectedType;
if (!expr)
return std::nullopt;
auto it = module.astExpectedTypes.find(expr); if (FFlag::LuauAutocompleteFirstArg)
if (!it) {
return std::nullopt; 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)) if (get<FunctionTypeVar>(expectedType))
return true; return true;
@ -1147,7 +1241,7 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
std::string n = toString(name); std::string n = toString(name);
if (!result.count(n)) 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, result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt,
binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)}; binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)};
@ -1157,9 +1251,10 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
scope = scope->parent; scope = scope->parent;
} }
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, typeChecker.nilType); TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.nilType);
TypeCorrectKind correctForBoolean = checkTypeCorrectKind(module, typeArena, node, typeChecker.booleanType); TypeCorrectKind correctForBoolean = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.booleanType);
TypeCorrectKind correctForFunction = functionIsExpectedAt(module, node).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None; TypeCorrectKind correctForFunction =
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
if (FFlag::LuauIfElseExpressionAnalysisSupport) if (FFlag::LuauIfElseExpressionAnalysisSupport)
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false}; 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) else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (AstStatIf* statIf = node->as<AstStatIf>(); FFlag::ElseElseIfCompletionImprovements && statIf && !statIf->hasElse) else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->hasElse)
{ {
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
finder.ancestry}; finder.ancestry};

View file

@ -8,8 +8,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauNewRequireTrace2)
/** FIXME: Many of these type definitions are not quite completely accurate. /** FIXME: Many of these type definitions are not quite completely accurate.
* *
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk * Some of them require richer generics than we have. For instance, we do not yet have a way to talk
@ -219,9 +217,9 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypeId genericK = arena.addType(GenericTypeVar{"K"}); TypeId genericK = arena.addType(GenericTypeVar{"K"});
TypeId genericV = arena.addType(GenericTypeVar{"V"}); 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); LUAU_ASSERT(stringMetatableTy);
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy)); const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
LUAU_ASSERT(stringMetatableTable); LUAU_ASSERT(stringMetatableTable);
@ -273,7 +271,10 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
persist(pair.second.typeId); persist(pair.second.typeId);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(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); attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
@ -473,9 +474,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
if (!checkRequirePath(typechecker, expr.args.data[0])) if (!checkRequirePath(typechecker, expr.args.data[0]))
return std::nullopt; return std::nullopt;
const AstExpr* require = FFlag::LuauNewRequireTrace2 ? &expr : expr.args.data[0]; if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr))
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require))
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})}; return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
return std::nullopt; return std::nullopt;

View file

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauFixTonumberReturnType, false)
namespace Luau namespace Luau
{ {
@ -113,7 +115,6 @@ declare function gcinfo(): number
declare function error<T>(message: T, level: number?) declare function error<T>(message: T, level: number?)
declare function tostring<T>(value: T): string 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 rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V declare function rawget<K, V>(tab: {[K]: V}, k: K): V
@ -204,7 +205,14 @@ declare function gcinfo(): number
std::string getBuiltinDefinitionSource() 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 } // namespace Luau

View file

@ -7,57 +7,14 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(LuauTypeAliasPacks)
static std::string wrongNumberOfArgsString_DEPRECATED(size_t expectedCount, size_t actualCount, bool isTypeArgs = false)
{
std::string s = "expects " + std::to_string(expectedCount) + " ";
if (isTypeArgs)
s += "type ";
s += "argument";
if (expectedCount != 1)
s += "s";
s += ", but ";
if (actualCount == 0)
{
s += "none";
}
else
{
if (actualCount < expectedCount)
s += "only ";
s += std::to_string(actualCount);
}
s += (actualCount == 1) ? " is" : " are";
s += " specified";
return s;
}
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
{ {
std::string s; std::string s = "expects ";
if (FFlag::LuauTypeAliasPacks) if (isVariadic)
{ s += "at least ";
s = "expects ";
if (isVariadic) s += std::to_string(expectedCount) + " ";
s += "at least ";
s += std::to_string(expectedCount) + " ";
}
else
{
s = "expects " + std::to_string(expectedCount) + " ";
}
if (argPrefix) if (argPrefix)
s += std::string(argPrefix) + " "; s += std::string(argPrefix) + " ";
@ -101,7 +58,7 @@ struct ErrorConverter
result += "\ncaused by:\n "; result += "\ncaused by:\n ";
if (!tm.reason.empty()) if (!tm.reason.empty())
result += tm.reason + ". "; result += tm.reason + " ";
result += Luau::toString(*tm.error); 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) + return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
" are required here"; " are required here";
case CountMismatch::Arg: case CountMismatch::Arg:
if (FFlag::LuauTypeAliasPacks) return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
else
return "Argument count mismatch. Function " + wrongNumberOfArgsString_DEPRECATED(e.expected, e.actual);
} }
LUAU_ASSERT(!"Unknown context"); LUAU_ASSERT(!"Unknown context");
@ -232,7 +186,7 @@ struct ErrorConverter
std::string operator()(const Luau::IncorrectGenericParameterCount& e) const std::string operator()(const Luau::IncorrectGenericParameterCount& e) const
{ {
std::string name = e.name; std::string name = e.name;
if (!e.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !e.typeFun.typePackParams.empty())) if (!e.typeFun.typeParams.empty() || !e.typeFun.typePackParams.empty())
{ {
name += "<"; name += "<";
bool first = true; bool first = true;
@ -246,36 +200,25 @@ struct ErrorConverter
name += toString(t); name += toString(t);
} }
if (FFlag::LuauTypeAliasPacks) for (TypePackId t : e.typeFun.typePackParams)
{ {
for (TypePackId t : e.typeFun.typePackParams) if (first)
{ first = false;
if (first) else
first = false; name += ", ";
else
name += ", ";
name += toString(t); name += toString(t);
}
} }
name += ">"; name += ">";
} }
if (FFlag::LuauTypeAliasPacks) if (e.typeFun.typeParams.size() != e.actualParameters)
{ return "Generic type '" + name + "' " +
if (e.typeFun.typeParams.size() != e.actualParameters) wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
return "Generic type '" + name + "' " +
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
return "Generic type '" + name + "' " + return "Generic type '" + name + "' " +
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false); wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
}
else
{
return "Generic type '" + name + "' " +
wrongNumberOfArgsString_DEPRECATED(e.typeFun.typeParams.size(), e.actualParameters, /*isTypeArgs*/ true);
}
} }
std::string operator()(const Luau::SyntaxError& e) const std::string operator()(const Luau::SyntaxError& e) const
@ -467,6 +410,11 @@ struct ErrorConverter
return ss + " in the type '" + toString(e.type) + "'"; 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 struct InvalidNameChecker
@ -591,11 +539,8 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size()) if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size())
return false; return false;
if (FFlag::LuauTypeAliasPacks) if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
{ return false;
if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size())
return false;
}
for (size_t i = 0; i < typeFun.typeParams.size(); ++i) for (size_t i = 0; i < typeFun.typeParams.size(); ++i)
{ {
@ -603,13 +548,10 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC
return false; return false;
} }
if (FFlag::LuauTypeAliasPacks) for (size_t i = 0; i < typeFun.typePackParams.size(); ++i)
{ {
for (size_t i = 0; i < typeFun.typePackParams.size(); ++i) if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
{ return false;
if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i])
return false;
}
} }
return true; return true;
@ -721,6 +663,11 @@ bool MissingUnionProperty::operator==(const MissingUnionProperty& rhs) const
return *type == *rhs.type && key == rhs.key; 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) std::string toString(const TypeError& error)
{ {
ErrorConverter converter; ErrorConverter converter;
@ -733,14 +680,14 @@ bool containsParseErrorName(const TypeError& error)
} }
template<typename T> template<typename T>
void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks) void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState cloneState)
{ {
auto clone = [&](auto&& ty) { auto clone = [&](auto&& ty) {
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks); return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks, cloneState);
}; };
auto visitErrorData = [&](auto&& e) { auto visitErrorData = [&](auto&& e) {
copyError(e, destArena, seenTypes, seenTypePacks); copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
}; };
if constexpr (false) if constexpr (false)
@ -856,6 +803,11 @@ void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks&
for (auto& ty : e.missing) for (auto& ty : e.missing)
ty = clone(ty); ty = clone(ty);
} }
else if constexpr (std::is_same_v<T, TypesAreUnrelated>)
{
e.left = clone(e.left);
e.right = clone(e.right);
}
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }
@ -864,9 +816,10 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
{ {
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState;
auto visitErrorData = [&](auto&& e) { auto visitErrorData = [&](auto&& e) {
copyError(e, destArena, seenTypes, seenTypePacks); copyError(e, destArena, seenTypes, seenTypePacks, cloneState);
}; };
LUAU_ASSERT(!destArena.typeVars.isFrozen()); LUAU_ASSERT(!destArena.typeVars.isFrozen());

View file

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

View file

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

View file

@ -5,8 +5,6 @@
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauTypeAliasPacks)
namespace Luau namespace Luau
{ {
@ -264,7 +262,7 @@ struct AstJsonEncoder : public AstVisitor
if (comma) if (comma)
writeRaw(","); writeRaw(",");
else else
comma = false; comma = true;
write(a); write(a);
} }
@ -381,7 +379,7 @@ struct AstJsonEncoder : public AstVisitor
if (comma) if (comma)
writeRaw(","); writeRaw(",");
else else
comma = false; comma = true;
write(prop); write(prop);
} }
}); });
@ -615,12 +613,7 @@ struct AstJsonEncoder : public AstVisitor
writeNode(node, "AstStatTypeAlias", [&]() { writeNode(node, "AstStatTypeAlias", [&]() {
PROP(name); PROP(name);
PROP(generics); PROP(generics);
PROP(genericPacks);
if (FFlag::LuauTypeAliasPacks)
{
PROP(genericPacks);
}
PROP(type); PROP(type);
PROP(exported); PROP(exported);
}); });

View file

@ -1,20 +1,19 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Common.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h" #include "Luau/VisitTypeVar.h"
#include "Luau/Common.h"
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauCloneBoundTables, false)
namespace Luau namespace Luau
{ {
@ -23,7 +22,7 @@ static bool contains(Position pos, Comment comment)
{ {
if (comment.location.contains(pos)) if (comment.location.contains(pos))
return true; 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 comment.location.begin <= pos) // Broken comments are broken specifically because they don't have an end
return true; return true;
else if (comment.type == Lexeme::Comment && comment.location.end == pos) else if (comment.type == Lexeme::Comment && comment.location.end == pos)
@ -120,12 +119,6 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
return allocated; return allocated;
} }
using SeenTypes = std::unordered_map<TypeId, TypeId>;
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
namespace namespace
{ {
@ -138,11 +131,12 @@ struct TypePackCloner;
struct TypeCloner struct TypeCloner
{ {
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks) TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
: dest(dest) : dest(dest)
, typeId(typeId) , typeId(typeId)
, seenTypes(seenTypes) , seenTypes(seenTypes)
, seenTypePacks(seenTypePacks) , seenTypePacks(seenTypePacks)
, cloneState(cloneState)
{ {
} }
@ -150,8 +144,7 @@ struct TypeCloner
TypeId typeId; TypeId typeId;
SeenTypes& seenTypes; SeenTypes& seenTypes;
SeenTypePacks& seenTypePacks; SeenTypePacks& seenTypePacks;
CloneState& cloneState;
bool* encounteredFreeType = nullptr;
template<typename T> template<typename T>
void defaultClone(const T& t); void defaultClone(const T& t);
@ -178,13 +171,14 @@ struct TypePackCloner
TypePackId typePackId; TypePackId typePackId;
SeenTypes& seenTypes; SeenTypes& seenTypes;
SeenTypePacks& seenTypePacks; SeenTypePacks& seenTypePacks;
bool* encounteredFreeType = nullptr; CloneState& cloneState;
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks) TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
: dest(dest) : dest(dest)
, typePackId(typePackId) , typePackId(typePackId)
, seenTypes(seenTypes) , seenTypes(seenTypes)
, seenTypePacks(seenTypePacks) , seenTypePacks(seenTypePacks)
, cloneState(cloneState)
{ {
} }
@ -197,10 +191,9 @@ struct TypePackCloner
void operator()(const Unifiable::Free& t) void operator()(const Unifiable::Free& t)
{ {
if (encounteredFreeType) cloneState.encounteredFreeType = true;
*encounteredFreeType = true;
TypePackId err = singletonTypes.errorRecoveryTypePack(singletonTypes.anyTypePack); TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
TypePackId cloned = dest.addTypePack(*err); TypePackId cloned = dest.addTypePack(*err);
seenTypePacks[typePackId] = cloned; 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. // We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
void operator()(const Unifiable::Bound<TypePackId>& t) void operator()(const Unifiable::Bound<TypePackId>& t)
{ {
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType); TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
seenTypePacks[typePackId] = cloned; seenTypePacks[typePackId] = cloned;
} }
void operator()(const VariadicTypePack& t) void operator()(const VariadicTypePack& t)
{ {
TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, encounteredFreeType)}}); TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, cloneState)}});
seenTypePacks[typePackId] = cloned; seenTypePacks[typePackId] = cloned;
} }
@ -236,10 +229,10 @@ struct TypePackCloner
seenTypePacks[typePackId] = cloned; seenTypePacks[typePackId] = cloned;
for (TypeId ty : t.head) for (TypeId ty : t.head)
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
if (t.tail) if (t.tail)
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, encounteredFreeType); destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, cloneState);
} }
}; };
@ -252,9 +245,8 @@ void TypeCloner::defaultClone(const T& t)
void TypeCloner::operator()(const Unifiable::Free& t) void TypeCloner::operator()(const Unifiable::Free& t)
{ {
if (encounteredFreeType) cloneState.encounteredFreeType = true;
*encounteredFreeType = true; TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType);
TypeId err = singletonTypes.errorRecoveryType(singletonTypes.anyType);
TypeId cloned = dest.addType(*err); TypeId cloned = dest.addType(*err);
seenTypes[typeId] = cloned; seenTypes[typeId] = cloned;
} }
@ -266,7 +258,7 @@ void TypeCloner::operator()(const Unifiable::Generic& t)
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t) void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
{ {
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType); TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
seenTypes[typeId] = boundTo; seenTypes[typeId] = boundTo;
} }
@ -294,23 +286,23 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
seenTypes[typeId] = result; seenTypes[typeId] = result;
for (TypeId generic : t.generics) for (TypeId generic : t.generics)
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, encounteredFreeType)); ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, cloneState));
for (TypePackId genericPack : t.genericPacks) for (TypePackId genericPack : t.genericPacks)
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, encounteredFreeType)); ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, cloneState));
ftv->tags = t.tags; ftv->tags = t.tags;
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, encounteredFreeType); ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, cloneState);
ftv->argNames = t.argNames; ftv->argNames = t.argNames;
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, encounteredFreeType); ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, cloneState);
} }
void TypeCloner::operator()(const TableTypeVar& t) void TypeCloner::operator()(const TableTypeVar& t)
{ {
// If table is now bound to another one, we ignore the content of the original // If table is now bound to another one, we ignore the content of the original
if (FFlag::LuauCloneBoundTables && t.boundTo) if (t.boundTo)
{ {
TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType); TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, cloneState);
seenTypes[typeId] = boundTo; seenTypes[typeId] = boundTo;
return; return;
} }
@ -326,34 +318,21 @@ void TypeCloner::operator()(const TableTypeVar& t)
ttv->level = TypeLevel{0, 0}; ttv->level = TypeLevel{0, 0};
for (const auto& [name, prop] : t.props) for (const auto& [name, prop] : t.props)
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags}; ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
if (t.indexer) if (t.indexer)
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, encounteredFreeType), ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, cloneState),
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, encounteredFreeType)}; clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, cloneState)};
if (!FFlag::LuauCloneBoundTables)
{
if (t.boundTo)
ttv->boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
}
for (TypeId& arg : ttv->instantiatedTypeParams) for (TypeId& arg : ttv->instantiatedTypeParams)
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType); arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
if (FFlag::LuauTypeAliasPacks) for (TypePackId& arg : ttv->instantiatedTypePackParams)
{ arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState);
for (TypePackId& arg : ttv->instantiatedTypePackParams)
arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType);
}
if (ttv->state == TableState::Free) if (ttv->state == TableState::Free)
{ {
if (FFlag::LuauCloneBoundTables || !t.boundTo) cloneState.encounteredFreeType = true;
{
if (encounteredFreeType)
*encounteredFreeType = true;
}
ttv->state = TableState::Sealed; ttv->state = TableState::Sealed;
} }
@ -369,8 +348,8 @@ void TypeCloner::operator()(const MetatableTypeVar& t)
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result); MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result);
seenTypes[typeId] = result; seenTypes[typeId] = result;
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, encounteredFreeType); mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, cloneState);
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType); mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, cloneState);
} }
void TypeCloner::operator()(const ClassTypeVar& t) void TypeCloner::operator()(const ClassTypeVar& t)
@ -381,13 +360,13 @@ void TypeCloner::operator()(const ClassTypeVar& t)
seenTypes[typeId] = result; seenTypes[typeId] = result;
for (const auto& [name, prop] : t.props) for (const auto& [name, prop] : t.props)
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags}; ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags};
if (t.parent) if (t.parent)
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, encounteredFreeType); ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, cloneState);
if (t.metatable) if (t.metatable)
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType); ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, cloneState);
} }
void TypeCloner::operator()(const AnyTypeVar& t) void TypeCloner::operator()(const AnyTypeVar& t)
@ -404,7 +383,7 @@ void TypeCloner::operator()(const UnionTypeVar& t)
LUAU_ASSERT(option != nullptr); LUAU_ASSERT(option != nullptr);
for (TypeId ty : t.options) for (TypeId ty : t.options)
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
} }
void TypeCloner::operator()(const IntersectionTypeVar& t) void TypeCloner::operator()(const IntersectionTypeVar& t)
@ -416,7 +395,7 @@ void TypeCloner::operator()(const IntersectionTypeVar& t)
LUAU_ASSERT(option != nullptr); LUAU_ASSERT(option != nullptr);
for (TypeId ty : t.parts) for (TypeId ty : t.parts)
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
} }
void TypeCloner::operator()(const LazyTypeVar& t) void TypeCloner::operator()(const LazyTypeVar& t)
@ -426,60 +405,55 @@ void TypeCloner::operator()(const LazyTypeVar& t)
} // anonymous namespace } // anonymous namespace
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType) TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
{ {
if (tp->persistent) if (tp->persistent)
return tp; return tp;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypePackId& res = seenTypePacks[tp]; TypePackId& res = seenTypePacks[tp];
if (res == nullptr) if (res == nullptr)
{ {
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks}; TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks, cloneState};
cloner.encounteredFreeType = encounteredFreeType;
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
} }
if (FFlag::DebugLuauTrackOwningArena)
asMutable(res)->owningArena = &dest;
return res; return res;
} }
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType) TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
{ {
if (typeId->persistent) if (typeId->persistent)
return typeId; return typeId;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypeId& res = seenTypes[typeId]; TypeId& res = seenTypes[typeId];
if (res == nullptr) if (res == nullptr)
{ {
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks}; TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
cloner.encounteredFreeType = encounteredFreeType;
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
// TODO: Make this work when the arena of 'res' might be frozen
asMutable(res)->documentationSymbol = typeId->documentationSymbol; asMutable(res)->documentationSymbol = typeId->documentationSymbol;
} }
if (FFlag::DebugLuauTrackOwningArena)
asMutable(res)->owningArena = &dest;
return res; return res;
} }
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType) TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState)
{ {
TypeFun result; TypeFun result;
for (TypeId ty : typeFun.typeParams) for (TypeId ty : typeFun.typeParams)
result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
if (FFlag::LuauTypeAliasPacks) for (TypePackId tp : typeFun.typePackParams)
{ result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, cloneState));
for (TypePackId tp : typeFun.typePackParams)
result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, encounteredFreeType));
}
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, encounteredFreeType); result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, cloneState);
return result; return result;
} }
@ -519,19 +493,18 @@ bool Module::clonePublicInterface()
LUAU_ASSERT(interfaceTypes.typeVars.empty()); LUAU_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty()); LUAU_ASSERT(interfaceTypes.typePacks.empty());
bool encounteredFreeType = false;
SeenTypePacks seenTypePacks;
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
ScopePtr moduleScope = getModuleScope(); ScopePtr moduleScope = getModuleScope();
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType); moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, cloneState);
if (moduleScope->varargPack) if (moduleScope->varargPack)
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType); moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (auto& pair : moduleScope->exportedTypeBindings) for (auto& [name, tf] : moduleScope->exportedTypeBindings)
pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType); tf = clone(tf, interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (TypeId ty : moduleScope->returnType) for (TypeId ty : moduleScope->returnType)
if (get<GenericTypeVar>(follow(ty))) if (get<GenericTypeVar>(follow(ty)))
@ -540,7 +513,7 @@ bool Module::clonePublicInterface()
freeze(internalTypes); freeze(internalTypes);
freeze(interfaceTypes); freeze(interfaceTypes);
return encounteredFreeType; return cloneState.encounteredFreeType;
} }
} // namespace Luau } // namespace Luau

View file

@ -24,7 +24,7 @@ std::optional<LValue> tryGetLValue(const AstExpr& node)
else if (auto indexexpr = expr->as<AstExprIndexExpr>()) else if (auto indexexpr = expr->as<AstExprIndexExpr>())
{ {
if (auto lvalue = tryGetLValue(*indexexpr->expr)) 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)}; return Field{std::make_shared<LValue>(*lvalue), std::string(string->value.data, string->value.size)};
} }

View file

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

View file

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

View file

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

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

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

View file

@ -11,7 +11,14 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions) 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 namespace Luau
{ {
@ -59,11 +66,8 @@ struct FindCyclicTypes
for (TypeId itp : ttv.instantiatedTypeParams) for (TypeId itp : ttv.instantiatedTypeParams)
visitTypeVar(itp, *this, seen); visitTypeVar(itp, *this, seen);
if (FFlag::LuauTypeAliasPacks) for (TypePackId itp : ttv.instantiatedTypePackParams)
{ visitTypeVar(itp, *this, seen);
for (TypePackId itp : ttv.instantiatedTypePackParams)
visitTypeVar(itp, *this, seen);
}
return exhaustive; return exhaustive;
} }
@ -248,65 +252,60 @@ struct TypeVarStringifier
void stringify(const std::vector<TypeId>& types, const std::vector<TypePackId>& typePacks) void stringify(const std::vector<TypeId>& types, const std::vector<TypePackId>& typePacks)
{ {
if (types.size() == 0 && (!FFlag::LuauTypeAliasPacks || typePacks.size() == 0)) if (types.size() == 0 && typePacks.size() == 0)
return; return;
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size())) if (types.size() || typePacks.size())
state.emit("<"); state.emit("<");
if (FFlag::LuauTypeAliasPacks) bool first = true;
{
bool first = true;
for (TypeId ty : types) for (TypeId ty : types)
{ {
if (!first) if (!first)
state.emit(", "); state.emit(", ");
first = false;
stringify(ty);
}
bool singleTp = typePacks.size() == 1;
for (TypePackId tp : typePacks)
{
if (isEmpty(tp) && singleTp)
continue;
if (!first)
state.emit(", ");
else
first = false; first = false;
stringify(ty); if (!singleTp)
} state.emit("(");
bool singleTp = typePacks.size() == 1; stringify(tp);
for (TypePackId tp : typePacks) if (!singleTp)
{ state.emit(")");
if (isEmpty(tp) && singleTp)
continue;
if (!first)
state.emit(", ");
else
first = false;
if (!singleTp)
state.emit("(");
stringify(tp);
if (!singleTp)
state.emit(")");
}
}
else
{
for (size_t i = 0; i < types.size(); ++i)
{
if (i > 0)
state.emit(", ");
stringify(types[i]);
}
} }
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size())) if (types.size() || typePacks.size())
state.emit(">"); state.emit(">");
} }
void operator()(TypeId ty, const Unifiable::Free& ftv) void operator()(TypeId ty, const Unifiable::Free& ftv)
{ {
state.result.invalid = true; state.result.invalid = true;
if (FFlag::DebugLuauVerboseTypeNames)
state.emit("free-");
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FFlag::DebugLuauVerboseTypeNames)
{
state.emit("-");
state.emit(std::to_string(ftv.level.level));
}
} }
void operator()(TypeId, const BoundTypeVar& btv) void operator()(TypeId, const BoundTypeVar& btv)
@ -767,12 +766,23 @@ struct TypePackStringifier
else else
state.emit(", "); state.emit(", ");
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size()); if (FFlag::LuauFunctionArgumentNameSize)
if (!elemNames.empty() && elemNames[elemIndex])
{ {
state.emit(elemNames[elemIndex]->name); if (elemIndex < elemNames.size() && elemNames[elemIndex])
state.emit(": "); {
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
}
else
{
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
if (!elemNames.empty() && elemNames[elemIndex])
{
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
} }
elemIndex++; elemIndex++;
@ -807,6 +817,8 @@ struct TypePackStringifier
void operator()(TypePackId tp, const GenericTypePack& pack) void operator()(TypePackId tp, const GenericTypePack& pack)
{ {
if (FFlag::DebugLuauVerboseTypeNames)
state.emit("gen-");
if (pack.explicitName) if (pack.explicitName)
{ {
state.result.nameMap.typePacks[tp] = pack.name; state.result.nameMap.typePacks[tp] = pack.name;
@ -822,7 +834,16 @@ struct TypePackStringifier
void operator()(TypePackId tp, const FreeTypePack& pack) void operator()(TypePackId tp, const FreeTypePack& pack)
{ {
state.result.invalid = true; state.result.invalid = true;
if (FFlag::DebugLuauVerboseTypeNames)
state.emit("free-");
state.emit(state.getName(tp)); state.emit(state.getName(tp));
if (FFlag::DebugLuauVerboseTypeNames)
{
state.emit("-");
state.emit(std::to_string(pack.level.level));
}
state.emit("..."); state.emit("...");
} }
@ -929,38 +950,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
result.name += ttv->name ? *ttv->name : *ttv->syntheticName; result.name += ttv->name ? *ttv->name : *ttv->syntheticName;
if (FFlag::LuauTypeAliasPacks) tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
{
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
}
else
{
if (ttv->instantiatedTypeParams.empty() && (!FFlag::LuauTypeAliasPacks || ttv->instantiatedTypePackParams.empty()))
return result;
result.name += "<";
bool first = true;
for (TypeId ty : ttv->instantiatedTypeParams)
{
if (!first)
result.name += ", ";
else
first = false;
tvs.stringify(ty);
}
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
{
result.truncated = true;
result.name += "... <TRUNCATED>";
}
else
{
result.name += ">";
}
}
return result; return result;
} }
@ -1161,17 +1151,37 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
s += ", "; s += ", ";
first = false; first = false;
// argNames is guaranteed to be equal to argTypes iff argNames is not empty. if (FFlag::LuauFunctionArgumentNameSize)
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (!ftv.argNames.empty())
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
s += toString_(*argPackIter);
++argPackIter;
if (!ftv.argNames.empty())
{ {
LUAU_ASSERT(argNameIter != ftv.argNames.end()); // We don't currently respect opts.functionTypeArguments. I don't think this function should.
++argNameIter; if (argNameIter != ftv.argNames.end())
{
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
++argNameIter;
}
else
{
s += "_: ";
}
}
else
{
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (!ftv.argNames.empty())
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
}
s += toString_(*argPackIter);
++argPackIter;
if (!FFlag::LuauFunctionArgumentNameSize)
{
if (!ftv.argNames.empty())
{
LUAU_ASSERT(argNameIter != ftv.argNames.end());
++argNameIter;
}
} }
} }
@ -1197,20 +1207,24 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
return s; return s;
} }
void dump(TypeId ty) std::string dump(TypeId ty)
{ {
ToStringOptions opts; ToStringOptions opts;
opts.exhaustive = true; opts.exhaustive = true;
opts.functionTypeArguments = 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; ToStringOptions opts;
opts.exhaustive = true; opts.exhaustive = true;
opts.functionTypeArguments = 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) std::string generateName(size_t i)

View file

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

View file

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

View file

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

View file

@ -9,9 +9,9 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/TopoSortStatements.h" #include "Luau/TopoSortStatements.h"
#include "Luau/ToString.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/ToString.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/TimeTrace.h" #include "Luau/TimeTrace.h"
@ -23,22 +23,25 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 500)
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000) LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauClassPropertyAccessAsString, false)
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false) LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false) LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false)
LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false) LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false) LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false)
LUAU_FASTFLAGVARIABLE(LuauStrictRequire, false)
LUAU_FASTFLAG(LuauSubstitutionDontReplaceIgnoredTypes)
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
LUAU_FASTFLAG(LuauNewRequireTrace2)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauTailArgumentTypeInfo, false)
LUAU_FASTFLAGVARIABLE(LuauModuleRequireErrorPack, false)
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 namespace Luau
{ {
@ -208,14 +211,14 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
: resolver(resolver) : resolver(resolver)
, iceHandler(iceHandler) , iceHandler(iceHandler)
, unifierState(iceHandler) , unifierState(iceHandler)
, nilType(singletonTypes.nilType) , nilType(getSingletonTypes().nilType)
, numberType(singletonTypes.numberType) , numberType(getSingletonTypes().numberType)
, stringType(singletonTypes.stringType) , stringType(getSingletonTypes().stringType)
, booleanType(singletonTypes.booleanType) , booleanType(getSingletonTypes().booleanType)
, threadType(singletonTypes.threadType) , threadType(getSingletonTypes().threadType)
, anyType(singletonTypes.anyType) , anyType(getSingletonTypes().anyType)
, optionalNumberType(singletonTypes.optionalNumberType) , optionalNumberType(getSingletonTypes().optionalNumberType)
, anyTypePack(singletonTypes.anyTypePack) , anyTypePack(getSingletonTypes().anyTypePack)
{ {
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); 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; functionDecls[*protoIter] = pair;
++subLevel; ++subLevel;
TypeId leftType = checkFunctionName(scope, *fun->name); TypeId leftType = checkFunctionName(scope, *fun->name, funScope->level);
unify(leftType, funTy, fun->location); unify(leftType, funTy, fun->location);
} }
else if (auto fun = (*protoIter)->as<AstStatLocalFunction>()) 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); return canUnify_(left, right, location);
} }
ErrorVec TypeChecker::canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId superTy, TypeId subTy, const Location& location)
{
Unifier state = mkUnifier(seen, location);
return state.canUnify(superTy, subTy);
}
template<typename Id> template<typename Id>
ErrorVec TypeChecker::canUnify_(Id superTy, Id subTy, const Location& location) ErrorVec TypeChecker::canUnify_(Id superTy, Id subTy, const Location& location)
{ {
@ -719,14 +716,15 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign)
} }
else if (auto tail = valueIter.tail()) else if (auto tail = valueIter.tail())
{ {
if (get<Unifiable::Error>(*tail)) TypePackId tailPack = follow(*tail);
if (get<Unifiable::Error>(tailPack))
right = errorRecoveryType(scope); right = errorRecoveryType(scope);
else if (auto vtp = get<VariadicTypePack>(*tail)) else if (auto vtp = get<VariadicTypePack>(tailPack))
right = vtp->ty; right = vtp->ty;
else if (get<Unifiable::Free>(*tail)) else if (get<Unifiable::Free>(tailPack))
{ {
*asMutable(*tail) = TypePack{{left}}; *asMutable(tailPack) = TypePack{{left}};
growingPack = getMutable<TypePack>(*tail); growingPack = getMutable<TypePack>(tailPack);
} }
} }
@ -1115,8 +1113,27 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
unify(leftType, ty, function.location); unify(leftType, ty, function.location);
if (leftTypeBinding) if (FFlag::LuauUpdateFunctionNameBinding)
*leftTypeBinding = follow(quantify(funScope, leftType, function.name->location)); {
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]; Location location = scope->typeAliasLocations[name];
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}}); reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
if (FFlag::LuauTypeAliasPacks) bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
else
bindingsMap[name] = TypeFun{binding->typeParams, errorRecoveryType(anyType)};
} }
else else
{ {
ScopePtr aliasScope = ScopePtr aliasScope = childScope(scope, typealias.location);
FFlag::LuauQuantifyInPlace2 ? childScope(scope, typealias.location, subLevel) : 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); TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty); FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true; ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
}
else
{
std::vector<TypeId> generics;
for (AstName generic : typealias.generics)
{
Name n = generic.value;
// These generics are the only thing that will ever be added to aliasScope, so we can be certain that
// a collision can only occur when two generic typevars have the same name.
if (aliasScope->privateTypeBindings.end() != aliasScope->privateTypeBindings.find(n))
{
// TODO(jhuelsman): report the exact span of the generic type parameter whose name is a duplicate.
reportError(TypeError{typealias.location, DuplicateGenericParameter{n}});
}
TypeId g;
if (FFlag::LuauRecursiveTypeParameterRestriction)
{
TypeId& cached = scope->typeAliasTypeParameters[n];
if (!cached)
cached = addType(GenericTypeVar{aliasScope->level, n});
g = cached;
}
else
g = addType(GenericTypeVar{aliasScope->level, n});
generics.push_back(g);
aliasScope->privateTypeBindings[n] = TypeFun{{}, g};
}
TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), ty};
}
} }
} }
else else
@ -1215,6 +1193,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
ice("Not predeclared"); ice("Not predeclared");
ScopePtr aliasScope = childScope(scope, typealias.location); ScopePtr aliasScope = childScope(scope, typealias.location);
aliasScope->level = scope->level.incr();
for (TypeId ty : binding->typeParams) for (TypeId ty : binding->typeParams)
{ {
@ -1223,14 +1202,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, ty}; aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, ty};
} }
if (FFlag::LuauTypeAliasPacks) for (TypePackId tp : binding->typePackParams)
{ {
for (TypePackId tp : binding->typePackParams) auto generic = get<GenericTypePack>(tp);
{ LUAU_ASSERT(generic);
auto generic = get<GenericTypePack>(tp); aliasScope->privateTypePackBindings[generic->name] = tp;
LUAU_ASSERT(generic);
aliasScope->privateTypePackBindings[generic->name] = tp;
}
} }
TypeId ty = resolveType(aliasScope, *typealias.type); TypeId ty = resolveType(aliasScope, *typealias.type);
@ -1241,19 +1217,16 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
{ {
// Copy can be skipped if this is an identical alias // Copy can be skipped if this is an identical alias
if (ttv->name != name || ttv->instantiatedTypeParams != binding->typeParams || if (ttv->name != name || ttv->instantiatedTypeParams != binding->typeParams ||
(FFlag::LuauTypeAliasPacks && ttv->instantiatedTypePackParams != binding->typePackParams)) ttv->instantiatedTypePackParams != binding->typePackParams)
{ {
// This is a shallow clone, original recursive links to self are not updated // This is a shallow clone, original recursive links to self are not updated
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
clone.methodDefinitionLocations = ttv->methodDefinitionLocations; clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
clone.definitionModuleName = ttv->definitionModuleName; clone.definitionModuleName = ttv->definitionModuleName;
clone.name = name; clone.name = name;
clone.instantiatedTypeParams = binding->typeParams; clone.instantiatedTypeParams = binding->typeParams;
clone.instantiatedTypePackParams = binding->typePackParams;
if (FFlag::LuauTypeAliasPacks)
clone.instantiatedTypePackParams = binding->typePackParams;
ty = addType(std::move(clone)); ty = addType(std::move(clone));
} }
@ -1262,9 +1235,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
{ {
ttv->name = name; ttv->name = name;
ttv->instantiatedTypeParams = binding->typeParams; ttv->instantiatedTypeParams = binding->typeParams;
ttv->instantiatedTypePackParams = binding->typePackParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = binding->typePackParams;
} }
} }
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty))) else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
@ -1289,7 +1260,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
} }
// We don't have generic classes, so this assertion _should_ never be hit. // We don't have generic classes, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && (!FFlag::LuauTypeAliasPacks || lookupType->typePackParams.size() == 0)); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type; superTy = lookupType->type;
if (!get<ClassTypeVar>(follow(*superTy))) if (!get<ClassTypeVar>(follow(*superTy)))
@ -1562,9 +1533,9 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCa
else if (auto vtp = get<VariadicTypePack>(retPack)) else if (auto vtp = get<VariadicTypePack>(retPack))
return {vtp->ty, std::move(result.predicates)}; return {vtp->ty, std::move(result.predicates)};
else if (get<Unifiable::Generic>(retPack)) else if (get<Unifiable::Generic>(retPack))
ice("Unexpected abstract type pack!"); ice("Unexpected abstract type pack!", expr.location);
else else
ice("Unknown TypePack type!"); ice("Unknown TypePack type!", expr.location);
} }
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexName& expr) 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) else if (tableType->state == TableState::Free)
{ {
TypeId result = freshType(scope); TypeId result = FFlag::LuauAscribeCorrectLevelToInferredProperitesOfFreeTables ? freshType(tableType->level) : freshType(scope);
tableType->props[name] = {result}; tableType->props[name] = {result};
return 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) 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) 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)) if (isNonstrictMode() && !getTableType(exprType) && !get<FunctionTypeVar>(exprType))
exprType = anyType; exprType = anyType;
if (FFlag::LuauPropertiesGetExpectedType && expectedTable)
{
auto it = expectedTable->props.find(key->value.data);
if (it != expectedTable->props.end())
{
Property expectedProp = it->second;
ErrorVec errors = tryUnify(expectedProp.type, exprType, k->location);
if (errors.empty())
exprType = expectedProp.type;
}
else if (expectedTable->indexer && isString(expectedTable->indexer->indexType))
{
ErrorVec errors = tryUnify(expectedTable->indexer->indexResultType, exprType, k->location);
if (errors.empty())
exprType = expectedTable->indexer->indexResultType;
}
}
props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location}; props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location};
} }
else else
@ -2460,12 +2458,27 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTy
TypeId annotationType = resolveType(scope, *expr.annotation); TypeId annotationType = resolveType(scope, *expr.annotation);
ExprResult<TypeId> result = checkExpr(scope, *expr.expr, annotationType); ExprResult<TypeId> result = checkExpr(scope, *expr.expr, annotationType);
ErrorVec errorVec = canUnify(result.type, annotationType, expr.location); if (FFlag::LuauBidirectionalAsExpr)
reportErrors(errorVec); {
if (!errorVec.empty()) // Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
annotationType = errorRecoveryType(annotationType); 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) 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?" // Answers the question: "Can I define another function with this name?"
// Primarily about detecting duplicates. // 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>()) if (auto globalName = funName.as<AstExprGlobal>())
{ {
const ScopePtr& globalScope = currentModule->getModuleScope(); const ScopePtr& globalScope = currentModule->getModuleScope();
@ -2728,7 +2748,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName)
} }
else else
{ {
TypeId ty = freshType(scope); TypeId ty = freshTy();
globalScope->bindings[name] = {ty, funName.location}; globalScope->bindings[name] = {ty, funName.location};
return ty; return ty;
} }
@ -2738,7 +2758,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName)
Symbol name = localName->local; Symbol name = localName->local;
Binding& binding = scope->bindings[name]; Binding& binding = scope->bindings[name];
if (binding.typeId == nullptr) if (binding.typeId == nullptr)
binding = {freshType(scope), funName.location}; binding = {freshTy(), funName.location};
return binding.typeId; return binding.typeId;
} }
@ -2769,7 +2789,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName)
Property& property = ttv->props[name]; Property& property = ttv->props[name];
property.type = freshType(scope); property.type = freshTy();
property.location = indexName->indexLocation; property.location = indexName->indexLocation;
ttv->methodDefinitionLocations[name] = funName.location; ttv->methodDefinitionLocations[name] = funName.location;
return property.type; return property.type;
@ -3366,7 +3386,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
fn = follow(fn); fn = follow(fn);
if (auto ret = checkCallOverload( 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; 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, 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) std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors)
{ {
LUAU_ASSERT(argLocations);
fn = stripFromNilAndReport(fn, expr.func->location); fn = stripFromNilAndReport(fn, expr.func->location);
if (get<AnyTypeVar>(fn)) if (get<AnyTypeVar>(fn))
@ -3467,31 +3489,44 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
return {{retPack}}; return {{retPack}};
} }
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn); std::vector<Location> metaArgLocations;
if (!ftv)
// Might be a callable table
if (const MetatableTypeVar* mttv = get<MetatableTypeVar>(fn))
{ {
// Might be a callable table if (std::optional<TypeId> ty = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, false))
if (const MetatableTypeVar* mttv = get<MetatableTypeVar>(fn))
{ {
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 fn = instantiate(scope, *ty, expr.func->location);
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);
argPack = metaCallArgPack;
args = metaCallArgs;
argLocations = &metaArgLocations;
}
else
{
TypeId fn = *ty; TypeId fn = *ty;
fn = instantiate(scope, fn, expr.func->location); 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); overloadsThatMatchArgCount, overloadsThatDont, errors);
} }
} }
}
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn);
if (!ftv)
{
reportError(TypeError{expr.func->location, CannotCallNonFunction{fn}}); reportError(TypeError{expr.func->location, CannotCallNonFunction{fn}});
unify(retPack, errorRecoveryTypePack(scope), expr.func->location); unify(retPack, errorRecoveryTypePack(scope), expr.func->location);
return {{errorRecoveryTypePack(retPack)}}; return {{errorRecoveryTypePack(retPack)}};
@ -3516,7 +3551,7 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
return {}; return {};
} }
checkArgumentList(scope, state, argPack, ftv->argTypes, argLocations); checkArgumentList(scope, state, argPack, ftv->argTypes, *argLocations);
if (!state.errors.empty()) 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) for (size_t i = 0; i < exprs.size; ++i)
{ {
AstExpr* expr = exprs.data[i]; AstExpr* expr = exprs.data[i];
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
if (i == lastIndex && (expr->is<AstExprCall>() || expr->is<AstExprVarargs>())) if (i == lastIndex && (expr->is<AstExprCall>() || expr->is<AstExprVarargs>()))
{ {
auto [typePack, exprPredicates] = checkExprPack(scope, *expr); auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
insert(exprPredicates); insert(exprPredicates);
if (FFlag::LuauTailArgumentTypeInfo)
{
if (std::optional<TypeId> firstTy = first(typePack))
{
if (!currentModule->astTypes.find(expr))
currentModule->astTypes[expr] = follow(*firstTy);
}
if (expectedType)
currentModule->astExpectedTypes[expr] = *expectedType;
}
tp->tail = typePack; tp->tail = typePack;
} }
else else
{ {
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType); auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
insert(exprPredicates); insert(exprPredicates);
@ -3797,9 +3844,9 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker"); LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker");
LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str()); LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str());
if (FFlag::LuauNewRequireTrace2 && moduleInfo.name.empty()) if (moduleInfo.name.empty())
{ {
if (FFlag::LuauStrictRequire && currentModule->mode == Mode::Strict) if (currentModule->mode == Mode::Strict)
{ {
reportError(TypeError{location, UnknownRequire{}}); reportError(TypeError{location, UnknownRequire{}});
return errorRecoveryType(anyType); 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: // There are two reasons why we might fail to find the module:
// either the file does not exist or there's a cycle. If there's a cycle // either the file does not exist or there's a cycle. If there's a cycle
// we will already have reported the error. // we will already have reported the error.
if (!resolver->moduleExists(moduleInfo.name) && (FFlag::LuauTraceRequireLookupChild ? !moduleInfo.optional : true)) if (!resolver->moduleExists(moduleInfo.name) && !moduleInfo.optional)
{ {
std::string reportedModulePath = resolver->getHumanReadableModuleName(moduleInfo.name); std::string reportedModulePath = resolver->getHumanReadableModuleName(moduleInfo.name);
reportError(TypeError{location, UnknownRequire{reportedModulePath}}); reportError(TypeError{location, UnknownRequire{reportedModulePath}});
@ -3830,7 +3877,12 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
std::optional<TypeId> moduleType = first(module->getModuleScope()->returnType); TypePackId modulePack = module->getModuleScope()->returnType;
if (FFlag::LuauModuleRequireErrorPack && get<Unifiable::Error>(modulePack))
return errorRecoveryType(scope);
std::optional<TypeId> moduleType = first(modulePack);
if (!moduleType) if (!moduleType)
{ {
std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name); std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name);
@ -3840,7 +3892,8 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks); CloneState cloneState;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState);
} }
void TypeChecker::tablify(TypeId type) void TypeChecker::tablify(TypeId type)
@ -4289,9 +4342,11 @@ ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location&
} }
// Creates a new Scope and carries forward the varargs from the parent. // 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; scope->varargPack = parent->varargPack;
currentModule->scopes.push_back(std::make_pair(location, scope)); currentModule->scopes.push_back(std::make_pair(location, scope));
@ -4326,11 +4381,6 @@ Unifier TypeChecker::mkUnifier(const Location& location)
return Unifier{&currentModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState}; return Unifier{&currentModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState};
} }
Unifier TypeChecker::mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location)
{
return Unifier{&currentModule->internalTypes, currentModule->mode, globalScope, seen, location, Variance::Covariant, unifierState};
}
TypeId TypeChecker::freshType(const ScopePtr& scope) TypeId TypeChecker::freshType(const ScopePtr& scope)
{ {
return freshType(scope->level); return freshType(scope->level);
@ -4355,22 +4405,22 @@ TypeId TypeChecker::singletonType(std::string value)
TypeId TypeChecker::errorRecoveryType(const ScopePtr& scope) TypeId TypeChecker::errorRecoveryType(const ScopePtr& scope)
{ {
return singletonTypes.errorRecoveryType(); return getSingletonTypes().errorRecoveryType();
} }
TypeId TypeChecker::errorRecoveryType(TypeId guess) TypeId TypeChecker::errorRecoveryType(TypeId guess)
{ {
return singletonTypes.errorRecoveryType(guess); return getSingletonTypes().errorRecoveryType(guess);
} }
TypePackId TypeChecker::errorRecoveryTypePack(const ScopePtr& scope) TypePackId TypeChecker::errorRecoveryTypePack(const ScopePtr& scope)
{ {
return singletonTypes.errorRecoveryTypePack(); return getSingletonTypes().errorRecoveryTypePack();
} }
TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess) TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
{ {
return singletonTypes.errorRecoveryTypePack(guess); return getSingletonTypes().errorRecoveryTypePack(guess);
} }
std::optional<TypeId> TypeChecker::filterMap(TypeId type, TypeIdPredicate predicate) 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); return errorRecoveryType(scope);
} }
if (lit->parameters.size == 0 && tf->typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf->typePackParams.empty())) if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty())
{
return tf->type; return tf->type;
}
else if (!FFlag::LuauTypeAliasPacks && lit->parameters.size != tf->typeParams.size()) if (!lit->hasParameterList && !tf->typePackParams.empty())
{ {
reportError(TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, lit->parameters.size, 0}}); reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
if (!FFlag::LuauErrorRecoveryType) if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
if (FFlag::LuauTypeAliasPacks) std::vector<TypeId> typeParams;
std::vector<TypeId> extraTypes;
std::vector<TypePackId> typePackParams;
for (size_t i = 0; i < lit->parameters.size; ++i)
{ {
if (!lit->hasParameterList && !tf->typePackParams.empty()) if (AstType* type = lit->parameters.data[i].type)
{ {
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}}); TypeId ty = resolveType(scope, *type);
if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope);
}
std::vector<TypeId> typeParams; if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
std::vector<TypeId> extraTypes; typeParams.push_back(ty);
std::vector<TypePackId> typePackParams; else if (typePackParams.empty())
extraTypes.push_back(ty);
for (size_t i = 0; i < lit->parameters.size; ++i)
{
if (AstType* type = lit->parameters.data[i].type)
{
TypeId ty = resolveType(scope, *type);
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
typeParams.push_back(ty);
else if (typePackParams.empty())
extraTypes.push_back(ty);
else
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
}
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
{
TypePackId tp = resolveTypePack(scope, *typePack);
// If we have collected an implicit type pack, materialize it
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we need more regular types, we can use single element type packs to fill those in
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
typeParams.push_back(*first(tp));
else
typePackParams.push_back(tp);
}
}
// If we still haven't meterialized an implicit type pack, do it now
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
typePackParams.push_back(addTypePack({}));
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
{
reportError(
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
if (FFlag::LuauErrorRecoveryType)
{
// Pad the types out with error recovery types
while (typeParams.size() < tf->typeParams.size())
typeParams.push_back(errorRecoveryType(scope));
while (typePackParams.size() < tf->typePackParams.size())
typePackParams.push_back(errorRecoveryTypePack(scope));
}
else else
return errorRecoveryType(scope); reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
} }
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
{ {
// If the generic parameters and the type arguments are the same, we are about to TypePackId tp = resolveTypePack(scope, *typePack);
// perform an identity substitution, which we can just short-circuit.
return tf->type; // If we have collected an implicit type pack, materialize it
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we need more regular types, we can use single element type packs to fill those in
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
typeParams.push_back(*first(tp));
else
typePackParams.push_back(tp);
} }
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
} }
else
{
std::vector<TypeId> typeParams;
for (const auto& param : lit->parameters) // If we still haven't meterialized an implicit type pack, do it now
typeParams.push_back(resolveType(scope, *param.type)); if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
typePackParams.push_back(addTypePack({}));
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
{
reportError(
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
if (FFlag::LuauErrorRecoveryType) if (FFlag::LuauErrorRecoveryType)
{ {
// If there aren't enough type parameters, pad them out with error recovery types // Pad the types out with error recovery types
// (we've already reported the error) while (typeParams.size() < tf->typeParams.size())
while (typeParams.size() < lit->parameters.size)
typeParams.push_back(errorRecoveryType(scope)); typeParams.push_back(errorRecoveryType(scope));
while (typePackParams.size() < tf->typePackParams.size())
typePackParams.push_back(errorRecoveryTypePack(scope));
} }
else
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams) return errorRecoveryType(scope);
{
// If the generic parameters and the type arguments are the same, we are about to
// perform an identity substitution, which we can just short-circuit.
return tf->type;
}
return instantiateTypeFun(scope, *tf, typeParams, {}, annotation.location);
} }
if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams)
{
// If the generic parameters and the type arguments are the same, we are about to
// perform an identity substitution, which we can just short-circuit.
return tf->type;
}
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
} }
else if (const auto& table = annotation.as<AstTypeTable>()) else if (const auto& table = annotation.as<AstTypeTable>())
{ {
@ -4608,6 +4623,7 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
else if (const auto& func = annotation.as<AstTypeFunction>()) else if (const auto& func = annotation.as<AstTypeFunction>())
{ {
ScopePtr funcScope = childScope(scope, func->location); ScopePtr funcScope = childScope(scope, func->location);
funcScope->level = scope->level.incr();
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks); 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) bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{ {
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypeVar>(ty)) if (get<GenericTypeVar>(ty))
return true; return true;
else else
return false; return false;
@ -4765,7 +4781,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
bool ApplyTypeFunction::ignoreChildren(TypePackId tp) bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
{ {
if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get<GenericTypePack>(tp)) if (get<GenericTypePack>(tp))
return true; return true;
else else
return false; return false;
@ -4788,36 +4804,26 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp)
// Really this should just replace the arguments, // Really this should just replace the arguments,
// but for bug-compatibility with existing code, we replace // but for bug-compatibility with existing code, we replace
// all generics by free type variables. // all generics by free type variables.
if (FFlag::LuauTypeAliasPacks) TypePackId& arg = typePackArguments[tp];
{ if (arg)
TypePackId& arg = typePackArguments[tp]; return arg;
if (arg)
return arg;
else
return addTypePack(FreeTypePack{level});
}
else else
{
return addTypePack(FreeTypePack{level}); return addTypePack(FreeTypePack{level});
}
} }
TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams, TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& typePackParams, const Location& location) const std::vector<TypePackId>& typePackParams, const Location& location)
{ {
if (tf.typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf.typePackParams.empty())) if (tf.typeParams.empty() && tf.typePackParams.empty())
return tf.type; return tf.type;
applyTypeFunction.typeArguments.clear(); applyTypeFunction.typeArguments.clear();
for (size_t i = 0; i < tf.typeParams.size(); ++i) for (size_t i = 0; i < tf.typeParams.size(); ++i)
applyTypeFunction.typeArguments[tf.typeParams[i]] = typeParams[i]; applyTypeFunction.typeArguments[tf.typeParams[i]] = typeParams[i];
if (FFlag::LuauTypeAliasPacks) applyTypeFunction.typePackArguments.clear();
{ for (size_t i = 0; i < tf.typePackParams.size(); ++i)
applyTypeFunction.typePackArguments.clear(); applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i];
}
applyTypeFunction.currentModule = currentModule; applyTypeFunction.currentModule = currentModule;
applyTypeFunction.level = scope->level; applyTypeFunction.level = scope->level;
@ -4866,9 +4872,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
if (ttv) if (ttv)
{ {
ttv->instantiatedTypeParams = typeParams; ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = typePackParams;
} }
} }
else else
@ -4884,9 +4888,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
} }
ttv->instantiatedTypeParams = typeParams; ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams;
if (FFlag::LuauTypeAliasPacks)
ttv->instantiatedTypePackParams = typePackParams;
} }
} }
@ -4914,7 +4916,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
} }
TypeId g; TypeId g;
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks) if (FFlag::LuauRecursiveTypeParameterRestriction)
{ {
TypeId& cached = scope->parent->typeAliasTypeParameters[n]; TypeId& cached = scope->parent->typeAliasTypeParameters[n];
if (!cached) if (!cached)
@ -4944,7 +4946,7 @@ std::pair<std::vector<TypeId>, std::vector<TypePackId>> TypeChecker::createGener
} }
TypePackId g; TypePackId g;
if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks) if (FFlag::LuauRecursiveTypeParameterRestriction)
{ {
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n]; TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
if (!cached) if (!cached)
@ -5245,7 +5247,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
auto typeFun = globalScope->lookupType(typeguardP.kind); auto typeFun = globalScope->lookupType(typeguardP.kind);
if (!typeFun || !typeFun->typeParams.empty() || (FFlag::LuauTypeAliasPacks && !typeFun->typePackParams.empty())) if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
TypeId type = follow(typeFun->type); TypeId type = follow(typeFun->type);

View file

@ -19,7 +19,7 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globa
TypeId unwrapped = follow(*metatable); TypeId unwrapped = follow(*metatable);
if (get<AnyTypeVar>(unwrapped)) if (get<AnyTypeVar>(unwrapped))
return singletonTypes.anyType; return getSingletonTypes().anyType;
const TableTypeVar* mtt = getTableType(unwrapped); const TableTypeVar* mtt = getTableType(unwrapped);
if (!mtt) if (!mtt)
@ -61,12 +61,12 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const Sc
{ {
std::optional<TypeId> r = first(follow(itf->retType)); std::optional<TypeId> r = first(follow(itf->retType));
if (!r) if (!r)
return singletonTypes.nilType; return getSingletonTypes().nilType;
else else
return *r; return *r;
} }
else if (get<AnyTypeVar>(index)) else if (get<AnyTypeVar>(index))
return singletonTypes.anyType; return getSingletonTypes().anyType;
else else
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}}); errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});

View file

@ -19,9 +19,9 @@
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false) LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false)
LUAU_FASTFLAG(LuauErrorRecoveryType) LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAG(DebugLuauFreezeArena)
namespace Luau namespace Luau
{ {
@ -580,11 +580,25 @@ SingletonTypes::SingletonTypes()
, arena(new TypeArena) , arena(new TypeArena)
{ {
TypeId stringMetatable = makeStringMetatable(); TypeId stringMetatable = makeStringMetatable();
stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, makeStringMetatable()}; stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, stringMetatable};
persist(stringMetatable); persist(stringMetatable);
debugFreezeArena = FFlag::DebugLuauFreezeArena;
freeze(*arena); 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() TypeId SingletonTypes::makeStringMetatable()
{ {
const TypeId optionalNumber = arena->addType(UnionTypeVar{{nilType, numberType}}); 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}); 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}); return arena->addType(TableTypeVar{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
} }
@ -671,7 +688,11 @@ TypePackId SingletonTypes::errorRecoveryTypePack(TypePackId guess)
return &errorTypePack_; return &errorTypePack_;
} }
SingletonTypes singletonTypes; SingletonTypes& getSingletonTypes()
{
static SingletonTypes singletonTypes;
return singletonTypes;
}
void persist(TypeId ty) void persist(TypeId ty)
{ {
@ -720,6 +741,18 @@ void persist(TypeId ty)
for (TypeId opt : itv->parts) for (TypeId opt : itv->parts)
queue.push_back(opt); 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) if (p->tail)
persist(*p->tail); persist(*p->tail);
} }
} else if (auto vtp = get<VariadicTypePack>(tp))
namespace
{
struct StateDot
{
StateDot(ToDotOptions opts)
: opts(opts)
{ {
persist(vtp->ty);
} }
else if (get<GenericTypePack>(tp))
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 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) const TypeLevel* getLevel(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
@ -1121,167 +802,6 @@ TypeLevel* getMutableLevel(TypeId ty)
return const_cast<TypeLevel*>(getLevel(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) const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name)
{ {
while (cls) while (cls)
@ -1317,16 +837,6 @@ bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent)
return false; 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) UnionTypeVarIterator::UnionTypeVarIterator(const UnionTypeVar* utv)
{ {
LUAU_ASSERT(utv); LUAU_ASSERT(utv);

View file

@ -14,20 +14,90 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000); LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance, false); LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false) LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false)
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false) LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false) LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false)
LUAU_FASTFLAGVARIABLE(LuauTypecheckOpts, false)
LUAU_FASTFLAG(LuauShareTxnSeen);
LUAU_FASTFLAGVARIABLE(LuauCacheUnifyTableResults, false)
LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false) LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false)
LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false) LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false)
LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAG(LuauProperTypeLevels);
LUAU_FASTFLAGVARIABLE(LuauExtendedUnionMismatchError, false)
LUAU_FASTFLAGVARIABLE(LuauExtendedFunctionMismatchError, false)
namespace Luau 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 struct SkipCacheForType
{ {
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType) SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType)
@ -130,44 +200,48 @@ static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
return *it; 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) Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState)
: types(types) : types(types)
, mode(mode) , mode(mode)
, globalScope(std::move(globalScope)) , globalScope(std::move(globalScope))
, location(location) , location(location)
, variance(variance) , variance(variance)
, counters(&countersData)
, counters_DEPRECATED(std::make_shared<UnifierCounters>())
, sharedState(sharedState)
{
LUAU_ASSERT(sharedState.iceHandler);
}
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& ownedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters)
: types(types)
, mode(mode)
, globalScope(std::move(globalScope))
, log(ownedSeen)
, location(location)
, variance(variance)
, counters(counters ? counters : &countersData)
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
, sharedState(sharedState) , sharedState(sharedState)
{ {
LUAU_ASSERT(sharedState.iceHandler); LUAU_ASSERT(sharedState.iceHandler);
} }
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location, Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, const std::shared_ptr<UnifierCounters>& counters_DEPRECATED, UnifierCounters* counters) Variance variance, UnifierSharedState& sharedState)
: types(types) : types(types)
, mode(mode) , mode(mode)
, globalScope(std::move(globalScope)) , globalScope(std::move(globalScope))
, log(sharedSeen) , log(sharedSeen)
, location(location) , location(location)
, variance(variance) , variance(variance)
, counters(counters ? counters : &countersData)
, counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared<UnifierCounters>())
, sharedState(sharedState) , sharedState(sharedState)
{ {
LUAU_ASSERT(sharedState.iceHandler); LUAU_ASSERT(sharedState.iceHandler);
@ -175,26 +249,18 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<
void Unifier::tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection) void Unifier::tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
{ {
if (FFlag::LuauTypecheckOpts) sharedState.counters.iterationCount = 0;
counters->iterationCount = 0;
else
counters_DEPRECATED->iterationCount = 0;
tryUnify_(superTy, subTy, isFunctionCall, isIntersection); tryUnify_(superTy, subTy, isFunctionCall, isIntersection);
} }
void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection) void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection)
{ {
RecursionLimiter _ra( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
if (FFlag::LuauTypecheckOpts) ++sharedState.counters.iterationCount;
++counters->iterationCount;
else
++counters_DEPRECATED->iterationCount;
if (FInt::LuauTypeInferIterationLimit > 0 && if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
{ {
errors.push_back(TypeError{location, UnificationTooComplex{}}); errors.push_back(TypeError{location, UnificationTooComplex{}});
return; return;
@ -244,9 +310,11 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
{ {
occursCheck(superTy, subTy); occursCheck(superTy, subTy);
TypeLevel superLevel = l->level;
// Unification can't change the level of a generic. // Unification can't change the level of a generic.
auto rightGeneric = get<GenericTypeVar>(subTy); 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 // TODO: a more informative error message? CLI-39912
errors.push_back(TypeError{location, GenericError{"Generic subtype escaping scope"}}); 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 // The occurrence check might have caused superTy no longer to be a free type
if (!get<ErrorTypeVar>(superTy)) 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)) if (!rightLevel->subsumes(l->level))
*rightLevel = l->level; *rightLevel = l->level;
@ -270,6 +340,8 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
} }
else if (r) else if (r)
{ {
TypeLevel subLevel = r->level;
occursCheck(subTy, superTy); occursCheck(subTy, superTy);
// Unification can't change the level of a generic. // 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 (!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)) if (!superLevel->subsumes(r->level))
*leftLevel = r->level; {
log(superTy);
*superLevel = r->level;
}
} }
log(subTy); log(subTy);
@ -302,7 +380,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy)) if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
return tryUnifyWithAny(subTy, superTy); return tryUnifyWithAny(subTy, superTy);
bool cacheEnabled = FFlag::LuauCacheUnifyTableResults && !isFunctionCall && !isIntersection; bool cacheEnabled = !isFunctionCall && !isIntersection;
auto& cache = sharedState.cachedUnify; auto& cache = sharedState.cachedUnify;
// What if the types are immutable and we proved their relation before // What if the types are immutable and we proved their relation before
@ -357,7 +435,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
else if (failed) else if (failed)
{ {
if (FFlag::LuauExtendedTypeMismatchError && firstFailedOption) 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 else
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}}); 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; bool found = false;
std::optional<TypeError> unificationTooComplex; std::optional<TypeError> unificationTooComplex;
size_t failedOptionCount = 0;
std::optional<TypeError> failedOption;
bool foundHeuristic = false;
size_t startIndex = 0; size_t startIndex = 0;
if (FFlag::LuauUnionHeuristic) if (FFlag::LuauUnionHeuristic)
{ {
bool found = false; if (const std::string* subName = getName(subTy))
const std::string* subName = getName(subTy);
if (subName)
{ {
for (size_t i = 0; i < uv->options.size(); ++i) for (size_t i = 0; i < uv->options.size(); ++i)
{ {
const std::string* optionName = getName(uv->options[i]); const std::string* optionName = getName(uv->options[i]);
if (optionName && *optionName == *subName) if (optionName && *optionName == *subName)
{ {
found = true; foundHeuristic = true;
startIndex = i; startIndex = i;
break; 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) 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; unificationTooComplex = e;
} }
else if (FFlag::LuauExtendedUnionMismatchError && !isNil(type))
{
failedOptionCount++;
if (!failedOption)
failedOption = {innerState.errors.front()};
}
innerState.log.rollback(); innerState.log.rollback();
} }
if (unificationTooComplex) if (unificationTooComplex)
{
errors.push_back(*unificationTooComplex); errors.push_back(*unificationTooComplex);
}
else if (!found) 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"}}); errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "none of the union options are compatible"}});
else else
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}}); errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
@ -461,7 +569,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
if (unificationTooComplex) if (unificationTooComplex)
errors.push_back(*unificationTooComplex); errors.push_back(*unificationTooComplex);
else if (firstFailedOption) 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 else
{ {
@ -563,8 +671,6 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool
void Unifier::cacheResult(TypeId superTy, TypeId subTy) void Unifier::cacheResult(TypeId superTy, TypeId subTy)
{ {
LUAU_ASSERT(FFlag::LuauCacheUnifyTableResults);
bool* superTyInfo = sharedState.skipCacheForType.find(superTy); bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
if (superTyInfo && *superTyInfo) if (superTyInfo && *superTyInfo)
@ -686,10 +792,7 @@ ErrorVec Unifier::canUnify(TypePackId superTy, TypePackId subTy, bool isFunction
void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall) void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
{ {
if (FFlag::LuauTypecheckOpts) sharedState.counters.iterationCount = 0;
counters->iterationCount = 0;
else
counters_DEPRECATED->iterationCount = 0;
tryUnify_(superTp, subTp, isFunctionCall); tryUnify_(superTp, subTp, isFunctionCall);
} }
@ -700,16 +803,11 @@ void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall
*/ */
void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCall) void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCall)
{ {
RecursionLimiter _ra( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
if (FFlag::LuauTypecheckOpts) ++sharedState.counters.iterationCount;
++counters->iterationCount;
else
++counters_DEPRECATED->iterationCount;
if (FInt::LuauTypeInferIterationLimit > 0 && if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount))
{ {
errors.push_back(TypeError{location, UnificationTooComplex{}}); errors.push_back(TypeError{location, UnificationTooComplex{}});
return; return;
@ -811,6 +909,10 @@ void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCal
if (superIter.good() && subIter.good()) if (superIter.good() && subIter.good())
{ {
tryUnify_(*superIter, *subIter); tryUnify_(*superIter, *subIter);
if (FFlag::LuauExtendedFunctionMismatchError && !errors.empty() && !firstPackErrorPos)
firstPackErrorPos = loopCount;
superIter.advance(); superIter.advance();
subIter.advance(); subIter.advance();
continue; continue;
@ -893,13 +995,13 @@ void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCal
while (superIter.good()) while (superIter.good())
{ {
tryUnify_(singletonTypes.errorRecoveryType(), *superIter); tryUnify_(getSingletonTypes().errorRecoveryType(), *superIter);
superIter.advance(); superIter.advance();
} }
while (subIter.good()) while (subIter.good())
{ {
tryUnify_(singletonTypes.errorRecoveryType(), *subIter); tryUnify_(getSingletonTypes().errorRecoveryType(), *subIter);
subIter.advance(); subIter.advance();
} }
@ -957,14 +1059,22 @@ void Unifier::tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCal
if (numGenerics != rf->generics.size()) if (numGenerics != rf->generics.size())
{ {
numGenerics = std::min(lf->generics.size(), 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(); size_t numGenericPacks = lf->genericPacks.size();
if (numGenericPacks != rf->genericPacks.size()) if (numGenericPacks != rf->genericPacks.size())
{ {
numGenericPacks = std::min(lf->genericPacks.size(), 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++) for (size_t i = 0; i < numGenerics; i++)
@ -976,13 +1086,49 @@ void Unifier::tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCal
{ {
Unifier innerState = makeChildUnifier(); Unifier innerState = makeChildUnifier();
ctx = CountMismatch::Arg; if (FFlag::LuauExtendedFunctionMismatchError)
innerState.tryUnify_(rf->argTypes, lf->argTypes, isFunctionCall); {
innerState.ctx = CountMismatch::Arg;
innerState.tryUnify_(rf->argTypes, lf->argTypes, isFunctionCall);
ctx = CountMismatch::Result; bool reported = !innerState.errors.empty();
innerState.tryUnify_(lf->retType, rf->retType);
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)); log.concat(std::move(innerState.log));
} }
@ -1034,7 +1180,7 @@ struct Resetter
void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection) void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
{ {
if (!FFlag::LuauTableSubtypingVariance) if (!FFlag::LuauTableSubtypingVariance2)
return DEPRECATED_tryUnifyTables(left, right, isIntersection); return DEPRECATED_tryUnifyTables(left, right, isIntersection);
TableTypeVar* lt = getMutable<TableTypeVar>(left); 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. // TODO: hopefully readonly/writeonly properties will fix this.
Property clone = prop; Property clone = prop;
clone.type = deeplyOptional(clone.type); clone.type = deeplyOptional(clone.type);
log(lt); log(left);
lt->props[name] = clone; lt->props[name] = clone;
} }
else if (variance == Covariant) else if (variance == Covariant)
@ -1186,7 +1332,7 @@ void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
} }
else if (lt->state == TableState::Free) else if (lt->state == TableState::Free)
{ {
log(lt); log(left);
lt->props[name] = prop; lt->props[name] = prop;
} }
else 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. // 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 // TODO: we only need to do this if the supertype's indexer is read/write
// since that can add indexed elements. // since that can add indexed elements.
log(rt); log(right);
rt->indexer = lt->indexer; rt->indexer = lt->indexer;
} }
} }
@ -1225,7 +1371,7 @@ void Unifier::tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
// Symmetric if we are invariant // Symmetric if we are invariant
if (lt->state == TableState::Unsealed || lt->state == TableState::Free) if (lt->state == TableState::Unsealed || lt->state == TableState::Free)
{ {
log(lt); log(left);
lt->indexer = rt->indexer; lt->indexer = rt->indexer;
} }
} }
@ -1281,15 +1427,15 @@ TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> see
TableTypeVar* resultTtv = getMutable<TableTypeVar>(result); TableTypeVar* resultTtv = getMutable<TableTypeVar>(result);
for (auto& [name, prop] : resultTtv->props) for (auto& [name, prop] : resultTtv->props)
prop.type = deeplyOptional(prop.type, seen); prop.type = deeplyOptional(prop.type, seen);
return types->addType(UnionTypeVar{{singletonTypes.nilType, result}}); return types->addType(UnionTypeVar{{getSingletonTypes().nilType, result}});
} }
else 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) void Unifier::DEPRECATED_tryUnifyTables(TypeId left, TypeId right, bool isIntersection)
{ {
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance); LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
Resetter resetter{&variance}; Resetter resetter{&variance};
variance = Invariant; variance = Invariant;
@ -1507,7 +1653,7 @@ void Unifier::tryUnifySealedTables(TypeId left, TypeId right, bool isIntersectio
} }
else if (lt->indexer) 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. // 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. // Skip those and just look for the ones remaining and see if they fit into the indexer.
for (const auto& [name, type] : rt->props) for (const auto& [name, type] : rt->props)
@ -1676,7 +1822,7 @@ void Unifier::tryUnifyWithClass(TypeId superTy, TypeId subTy, bool reversed)
ok = false; ok = false;
errors.push_back(TypeError{location, UnknownProperty{superTy, propName}}); errors.push_back(TypeError{location, UnknownProperty{superTy, propName}});
if (!FFlag::LuauExtendedClassMismatchError) if (!FFlag::LuauExtendedClassMismatchError)
tryUnify_(prop.type, singletonTypes.errorRecoveryType()); tryUnify_(prop.type, getSingletonTypes().errorRecoveryType());
} }
else else
{ {
@ -1727,39 +1873,8 @@ void Unifier::tryUnify(const TableIndexer& superIndexer, const TableIndexer& sub
tryUnify_(superIndexer.indexResultType, subIndexer.indexResultType); tryUnify_(superIndexer.indexResultType, subIndexer.indexResultType);
} }
static void queueTypePack_DEPRECATED(
std::vector<TypeId>& queue, std::unordered_set<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOpts);
while (true)
{
a = follow(a);
if (seenTypePacks.count(a))
break;
seenTypePacks.insert(a);
if (get<Unifiable::Free>(a))
{
state.log(a);
*asMutable(a) = Unifiable::Bound{anyTypePack};
}
else if (auto tp = get<TypePack>(a))
{
queue.insert(queue.end(), tp->head.begin(), tp->head.end());
if (tp->tail)
a = *tp->tail;
else
break;
}
}
}
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack) static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{ {
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
while (true) while (true)
{ {
a = follow(a); a = follow(a);
@ -1837,66 +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, static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
TypeId anyType, TypePackId anyTypePack) TypeId anyType, TypePackId anyTypePack)
{ {
LUAU_ASSERT(FFlag::LuauTypecheckOpts);
while (!queue.empty()) while (!queue.empty())
{ {
TypeId ty = follow(queue.back()); TypeId ty = follow(queue.back());
@ -1949,83 +2007,36 @@ void Unifier::tryUnifyWithAny(TypeId any, TypeId ty)
{ {
LUAU_ASSERT(get<AnyTypeVar>(any) || get<ErrorTypeVar>(any)); LUAU_ASSERT(get<AnyTypeVar>(any) || get<ErrorTypeVar>(any));
if (FFlag::LuauTypecheckOpts) // These types are not visited in general loop below
{ if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
// These types are not visited in general loop below return;
if (get<PrimitiveTypeVar>(ty) || get<AnyTypeVar>(ty) || get<ClassTypeVar>(ty))
return;
}
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes.anyType}}); const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
const TypePackId anyTP = get<AnyTypeVar>(any) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}}); const TypePackId anyTP = get<AnyTypeVar>(any) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
if (FFlag::LuauTypecheckOpts) std::vector<TypeId> queue = {ty};
{
std::vector<TypeId> queue = {ty};
if (FFlag::LuauCacheUnifyTableResults) sharedState.tempSeenTy.clear();
{ sharedState.tempSeenTp.clear();
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP); Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, getSingletonTypes().anyType, anyTP);
}
else
{
tempSeenTy_DEPRECATED.clear();
tempSeenTp_DEPRECATED.clear();
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, singletonTypes.anyType, anyTP);
}
}
else
{
std::unordered_set<TypePackId> seenTypePacks;
std::vector<TypeId> queue = {ty};
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, singletonTypes.anyType, anyTP);
}
} }
void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty) void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty)
{ {
LUAU_ASSERT(get<Unifiable::Error>(any)); 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); Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any);
}
else
{
tempSeenTy_DEPRECATED.clear();
tempSeenTp_DEPRECATED.clear();
queueTypePack(queue, tempSeenTp_DEPRECATED, *this, ty, any);
Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, anyTy, any);
}
}
else
{
std::unordered_set<TypePackId> seenTypePacks;
std::vector<TypeId> queue;
queueTypePack_DEPRECATED(queue, seenTypePacks, *this, ty, any);
Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, anyTy, any);
}
} }
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name) std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
@ -2035,46 +2046,22 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
void Unifier::occursCheck(TypeId needle, TypeId haystack) void Unifier::occursCheck(TypeId needle, TypeId haystack)
{ {
std::unordered_set<TypeId> seen_DEPRECATED; sharedState.tempSeenTy.clear();
if (FFlag::LuauCacheUnifyTableResults) return occursCheck(sharedState.tempSeenTy, needle, haystack);
{
if (FFlag::LuauTypecheckOpts)
sharedState.tempSeenTy.clear();
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTy, needle, haystack);
}
else
{
if (FFlag::LuauTypecheckOpts)
tempSeenTy_DEPRECATED.clear();
return occursCheck(seen_DEPRECATED, tempSeenTy_DEPRECATED, needle, haystack);
}
} }
void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack) void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
{ {
RecursionLimiter _ra( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
needle = follow(needle); needle = follow(needle);
haystack = follow(haystack); haystack = follow(haystack);
if (FFlag::LuauTypecheckOpts) if (seen.find(haystack))
{ return;
if (seen.find(haystack))
return;
seen.insert(haystack); seen.insert(haystack);
}
else
{
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
return;
seen_DEPRECATED.insert(haystack);
}
if (get<Unifiable::Error>(needle)) if (get<Unifiable::Error>(needle))
return; return;
@ -2086,12 +2073,12 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
{ {
errors.push_back(TypeError{location, OccursCheckFailed{}}); errors.push_back(TypeError{location, OccursCheckFailed{}});
log(needle); log(needle);
*asMutable(needle) = *singletonTypes.errorRecoveryType(); *asMutable(needle) = *getSingletonTypes().errorRecoveryType();
return; return;
} }
auto check = [&](TypeId tv) { auto check = [&](TypeId tv) {
occursCheck(seen_DEPRECATED, seen, needle, tv); occursCheck(seen, needle, tv);
}; };
if (get<FreeTypeVar>(haystack)) if (get<FreeTypeVar>(haystack))
@ -2121,43 +2108,20 @@ void Unifier::occursCheck(std::unordered_set<TypeId>& seen_DEPRECATED, DenseHash
void Unifier::occursCheck(TypePackId needle, TypePackId haystack) void Unifier::occursCheck(TypePackId needle, TypePackId haystack)
{ {
std::unordered_set<TypePackId> seen_DEPRECATED; sharedState.tempSeenTp.clear();
if (FFlag::LuauCacheUnifyTableResults) return occursCheck(sharedState.tempSeenTp, needle, haystack);
{
if (FFlag::LuauTypecheckOpts)
sharedState.tempSeenTp.clear();
return occursCheck(seen_DEPRECATED, sharedState.tempSeenTp, needle, haystack);
}
else
{
if (FFlag::LuauTypecheckOpts)
tempSeenTp_DEPRECATED.clear();
return occursCheck(seen_DEPRECATED, tempSeenTp_DEPRECATED, needle, haystack);
}
} }
void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack) void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
{ {
needle = follow(needle); needle = follow(needle);
haystack = follow(haystack); haystack = follow(haystack);
if (FFlag::LuauTypecheckOpts) if (seen.find(haystack))
{ return;
if (seen.find(haystack))
return;
seen.insert(haystack); seen.insert(haystack);
}
else
{
if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack))
return;
seen_DEPRECATED.insert(haystack);
}
if (get<Unifiable::Error>(needle)) if (get<Unifiable::Error>(needle))
return; return;
@ -2165,8 +2129,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
if (!get<Unifiable::Free>(needle)) if (!get<Unifiable::Free>(needle))
ice("Expected needle pack to be free"); ice("Expected needle pack to be free");
RecursionLimiter _ra( RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit);
while (!get<ErrorTypeVar>(haystack)) while (!get<ErrorTypeVar>(haystack))
{ {
@ -2174,7 +2137,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
{ {
errors.push_back(TypeError{location, OccursCheckFailed{}}); errors.push_back(TypeError{location, OccursCheckFailed{}});
log(needle); log(needle);
*asMutable(needle) = *singletonTypes.errorRecoveryTypePack(); *asMutable(needle) = *getSingletonTypes().errorRecoveryTypePack();
return; return;
} }
@ -2186,8 +2149,8 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
{ {
if (auto f = get<FunctionTypeVar>(follow(ty))) if (auto f = get<FunctionTypeVar>(follow(ty)))
{ {
occursCheck(seen_DEPRECATED, seen, needle, f->argTypes); occursCheck(seen, needle, f->argTypes);
occursCheck(seen_DEPRECATED, seen, needle, f->retType); occursCheck(seen, needle, f->retType);
} }
} }
} }
@ -2204,10 +2167,7 @@ void Unifier::occursCheck(std::unordered_set<TypePackId>& seen_DEPRECATED, Dense
Unifier Unifier::makeChildUnifier() Unifier Unifier::makeChildUnifier()
{ {
if (FFlag::LuauShareTxnSeen) return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState};
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
else
return Unifier{types, mode, globalScope, log.ownedSeen, location, variance, sharedState, counters_DEPRECATED, counters};
} }
bool Unifier::isNonstrictMode() const bool Unifier::isNonstrictMode() const
@ -2231,7 +2191,7 @@ void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const s
errors.push_back(*e); errors.push_back(*e);
else if (!innerErrors.empty()) else if (!innerErrors.empty())
errors.push_back( 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) void Unifier::ice(const std::string& message, const Location& location)

View file

@ -10,11 +10,8 @@
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false) LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false)
LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false) LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAliasPacks, false)
LUAU_FASTFLAGVARIABLE(LuauParseTypePackTypeParameters, false)
LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false) LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false) LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false)
@ -161,7 +158,7 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
{ {
std::vector<std::string> hotcomments; 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; const char* text = p.lexer.current().data;
unsigned int length = p.lexer.current().length; unsigned int length = p.lexer.current().length;
@ -782,8 +779,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
AstType* type = parseTypeAnnotation(); AstType* type = parseTypeAnnotation();
return allocator.alloc<AstStatTypeAlias>( return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, generics, genericPacks, type, exported);
Location(start, type->location), name->name, generics, FFlag::LuauTypeAliasPacks ? genericPacks : AstArray<AstName>{}, type, exported);
} }
AstDeclaredClassProp Parser::parseDeclaredClassMethod() AstDeclaredClassProp Parser::parseDeclaredClassMethod()
@ -1602,30 +1598,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}}; return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
} }
if (FFlag::LuauParseTypePackTypeParameters) bool hasParameters = false;
AstArray<AstTypeOrPack> parameters{};
if (lexer.current().type == '<')
{ {
bool hasParameters = false; hasParameters = true;
AstArray<AstTypeOrPack> parameters{}; parameters = parseTypeParams();
if (lexer.current().type == '<')
{
hasParameters = true;
parameters = parseTypeParams();
}
Location end = lexer.previousLocation();
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
} }
else
{
AstArray<AstTypeOrPack> generics = parseTypeParams();
Location end = lexer.previousLocation(); Location end = lexer.previousLocation();
// false in 'hasParameterList' as it is not used without FFlagLuauTypeAliasPacks return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, false, generics), {}};
}
} }
else if (lexer.current().type == '{') else if (lexer.current().type == '{')
{ {
@ -2414,37 +2398,24 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
while (true) while (true)
{ {
if (FFlag::LuauParseTypePackTypeParameters) if (shouldParseTypePackAnnotation(lexer))
{ {
if (shouldParseTypePackAnnotation(lexer)) auto typePack = parseTypePackAnnotation();
{
auto typePack = parseTypePackAnnotation();
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them parameters.push_back({{}, typePack});
parameters.push_back({{}, typePack}); }
} else if (lexer.current().type == '(')
else if (lexer.current().type == '(') {
{ auto [type, typePack] = parseTypeOrPackAnnotation();
auto [type, typePack] = parseTypeOrPackAnnotation();
if (typePack) if (typePack)
{ parameters.push_back({{}, typePack});
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
parameters.push_back({{}, typePack});
}
else
{
parameters.push_back({type, {}});
}
}
else if (lexer.current().type == '>' && parameters.empty())
{
break;
}
else else
{ parameters.push_back({type, {}});
parameters.push_back({parseTypeAnnotation(), {}}); }
} else if (lexer.current().type == '>' && parameters.empty())
{
break;
} }
else else
{ {
@ -2808,7 +2779,7 @@ const Lexeme& Parser::nextLexeme()
const Lexeme& lexeme = lexer.next(/*skipComments*/ false); 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. // 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. // 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}); commentLocations.push_back(Comment{lexeme.type, lexeme.location});
if (isComment(lexeme)) if (isComment(lexeme))
commentLocations.push_back(Comment{lexeme.type, lexeme.location}); commentLocations.push_back(Comment{lexeme.type, lexeme.location});

View file

@ -11,26 +11,33 @@
enum class ReportFormat enum class ReportFormat
{ {
Default, 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) switch (format)
{ {
case ReportFormat::Default: 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; break;
case ReportFormat::Luacheck: case ReportFormat::Luacheck:
{ {
// Note: luacheck's end column is inclusive but our end column is exclusive // 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 // 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; 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("\n");
printf("Available options:\n"); printf("Available options:\n");
printf(" --formatter=plain: report analysis errors in Luacheck-compatible format\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) 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>()) if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
{ {
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau"; Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau";
if (!moduleExists(name)) if (!readFile(name))
{ {
// fall back to .lua if a module with .luau doesn't exist // fall back to .lua if a module with .luau doesn't exist
name = std::string(expr->value.data, expr->value.size) + ".lua"; name = std::string(expr->value.data, expr->value.size) + ".lua";
@ -132,27 +140,6 @@ struct CliFileResolver : Luau::FileResolver
return std::nullopt; return std::nullopt;
} }
bool moduleExists(const Luau::ModuleName& name) const override
{
return !!readFile(name);
}
std::optional<Luau::ModuleName> fromAstFragment(Luau::AstExpr* expr) const override
{
return std::nullopt;
}
Luau::ModuleName concat(const Luau::ModuleName& lhs, std::string_view rhs) const override
{
return lhs + "/" + std::string(rhs);
}
std::optional<Luau::ModuleName> getParentModuleName(const Luau::ModuleName& name) const override
{
return std::nullopt;
}
}; };
struct CliConfigResolver : Luau::ConfigResolver struct CliConfigResolver : Luau::ConfigResolver
@ -222,6 +209,8 @@ int main(int argc, char** argv)
if (strcmp(argv[i], "--formatter=plain") == 0) if (strcmp(argv[i], "--formatter=plain") == 0)
format = ReportFormat::Luacheck; format = ReportFormat::Luacheck;
else if (strcmp(argv[i], "--formatter=gnu") == 0)
format = ReportFormat::Gnu;
else if (strcmp(argv[i], "--annotate") == 0) else if (strcmp(argv[i], "--annotate") == 0)
annotate = true; annotate = true;
} }

88
CLI/Coverage.cpp Normal file
View 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
View 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);

View file

@ -67,6 +67,10 @@ std::optional<std::string> readFile(const std::string& name)
if (read != size_t(length)) if (read != size_t(length))
return std::nullopt; 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; return result;
} }

View file

@ -110,12 +110,12 @@ void profilerStop()
gProfiler.thread.join(); 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) if (!f)
{ {
fprintf(stderr, "Error opening profile %s\n", name); fprintf(stderr, "Error opening profile %s\n", path);
return; return;
} }
@ -129,7 +129,7 @@ void profilerDump(const char* name)
fclose(f); 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())); static_cast<long long>(gProfiler.samples.load()), static_cast<long long>(gProfiler.data.size()));
uint64_t totalgc = 0; uint64_t totalgc = 0;

View file

@ -5,4 +5,4 @@ struct lua_State;
void profilerStart(lua_State* L, int frequency); void profilerStart(lua_State* L, int frequency);
void profilerStop(); void profilerStop();
void profilerDump(const char* name); void profilerDump(const char* path);

View file

@ -8,6 +8,7 @@
#include "FileUtils.h" #include "FileUtils.h"
#include "Profiler.h" #include "Profiler.h"
#include "Coverage.h"
#include "linenoise.hpp" #include "linenoise.hpp"
@ -24,6 +25,16 @@ enum class CompileFormat
Binary 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) static int lua_loadstring(lua_State* L)
{ {
size_t l = 0; size_t l = 0;
@ -32,7 +43,7 @@ static int lua_loadstring(lua_State* L)
lua_setsafeenv(L, LUA_ENVIRONINDEX, false); 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) if (luau_load(L, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
return 1; return 1;
@ -79,9 +90,12 @@ static int lua_require(lua_State* L)
luaL_sandboxthread(ML); luaL_sandboxthread(ML);
// now we can compile & run module on the new thread // 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 (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
{ {
if (coverageActive())
coverageTrack(ML, -1);
int status = lua_resume(ML, L, 0); int status = lua_resume(ML, L, 0);
if (status == 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) 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) 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 chunkname = "=" + std::string(name);
std::string bytecode = Luau::compile(*source); std::string bytecode = Luau::compile(*source, copts());
int status = 0; int status = 0;
if (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 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); status = lua_resume(L, NULL, 0);
} }
else else
@ -437,6 +454,7 @@ static void displayHelp(const char* argv0)
printf("\n"); printf("\n");
printf("Available options:\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(" --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) static int assertionHandler(const char* expr, const char* file, int line)
@ -495,6 +513,7 @@ int main(int argc, char** argv)
setupState(L); setupState(L);
int profile = 0; int profile = 0;
bool coverage = false;
for (int i = 1; i < argc; ++i) for (int i = 1; i < argc; ++i)
{ {
@ -505,11 +524,16 @@ int main(int argc, char** argv)
profile = 10000; // default to 10 KHz profile = 10000; // default to 10 KHz
else if (strncmp(argv[i], "--profile=", 10) == 0) else if (strncmp(argv[i], "--profile=", 10) == 0)
profile = atoi(argv[i] + 10); profile = atoi(argv[i] + 10);
else if (strcmp(argv[i], "--coverage") == 0)
coverage = true;
} }
if (profile) if (profile)
profilerStart(L, profile); profilerStart(L, profile);
if (coverage)
coverageInit(L);
std::vector<std::string> files = getSourceFiles(argc, argv); std::vector<std::string> files = getSourceFiles(argc, argv);
int failed = 0; int failed = 0;
@ -523,7 +547,9 @@ int main(int argc, char** argv)
profilerDump("profile.out"); profilerDump("profile.out");
} }
if (coverage)
coverageDump("coverage.out");
return failed; return failed;
} }
} }

View file

@ -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. 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. 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. 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 ## Code style

View file

@ -10,7 +10,6 @@
#include <bitset> #include <bitset>
#include <math.h> #include <math.h>
LUAU_FASTFLAGVARIABLE(LuauPreloadClosures, false)
LUAU_FASTFLAG(LuauIfElseExpressionBaseSupport) LUAU_FASTFLAG(LuauIfElseExpressionBaseSupport)
LUAU_FASTFLAGVARIABLE(LuauBit32CountBuiltin, false) LUAU_FASTFLAGVARIABLE(LuauBit32CountBuiltin, false)
@ -321,13 +320,15 @@ struct Compiler
compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i)); compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i));
} }
setDebugLine(expr->func); setDebugLineEnd(expr->func);
if (expr->self) if (expr->self)
{ {
AstExprIndexName* fi = expr->func->as<AstExprIndexName>(); AstExprIndexName* fi = expr->func->as<AstExprIndexName>();
LUAU_ASSERT(fi); LUAU_ASSERT(fi);
setDebugLine(fi->indexLocation);
BytecodeBuilder::StringRef iname = sref(fi->index); BytecodeBuilder::StringRef iname = sref(fi->index);
int32_t cid = bytecode.addConstantString(iname); int32_t cid = bytecode.addConstantString(iname);
if (cid < 0) if (cid < 0)
@ -460,20 +461,17 @@ struct Compiler
bool shared = false; 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 int32_t cid = bytecode.addConstantClosure(f->id);
// 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);
if (cid >= 0 && cid < 32768) if (cid >= 0 && cid < 32768)
{ {
bytecode.emitAD(LOP_DUPCLOSURE, target, cid); bytecode.emitAD(LOP_DUPCLOSURE, target, cid);
shared = true; shared = true;
}
} }
} }
@ -1313,6 +1311,8 @@ struct Compiler
RegScope rs(this); RegScope rs(this);
uint8_t reg = compileExprAuto(expr->expr, rs); uint8_t reg = compileExprAuto(expr->expr, rs);
setDebugLine(expr->indexLocation);
BytecodeBuilder::StringRef iname = sref(expr->index); BytecodeBuilder::StringRef iname = sref(expr->index);
int32_t cid = bytecode.addConstantString(iname); int32_t cid = bytecode.addConstantString(iname);
if (cid < 0) if (cid < 0)
@ -2710,6 +2710,12 @@ struct Compiler
bytecode.setDebugLine(node->location.begin.line + 1); bytecode.setDebugLine(node->location.begin.line + 1);
} }
void setDebugLine(const Location& location)
{
if (options.debugLevel >= 1)
bytecode.setDebugLine(location.begin.line + 1);
}
void setDebugLineEnd(AstNode* node) void setDebugLineEnd(AstNode* node)
{ {
if (options.debugLevel >= 1) if (options.debugLevel >= 1)
@ -3650,7 +3656,7 @@ struct Compiler
{ {
if (options.vectorLib) if (options.vectorLib)
{ {
if (builtin.object == options.vectorLib && builtin.method == options.vectorCtor) if (builtin.isMethod(options.vectorLib, options.vectorCtor))
return LBF_VECTOR; return LBF_VECTOR;
} }
else else

View file

@ -27,7 +27,7 @@ TESTS_SOURCES=$(wildcard tests/*.cpp)
TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o) TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o)
TESTS_TARGET=$(BUILD)/luau-tests 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_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o)
REPL_CLI_TARGET=$(BUILD)/luau 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_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze
FUZZ_SOURCES=$(wildcard fuzz/*.cpp) FUZZ_SOURCES=$(wildcard fuzz/*.cpp) fuzz/luau.pb.cpp
FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o) FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o)
TESTS_ARGS= TESTS_ARGS=
@ -128,10 +128,10 @@ luau-size: luau
# executable target aliases # executable target aliases
luau: $(REPL_CLI_TARGET) luau: $(REPL_CLI_TARGET)
cp $^ $@ ln -fs $^ $@
luau-analyze: $(ANALYZE_CLI_TARGET) luau-analyze: $(ANALYZE_CLI_TARGET)
cp $^ $@ ln -fs $^ $@
# executable targets # executable targets
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) $(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=. cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=.
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator $(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp
$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator $(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp
build/libprotobuf-mutator: build/libprotobuf-mutator:
git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator

View file

@ -54,6 +54,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Scope.h Analysis/include/Luau/Scope.h
Analysis/include/Luau/Substitution.h Analysis/include/Luau/Substitution.h
Analysis/include/Luau/Symbol.h Analysis/include/Luau/Symbol.h
Analysis/include/Luau/ToDot.h
Analysis/include/Luau/TopoSortStatements.h Analysis/include/Luau/TopoSortStatements.h
Analysis/include/Luau/ToString.h Analysis/include/Luau/ToString.h
Analysis/include/Luau/Transpiler.h Analysis/include/Luau/Transpiler.h
@ -86,6 +87,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/Scope.cpp Analysis/src/Scope.cpp
Analysis/src/Substitution.cpp Analysis/src/Substitution.cpp
Analysis/src/Symbol.cpp Analysis/src/Symbol.cpp
Analysis/src/ToDot.cpp
Analysis/src/TopoSortStatements.cpp Analysis/src/TopoSortStatements.cpp
Analysis/src/ToString.cpp Analysis/src/ToString.cpp
Analysis/src/Transpiler.cpp Analysis/src/Transpiler.cpp
@ -118,6 +120,7 @@ target_sources(Luau.VM PRIVATE
VM/src/ldo.cpp VM/src/ldo.cpp
VM/src/lfunc.cpp VM/src/lfunc.cpp
VM/src/lgc.cpp VM/src/lgc.cpp
VM/src/lgcdebug.cpp
VM/src/linit.cpp VM/src/linit.cpp
VM/src/lmathlib.cpp VM/src/lmathlib.cpp
VM/src/lmem.cpp VM/src/lmem.cpp
@ -130,6 +133,7 @@ target_sources(Luau.VM PRIVATE
VM/src/ltable.cpp VM/src/ltable.cpp
VM/src/ltablib.cpp VM/src/ltablib.cpp
VM/src/ltm.cpp VM/src/ltm.cpp
VM/src/ludata.cpp
VM/src/lutf8lib.cpp VM/src/lutf8lib.cpp
VM/src/lvmexecute.cpp VM/src/lvmexecute.cpp
VM/src/lvmload.cpp VM/src/lvmload.cpp
@ -149,12 +153,15 @@ target_sources(Luau.VM PRIVATE
VM/src/lstring.h VM/src/lstring.h
VM/src/ltable.h VM/src/ltable.h
VM/src/ltm.h VM/src/ltm.h
VM/src/ludata.h
VM/src/lvm.h VM/src/lvm.h
) )
if(TARGET Luau.Repl.CLI) if(TARGET Luau.Repl.CLI)
# Luau.Repl.CLI Sources # Luau.Repl.CLI Sources
target_sources(Luau.Repl.CLI PRIVATE target_sources(Luau.Repl.CLI PRIVATE
CLI/Coverage.h
CLI/Coverage.cpp
CLI/FileUtils.h CLI/FileUtils.h
CLI/FileUtils.cpp CLI/FileUtils.cpp
CLI/Profiler.h CLI/Profiler.h
@ -194,6 +201,7 @@ if(TARGET Luau.UnitTest)
tests/RequireTracer.test.cpp tests/RequireTracer.test.cpp
tests/StringUtils.test.cpp tests/StringUtils.test.cpp
tests/Symbol.test.cpp tests/Symbol.test.cpp
tests/ToDot.test.cpp
tests/TopoSort.test.cpp tests/TopoSort.test.cpp
tests/ToString.test.cpp tests/ToString.test.cpp
tests/Transpiler.test.cpp tests/Transpiler.test.cpp

View file

@ -21,6 +21,7 @@
#define LUA_ENVIRONINDEX (-10001) #define LUA_ENVIRONINDEX (-10001)
#define LUA_GLOBALSINDEX (-10002) #define LUA_GLOBALSINDEX (-10002)
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i)) #define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i))
#define lua_ispseudo(i) ((i) <= LUA_REGISTRYINDEX)
/* thread status; 0 is OK */ /* thread status; 0 is OK */
enum lua_Status enum lua_Status
@ -108,6 +109,7 @@ LUA_API int lua_isthreadreset(lua_State* L);
/* /*
** basic stack manipulation ** basic stack manipulation
*/ */
LUA_API int lua_absindex(lua_State* L, int idx);
LUA_API int lua_gettop(lua_State* L); LUA_API int lua_gettop(lua_State* L);
LUA_API void lua_settop(lua_State* L, int idx); LUA_API void lua_settop(lua_State* L, int idx);
LUA_API void lua_pushvalue(lua_State* L, int idx); LUA_API void lua_pushvalue(lua_State* L, int idx);
@ -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 int lua_getreadonly(lua_State* L, int idx);
LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled); LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled);
LUA_API void* lua_newuserdata(lua_State* L, size_t sz, int tag); LUA_API void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag);
LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*)); LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
LUA_API int lua_getmetatable(lua_State* L, int objindex); LUA_API int lua_getmetatable(lua_State* L, int objindex);
LUA_API void lua_getfenv(lua_State* L, int idx); LUA_API void lua_getfenv(lua_State* L, int idx);
@ -231,6 +233,7 @@ enum lua_GCOp
LUA_GCRESTART, LUA_GCRESTART,
LUA_GCCOLLECT, LUA_GCCOLLECT,
LUA_GCCOUNT, LUA_GCCOUNT,
LUA_GCCOUNTB,
LUA_GCISRUNNING, LUA_GCISRUNNING,
// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation // garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
@ -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_pop(L, n) lua_settop(L, -(n)-1)
#define lua_newtable(L) lua_createtable(L, 0, 0) #define lua_newtable(L) lua_createtable(L, 0, 0)
#define lua_newuserdata(L, s) lua_newuserdatatagged(L, s, 0)
#define lua_strlen(L, i) lua_objlen(L, (i)) #define lua_strlen(L, i) lua_objlen(L, (i))
@ -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_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL) #define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN) #define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isvector(L, n) (lua_type(L, (n)) == LUA_TVECTOR)
#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD) #define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL)
@ -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_singlestep(lua_State* L, int enabled);
LUA_API void lua_breakpoint(lua_State* L, int funcindex, int line, 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. */ /* 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); LUA_API const char* lua_debugtrace(lua_State* L);

View file

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

View file

@ -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_checkunsigned(lua_State* L, int numArg);
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def); LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def);
LUALIB_API const float* luaL_checkvector(lua_State* L, int narg);
LUALIB_API const float* luaL_optvector(lua_State* L, int narg, const float* def);
LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg); LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg);
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t); LUALIB_API void luaL_checktype(lua_State* L, int narg, int t);
LUALIB_API void luaL_checkany(lua_State* L, int narg); LUALIB_API void luaL_checkany(lua_State* L, int narg);

View file

@ -8,11 +8,14 @@
#include "lfunc.h" #include "lfunc.h"
#include "lgc.h" #include "lgc.h"
#include "ldo.h" #include "ldo.h"
#include "ludata.h"
#include "lvm.h" #include "lvm.h"
#include "lnumutils.h" #include "lnumutils.h"
#include <string.h> #include <string.h>
LUAU_FASTFLAG(LuauActivateBeforeExec)
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n" const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
"$URL: www.lua.org $\n"; "$URL: www.lua.org $\n";
@ -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); api_check(L, lua_ispseudo(idx));
if (idx > LUA_REGISTRYINDEX) switch (idx)
{ /* pseudo-indices */
case LUA_REGISTRYINDEX:
return registry(L);
case LUA_ENVIRONINDEX:
{ {
api_check(L, idx != 0 && -idx <= L->top - L->base); sethvalue(L, &L->env, getcurrenv(L));
return L->top + idx; 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) if (idx > 0)
{ {
@ -81,15 +78,20 @@ static LUAU_FORCEINLINE TValue* index2adr(lua_State* L, int idx)
else else
return o; return o;
} }
else if (idx > LUA_REGISTRYINDEX)
{
api_check(L, idx != 0 && -idx <= L->top - L->base);
return L->top + idx;
}
else else
{ {
return index2adrslow(L, idx); return pseudo2addr(L, idx);
} }
} }
const TValue* luaA_toobject(lua_State* L, int 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; 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); api_check(from, from->global == to->global);
luaC_checkthreadsleep(to); luaC_checkthreadsleep(to);
setobj2s(to, to->top, index2adr(from, idx)); setobj2s(to, to->top, index2addr(from, idx));
api_incr_top(to); api_incr_top(to);
return; return;
} }
@ -170,6 +172,12 @@ lua_State* lua_mainthread(lua_State* L)
** basic stack manipulation ** basic stack manipulation
*/ */
int lua_absindex(lua_State* L, int idx)
{
api_check(L, (idx > 0 && idx <= L->top - L->base) || (idx < 0 && -idx <= L->top - L->base) || lua_ispseudo(idx));
return idx > 0 || lua_ispseudo(idx) ? idx : cast_int(L->top - L->base) + idx + 1;
}
int lua_gettop(lua_State* L) int lua_gettop(lua_State* L)
{ {
return cast_int(L->top - L->base); return cast_int(L->top - L->base);
@ -194,7 +202,7 @@ void lua_settop(lua_State* L, int idx)
void lua_remove(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); api_checkvalidindex(L, p);
while (++p < L->top) while (++p < L->top)
setobjs2s(L, p - 1, p); 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) void lua_insert(lua_State* L, int idx)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId p = index2adr(L, idx); StkId p = index2addr(L, idx);
api_checkvalidindex(L, p); api_checkvalidindex(L, p);
for (StkId q = L->top; q > p; q--) for (StkId q = L->top; q > p; q--)
setobjs2s(L, q, q - 1); setobjs2s(L, q, q - 1);
@ -220,7 +228,7 @@ void lua_replace(lua_State* L, int idx)
luaG_runerror(L, "no calling environment"); luaG_runerror(L, "no calling environment");
api_checknelems(L, 1); api_checknelems(L, 1);
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
api_checkvalidindex(L, o); api_checkvalidindex(L, o);
if (idx == LUA_ENVIRONINDEX) if (idx == LUA_ENVIRONINDEX)
{ {
@ -242,7 +250,7 @@ void lua_replace(lua_State* L, int idx)
void lua_pushvalue(lua_State* L, int idx) void lua_pushvalue(lua_State* L, int idx)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
setobj2s(L, L->top, o); setobj2s(L, L->top, o);
api_incr_top(L); api_incr_top(L);
return; return;
@ -254,7 +262,7 @@ void lua_pushvalue(lua_State* L, int idx)
int lua_type(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); 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) int lua_iscfunction(lua_State* L, int idx)
{ {
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
return iscfunction(o); return iscfunction(o);
} }
int lua_isLfunction(lua_State* L, int idx) int lua_isLfunction(lua_State* L, int idx)
{ {
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
return isLfunction(o); return isLfunction(o);
} }
int lua_isnumber(lua_State* L, int idx) int lua_isnumber(lua_State* L, int idx)
{ {
TValue n; TValue n;
const TValue* o = index2adr(L, idx); const TValue* o = index2addr(L, idx);
return tonumber(o, &n); return tonumber(o, &n);
} }
@ -290,14 +298,14 @@ int lua_isstring(lua_State* L, int idx)
int lua_isuserdata(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)); return (ttisuserdata(o) || ttislightuserdata(o));
} }
int lua_rawequal(lua_State* L, int index1, int index2) int lua_rawequal(lua_State* L, int index1, int index2)
{ {
StkId o1 = index2adr(L, index1); StkId o1 = index2addr(L, index1);
StkId o2 = index2adr(L, index2); StkId o2 = index2addr(L, index2);
return (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaO_rawequalObj(o1, o2); 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; StkId o1, o2;
int i; int i;
o1 = index2adr(L, index1); o1 = index2addr(L, index1);
o2 = index2adr(L, index2); o2 = index2addr(L, index2);
i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : equalobj(L, o1, o2); i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : equalobj(L, o1, o2);
return i; return i;
} }
@ -315,8 +323,8 @@ int lua_lessthan(lua_State* L, int index1, int index2)
{ {
StkId o1, o2; StkId o1, o2;
int i; int i;
o1 = index2adr(L, index1); o1 = index2addr(L, index1);
o2 = index2adr(L, index2); o2 = index2addr(L, index2);
i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaV_lessthan(L, o1, o2); i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaV_lessthan(L, o1, o2);
return i; 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) double lua_tonumberx(lua_State* L, int idx, int* isnum)
{ {
TValue n; TValue n;
const TValue* o = index2adr(L, idx); const TValue* o = index2addr(L, idx);
if (tonumber(o, &n)) if (tonumber(o, &n))
{ {
if (isnum) 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) int lua_tointegerx(lua_State* L, int idx, int* isnum)
{ {
TValue n; TValue n;
const TValue* o = index2adr(L, idx); const TValue* o = index2addr(L, idx);
if (tonumber(o, &n)) if (tonumber(o, &n))
{ {
int res; 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) unsigned lua_tounsignedx(lua_State* L, int idx, int* isnum)
{ {
TValue n; TValue n;
const TValue* o = index2adr(L, idx); const TValue* o = index2addr(L, idx);
if (tonumber(o, &n)) if (tonumber(o, &n))
{ {
unsigned res; unsigned res;
@ -383,13 +391,13 @@ unsigned lua_tounsignedx(lua_State* L, int idx, int* isnum)
int lua_toboolean(lua_State* L, int idx) int lua_toboolean(lua_State* L, int idx)
{ {
const TValue* o = index2adr(L, idx); const TValue* o = index2addr(L, idx);
return !l_isfalse(o); return !l_isfalse(o);
} }
const char* lua_tolstring(lua_State* L, int idx, size_t* len) 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)) if (!ttisstring(o))
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
@ -400,7 +408,7 @@ const char* lua_tolstring(lua_State* L, int idx, size_t* len)
return NULL; return NULL;
} }
luaC_checkGC(L); 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) if (len != NULL)
*len = tsvalue(o)->len; *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) const char* lua_tostringatom(lua_State* L, int idx, int* atom)
{ {
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
if (!ttisstring(o)) if (!ttisstring(o))
return NULL; return NULL;
const TString* s = tsvalue(o); 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) const float* lua_tovector(lua_State* L, int idx)
{ {
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
if (!ttisvector(o)) if (!ttisvector(o))
{ {
return NULL; return NULL;
@ -440,7 +448,7 @@ const float* lua_tovector(lua_State* L, int idx)
int lua_objlen(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)) switch (ttype(o))
{ {
case LUA_TSTRING: case LUA_TSTRING:
@ -461,13 +469,13 @@ int lua_objlen(lua_State* L, int idx)
lua_CFunction lua_tocfunction(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); return (!iscfunction(o)) ? NULL : cast_to(lua_CFunction, clvalue(o)->c.f);
} }
void* lua_touserdata(lua_State* L, int idx) void* lua_touserdata(lua_State* L, int idx)
{ {
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
switch (ttype(o)) switch (ttype(o))
{ {
case LUA_TUSERDATA: 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) 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; return (ttisuserdata(o) && uvalue(o)->tag == tag) ? uvalue(o)->data : NULL;
} }
int lua_userdatatag(lua_State* L, int idx) int lua_userdatatag(lua_State* L, int idx)
{ {
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
if (ttisuserdata(o)) if (ttisuserdata(o))
return uvalue(o)->tag; return uvalue(o)->tag;
return -1; return -1;
@ -495,13 +503,13 @@ int lua_userdatatag(lua_State* L, int idx)
lua_State* lua_tothread(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); return (!ttisthread(o)) ? NULL : thvalue(o);
} }
const void* lua_topointer(lua_State* L, int idx) const void* lua_topointer(lua_State* L, int idx)
{ {
StkId o = index2adr(L, idx); StkId o = index2addr(L, idx);
switch (ttype(o)) switch (ttype(o))
{ {
case LUA_TTABLE: case LUA_TTABLE:
@ -649,7 +657,7 @@ int lua_pushthread(lua_State* L)
void lua_gettable(lua_State* L, int idx) void lua_gettable(lua_State* L, int idx)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2adr(L, idx); StkId t = index2addr(L, idx);
api_checkvalidindex(L, t); api_checkvalidindex(L, t);
luaV_gettable(L, t, L->top - 1, L->top - 1); luaV_gettable(L, t, L->top - 1, L->top - 1);
return; return;
@ -658,7 +666,7 @@ void lua_gettable(lua_State* L, int idx)
void lua_getfield(lua_State* L, int idx, const char* k) void lua_getfield(lua_State* L, int idx, const char* k)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2adr(L, idx); StkId t = index2addr(L, idx);
api_checkvalidindex(L, t); api_checkvalidindex(L, t);
TValue key; TValue key;
setsvalue(L, &key, luaS_new(L, k)); 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) void lua_rawgetfield(lua_State* L, int idx, const char* k)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2adr(L, idx); StkId t = index2addr(L, idx);
api_check(L, ttistable(t)); api_check(L, ttistable(t));
TValue key; TValue key;
setsvalue(L, &key, luaS_new(L, k)); 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) void lua_rawget(lua_State* L, int idx)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2adr(L, idx); StkId t = index2addr(L, idx);
api_check(L, ttistable(t)); api_check(L, ttistable(t));
setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
return; return;
@ -691,7 +699,7 @@ void lua_rawget(lua_State* L, int idx)
void lua_rawgeti(lua_State* L, int idx, int n) void lua_rawgeti(lua_State* L, int idx, int n)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2adr(L, idx); StkId t = index2addr(L, idx);
api_check(L, ttistable(t)); api_check(L, ttistable(t));
setobj2s(L, L->top, luaH_getnum(hvalue(t), n)); setobj2s(L, L->top, luaH_getnum(hvalue(t), n));
api_incr_top(L); 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) 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)); api_check(L, ttistable(o));
Table* t = hvalue(o); Table* t = hvalue(o);
api_check(L, t != hvalue(registry(L))); 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) 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)); api_check(L, ttistable(o));
Table* t = hvalue(o); Table* t = hvalue(o);
int res = t->readonly; 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) 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)); api_check(L, ttistable(o));
Table* t = hvalue(o); Table* t = hvalue(o);
t->safeenv = bool(enabled); t->safeenv = bool(enabled);
@ -740,7 +748,7 @@ int lua_getmetatable(lua_State* L, int objindex)
const TValue* obj; const TValue* obj;
Table* mt = NULL; Table* mt = NULL;
int res; int res;
obj = index2adr(L, objindex); obj = index2addr(L, objindex);
switch (ttype(obj)) switch (ttype(obj))
{ {
case LUA_TTABLE: case LUA_TTABLE:
@ -767,7 +775,7 @@ int lua_getmetatable(lua_State* L, int objindex)
void lua_getfenv(lua_State* L, int idx) void lua_getfenv(lua_State* L, int idx)
{ {
StkId o; StkId o;
o = index2adr(L, idx); o = index2addr(L, idx);
api_checkvalidindex(L, o); api_checkvalidindex(L, o);
switch (ttype(o)) switch (ttype(o))
{ {
@ -793,7 +801,7 @@ void lua_settable(lua_State* L, int idx)
{ {
StkId t; StkId t;
api_checknelems(L, 2); api_checknelems(L, 2);
t = index2adr(L, idx); t = index2addr(L, idx);
api_checkvalidindex(L, t); api_checkvalidindex(L, t);
luaV_settable(L, t, L->top - 2, L->top - 1); luaV_settable(L, t, L->top - 2, L->top - 1);
L->top -= 2; /* pop index and value */ L->top -= 2; /* pop index and value */
@ -805,7 +813,7 @@ void lua_setfield(lua_State* L, int idx, const char* k)
StkId t; StkId t;
TValue key; TValue key;
api_checknelems(L, 1); api_checknelems(L, 1);
t = index2adr(L, idx); t = index2addr(L, idx);
api_checkvalidindex(L, t); api_checkvalidindex(L, t);
setsvalue(L, &key, luaS_new(L, k)); setsvalue(L, &key, luaS_new(L, k));
luaV_settable(L, t, &key, L->top - 1); luaV_settable(L, t, &key, L->top - 1);
@ -817,7 +825,7 @@ void lua_rawset(lua_State* L, int idx)
{ {
StkId t; StkId t;
api_checknelems(L, 2); api_checknelems(L, 2);
t = index2adr(L, idx); t = index2addr(L, idx);
api_check(L, ttistable(t)); api_check(L, ttistable(t));
if (hvalue(t)->readonly) if (hvalue(t)->readonly)
luaG_runerror(L, "Attempt to modify a readonly table"); 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; StkId o;
api_checknelems(L, 1); api_checknelems(L, 1);
o = index2adr(L, idx); o = index2addr(L, idx);
api_check(L, ttistable(o)); api_check(L, ttistable(o));
if (hvalue(o)->readonly) if (hvalue(o)->readonly)
luaG_runerror(L, "Attempt to modify a readonly table"); luaG_runerror(L, "Attempt to modify a readonly table");
@ -846,7 +854,7 @@ int lua_setmetatable(lua_State* L, int objindex)
TValue* obj; TValue* obj;
Table* mt; Table* mt;
api_checknelems(L, 1); api_checknelems(L, 1);
obj = index2adr(L, objindex); obj = index2addr(L, objindex);
api_checkvalidindex(L, obj); api_checkvalidindex(L, obj);
if (ttisnil(L->top - 1)) if (ttisnil(L->top - 1))
mt = NULL; mt = NULL;
@ -888,7 +896,7 @@ int lua_setfenv(lua_State* L, int idx)
StkId o; StkId o;
int res = 1; int res = 1;
api_checknelems(L, 1); api_checknelems(L, 1);
o = index2adr(L, idx); o = index2addr(L, idx);
api_checkvalidindex(L, o); api_checkvalidindex(L, o);
api_check(L, ttistable(L->top - 1)); api_check(L, ttistable(L->top - 1));
switch (ttype(o)) switch (ttype(o))
@ -931,14 +939,21 @@ void lua_call(lua_State* L, int nargs, int nresults)
checkresults(L, nargs, nresults); checkresults(L, nargs, nresults);
func = L->top - (nargs + 1); func = L->top - (nargs + 1);
int wasActive = luaC_threadactive(L); if (FFlag::LuauActivateBeforeExec)
l_setbit(L->stackstate, THREAD_ACTIVEBIT); {
luaC_checkthreadsleep(L); luaD_call(L, func, nresults);
}
else
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
luaD_call(L, func, nresults); luaD_call(L, func, nresults);
if (!wasActive) if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT); resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
adjustresults(L, nresults); adjustresults(L, nresults);
return; return;
@ -972,21 +987,28 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc)
func = 0; func = 0;
else else
{ {
StkId o = index2adr(L, errfunc); StkId o = index2addr(L, errfunc);
api_checkvalidindex(L, o); api_checkvalidindex(L, o);
func = savestack(L, o); func = savestack(L, o);
} }
c.func = L->top - (nargs + 1); /* function to be called */ c.func = L->top - (nargs + 1); /* function to be called */
c.nresults = nresults; c.nresults = nresults;
int wasActive = luaC_threadactive(L); if (FFlag::LuauActivateBeforeExec)
l_setbit(L->stackstate, THREAD_ACTIVEBIT); {
luaC_checkthreadsleep(L); status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
}
else
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
if (!wasActive) if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT); resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
adjustresults(L, nresults); adjustresults(L, nresults);
return status; return status;
@ -1039,6 +1061,11 @@ int lua_gc(lua_State* L, int what, int data)
res = cast_int(g->totalbytes >> 10); res = cast_int(g->totalbytes >> 10);
break; break;
} }
case LUA_GCCOUNTB:
{
res = cast_int(g->totalbytes & 1023);
break;
}
case LUA_GCISRUNNING: case LUA_GCISRUNNING:
{ {
res = (g->GCthreshold != SIZE_MAX); res = (g->GCthreshold != SIZE_MAX);
@ -1123,7 +1150,7 @@ l_noret lua_error(lua_State* L)
int lua_next(lua_State* L, int idx) int lua_next(lua_State* L, int idx)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2adr(L, idx); StkId t = index2addr(L, idx);
api_check(L, ttistable(t)); api_check(L, ttistable(t));
int more = luaH_next(L, hvalue(t), L->top - 1); int more = luaH_next(L, hvalue(t), L->top - 1);
if (more) if (more)
@ -1155,12 +1182,12 @@ void lua_concat(lua_State* L, int n)
return; return;
} }
void* lua_newuserdata(lua_State* L, size_t sz, int tag) void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag)
{ {
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
luaC_checkGC(L); luaC_checkGC(L);
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
Udata* u = luaS_newudata(L, sz, tag); Udata* u = luaU_newudata(L, sz, tag);
setuvalue(L, L->top, u); setuvalue(L, L->top, u);
api_incr_top(L); api_incr_top(L);
return u->data; return u->data;
@ -1170,7 +1197,7 @@ void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*))
{ {
luaC_checkGC(L); luaC_checkGC(L);
luaC_checkthreadsleep(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)); memcpy(&u->data + sz, &dtor, sizeof(dtor));
setuvalue(L, L->top, u); setuvalue(L, L->top, u);
api_incr_top(L); api_incr_top(L);
@ -1205,7 +1232,7 @@ const char* lua_getupvalue(lua_State* L, int funcindex, int n)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
TValue* val; TValue* val;
const char* name = aux_upvalue(index2adr(L, funcindex), n, &val); const char* name = aux_upvalue(index2addr(L, funcindex), n, &val);
if (name) if (name)
{ {
setobj2s(L, L->top, val); setobj2s(L, L->top, val);
@ -1219,7 +1246,7 @@ const char* lua_setupvalue(lua_State* L, int funcindex, int n)
const char* name; const char* name;
TValue* val; TValue* val;
StkId fi; StkId fi;
fi = index2adr(L, funcindex); fi = index2addr(L, funcindex);
api_checknelems(L, 1); api_checknelems(L, 1);
name = aux_upvalue(fi, n, &val); name = aux_upvalue(fi, n, &val);
if (name) if (name)
@ -1240,9 +1267,10 @@ uintptr_t lua_encodepointer(lua_State* L, uintptr_t p)
int lua_ref(lua_State* L, int idx) int lua_ref(lua_State* L, int idx)
{ {
api_check(L, idx != LUA_REGISTRYINDEX); /* idx is a stack index for value */
int ref = LUA_REFNIL; int ref = LUA_REFNIL;
global_State* g = L->global; global_State* g = L->global;
StkId p = index2adr(L, idx); StkId p = index2addr(L, idx);
if (!ttisnil(p)) if (!ttisnil(p))
{ {
Table* reg = hvalue(registry(L)); Table* reg = hvalue(registry(L));

View file

@ -30,7 +30,7 @@ static const char* currfuncname(lua_State* L)
return debugname; return debugname;
} }
LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg) l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
{ {
const char* fname = currfuncname(L); const char* fname = currfuncname(L);
@ -40,7 +40,7 @@ LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
luaL_error(L, "invalid argument #%d (%s)", narg, extramsg); luaL_error(L, "invalid argument #%d (%s)", narg, extramsg);
} }
LUALIB_API l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname) l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname)
{ {
const char* fname = currfuncname(L); const char* fname = currfuncname(L);
const TValue* obj = luaA_toobject(L, narg); const TValue* obj = luaA_toobject(L, narg);
@ -66,7 +66,7 @@ static l_noret tag_error(lua_State* L, int narg, int tag)
luaL_typeerrorL(L, narg, lua_typename(L, tag)); luaL_typeerrorL(L, narg, lua_typename(L, tag));
} }
LUALIB_API void luaL_where(lua_State* L, int level) void luaL_where(lua_State* L, int level)
{ {
lua_Debug ar; lua_Debug ar;
if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0) if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0)
@ -77,7 +77,7 @@ LUALIB_API void luaL_where(lua_State* L, int level)
lua_pushliteral(L, ""); /* else, no information available... */ lua_pushliteral(L, ""); /* else, no information available... */
} }
LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...) l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
{ {
va_list argp; va_list argp;
va_start(argp, fmt); va_start(argp, fmt);
@ -90,7 +90,7 @@ LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
/* }====================================================== */ /* }====================================================== */
LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[]) int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[])
{ {
const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg); const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
int i; int i;
@ -101,7 +101,7 @@ LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const c
luaL_argerrorL(L, narg, msg); luaL_argerrorL(L, narg, msg);
} }
LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname) int luaL_newmetatable(lua_State* L, const char* tname)
{ {
lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */ lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */
if (!lua_isnil(L, -1)) /* name already in use? */ if (!lua_isnil(L, -1)) /* name already in use? */
@ -113,7 +113,7 @@ LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname)
return 1; return 1;
} }
LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname) void* luaL_checkudata(lua_State* L, int ud, const char* tname)
{ {
void* p = lua_touserdata(L, ud); void* p = lua_touserdata(L, ud);
if (p != NULL) if (p != NULL)
@ -131,25 +131,25 @@ LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname)
luaL_typeerrorL(L, ud, tname); /* else error */ luaL_typeerrorL(L, ud, tname); /* else error */
} }
LUALIB_API void luaL_checkstack(lua_State* L, int space, const char* mes) void luaL_checkstack(lua_State* L, int space, const char* mes)
{ {
if (!lua_checkstack(L, space)) if (!lua_checkstack(L, space))
luaL_error(L, "stack overflow (%s)", mes); luaL_error(L, "stack overflow (%s)", mes);
} }
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t) void luaL_checktype(lua_State* L, int narg, int t)
{ {
if (lua_type(L, narg) != t) if (lua_type(L, narg) != t)
tag_error(L, narg, t); tag_error(L, narg, t);
} }
LUALIB_API void luaL_checkany(lua_State* L, int narg) void luaL_checkany(lua_State* L, int narg)
{ {
if (lua_type(L, narg) == LUA_TNONE) if (lua_type(L, narg) == LUA_TNONE)
luaL_error(L, "missing argument #%d", narg); luaL_error(L, "missing argument #%d", narg);
} }
LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len) const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
{ {
const char* s = lua_tolstring(L, narg, len); const char* s = lua_tolstring(L, narg, len);
if (!s) if (!s)
@ -157,7 +157,7 @@ LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
return s; return s;
} }
LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len) const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len)
{ {
if (lua_isnoneornil(L, narg)) if (lua_isnoneornil(L, narg))
{ {
@ -169,7 +169,7 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def,
return luaL_checklstring(L, narg, len); return luaL_checklstring(L, narg, len);
} }
LUALIB_API double luaL_checknumber(lua_State* L, int narg) double luaL_checknumber(lua_State* L, int narg)
{ {
int isnum; int isnum;
double d = lua_tonumberx(L, narg, &isnum); double d = lua_tonumberx(L, narg, &isnum);
@ -178,12 +178,12 @@ LUALIB_API double luaL_checknumber(lua_State* L, int narg)
return d; return d;
} }
LUALIB_API double luaL_optnumber(lua_State* L, int narg, double def) double luaL_optnumber(lua_State* L, int narg, double def)
{ {
return luaL_opt(L, luaL_checknumber, narg, def); return luaL_opt(L, luaL_checknumber, narg, def);
} }
LUALIB_API int luaL_checkboolean(lua_State* L, int narg) int luaL_checkboolean(lua_State* L, int narg)
{ {
// This checks specifically for boolean values, ignoring // This checks specifically for boolean values, ignoring
// all other truthy/falsy values. If the desired result // 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); 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); 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 isnum;
int d = lua_tointegerx(L, narg, &isnum); int d = lua_tointegerx(L, narg, &isnum);
@ -208,12 +208,12 @@ LUALIB_API int luaL_checkinteger(lua_State* L, int narg)
return d; return d;
} }
LUALIB_API int luaL_optinteger(lua_State* L, int narg, int def) int luaL_optinteger(lua_State* L, int narg, int def)
{ {
return luaL_opt(L, luaL_checkinteger, narg, def); return luaL_opt(L, luaL_checkinteger, narg, def);
} }
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg) unsigned luaL_checkunsigned(lua_State* L, int narg)
{ {
int isnum; int isnum;
unsigned d = lua_tounsignedx(L, narg, &isnum); unsigned d = lua_tounsignedx(L, narg, &isnum);
@ -222,12 +222,25 @@ LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg)
return d; return d;
} }
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def) unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def)
{ {
return luaL_opt(L, luaL_checkunsigned, narg, def); return luaL_opt(L, luaL_checkunsigned, narg, def);
} }
LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event) const float* luaL_checkvector(lua_State* L, int narg)
{
const float* v = lua_tovector(L, narg);
if (!v)
tag_error(L, narg, LUA_TVECTOR);
return v;
}
const float* luaL_optvector(lua_State* L, int narg, const float* def)
{
return luaL_opt(L, luaL_checkvector, narg, def);
}
int luaL_getmetafield(lua_State* L, int obj, const char* event)
{ {
if (!lua_getmetatable(L, obj)) /* no metatable? */ if (!lua_getmetatable(L, obj)) /* no metatable? */
return 0; return 0;
@ -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); obj = abs_index(L, obj);
if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ if (!luaL_getmetafield(L, obj, event)) /* no metafield? */
@ -263,7 +276,7 @@ static int libsize(const luaL_Reg* l)
return size; return size;
} }
LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l) void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l)
{ {
if (libname) if (libname)
{ {
@ -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; const char* e;
lua_pushvalue(L, idx); lua_pushvalue(L, idx);
@ -340,7 +353,7 @@ static size_t getnextbuffersize(lua_State* L, size_t currentsize, size_t desired
return newsize; return newsize;
} }
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B) void luaL_buffinit(lua_State* L, luaL_Buffer* B)
{ {
// start with an internal buffer // start with an internal buffer
B->p = B->buffer; B->p = B->buffer;
@ -350,14 +363,14 @@ LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B)
B->storage = nullptr; B->storage = nullptr;
} }
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size) char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size)
{ {
luaL_buffinit(L, B); luaL_buffinit(L, B);
luaL_reservebuffer(B, size, -1); luaL_reservebuffer(B, size, -1);
return B->p; return B->p;
} }
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc) char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
{ {
lua_State* L = B->L; lua_State* L = B->L;
@ -388,13 +401,13 @@ LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int bo
return B->p; return B->p;
} }
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc) void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
{ {
if (size_t(B->end - B->p) < size) if (size_t(B->end - B->p) < size)
luaL_extendbuffer(B, size - (B->end - B->p), boxloc); luaL_extendbuffer(B, size - (B->end - B->p), boxloc);
} }
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len) void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
{ {
if (size_t(B->end - B->p) < len) if (size_t(B->end - B->p) < len)
luaL_extendbuffer(B, len - (B->end - B->p), -1); luaL_extendbuffer(B, len - (B->end - B->p), -1);
@ -403,7 +416,7 @@ LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
B->p += len; B->p += len;
} }
LUALIB_API void luaL_addvalue(luaL_Buffer* B) void luaL_addvalue(luaL_Buffer* B)
{ {
lua_State* L = B->L; lua_State* L = B->L;
@ -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; 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; B->p += size;
luaL_pushresult(B); 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? */ if (luaL_callmeta(L, idx, "__tostring")) /* is there a metafield? */
{ {

View file

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

View file

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

View file

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

View file

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

View file

@ -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)); 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) static size_t append(char* buf, size_t bufsize, size_t offset, const char* data)
{ {
size_t size = strlen(data); size_t size = strlen(data);

View file

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

View file

@ -8,12 +8,9 @@
#include "lfunc.h" #include "lfunc.h"
#include "lstring.h" #include "lstring.h"
#include "ldo.h" #include "ldo.h"
#include "ludata.h"
#include <string.h> #include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauRescanGrayAgainForwardBarrier, false)
LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false)
LUAU_FASTFLAG(LuauArrayBoundary) LUAU_FASTFLAG(LuauArrayBoundary)
@ -61,10 +58,6 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
case GCSpropagate: case GCSpropagate:
case GCSpropagateagain: case GCSpropagateagain:
g->gcstats.currcycle.marktime += seconds; 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; break;
case GCSatomic: case GCSatomic:
g->gcstats.currcycle.atomictime += seconds; g->gcstats.currcycle.atomictime += seconds;
@ -490,7 +483,7 @@ static void freeobj(lua_State* L, GCObject* o)
luaS_free(L, gco2ts(o)); luaS_free(L, gco2ts(o));
break; break;
case LUA_TUSERDATA: case LUA_TUSERDATA:
luaS_freeudata(L, gco2u(o)); luaU_freeudata(L, gco2u(o));
break; break;
default: default:
LUAU_ASSERT(0); LUAU_ASSERT(0);
@ -634,17 +627,9 @@ static size_t remarkupvals(global_State* g)
static size_t atomic(lua_State* L) static size_t atomic(lua_State* L)
{ {
global_State* g = L->global; global_State* g = L->global;
LUAU_ASSERT(g->gcstate == GCSatomic);
size_t work = 0; size_t work = 0;
if (FFlag::LuauSeparateAtomic)
{
LUAU_ASSERT(g->gcstate == GCSatomic);
}
else
{
g->gcstate = GCSatomic;
}
/* remark occasional upvalues of (maybe) dead threads */ /* remark occasional upvalues of (maybe) dead threads */
work += remarkupvals(g); work += remarkupvals(g);
/* traverse objects caught by write barrier and by 'remarkupvals' */ /* 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->sweepgc = &g->rootgc;
g->gcstate = GCSsweepstring; g->gcstate = GCSsweepstring;
if (!FFlag::LuauSeparateAtomic)
{
GC_INTERRUPT(GCSatomic);
}
return work; return work;
} }
@ -718,22 +698,7 @@ static size_t gcstep(lua_State* L, size_t limit)
if (!g->gray) /* no more `gray' objects */ if (!g->gray) /* no more `gray' objects */
{ {
if (FFlag::LuauSeparateAtomic) g->gcstate = GCSatomic;
{
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;
}
} }
break; break;
} }
@ -855,7 +820,7 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal)
void luaC_step(lua_State* L, bool assist) void luaC_step(lua_State* L, bool assist)
{ {
global_State* g = L->global; 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); LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
size_t debt = 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) if (g->gcstate == GCSpause)
startGcCycleStats(g); startGcCycleStats(g);
if (g->gcstate <= (FFlag::LuauSeparateAtomic ? GCSatomic : GCSpropagateagain)) if (g->gcstate <= GCSatomic)
{ {
/* reset sweep marks to sweep all elements (returning them to white) */ /* reset sweep marks to sweep all elements (returning them to white) */
g->sweepstrgc = 0; g->sweepstrgc = 0;
@ -988,7 +953,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
GCObject* o = obj2gco(t); GCObject* o = obj2gco(t);
// in the second propagation stage, table assignment barrier works as a forward barrier // in the second propagation stage, table assignment barrier works as a forward barrier
if (FFlag::LuauRescanGrayAgainForwardBarrier && g->gcstate == GCSpropagateagain) if (g->gcstate == GCSpropagateagain)
{ {
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
reallymarkobject(g, v); reallymarkobject(g, v);
@ -1044,550 +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 // measure the allocation rate in bytes/sec
// returns -1 if allocation rate cannot be measured // returns -1 if allocation rate cannot be measured
int64_t luaC_allocationrate(lua_State* L) int64_t luaC_allocationrate(lua_State* L)
@ -1595,7 +1016,7 @@ int64_t luaC_allocationrate(lua_State* L)
global_State* g = L->global; global_State* g = L->global;
const double durationthreshold = 1e-3; // avoid measuring intervals smaller than 1ms 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; double duration = lua_clock() - g->gcstats.lastcycle.endtimestamp;

559
VM/src/lgcdebug.cpp Normal file
View 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");
}

View file

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

View file

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

View file

@ -78,15 +78,7 @@ typedef struct lua_TValue
#define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th) #define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th)
#define upvalue(o) check_exp(ttisupval(o), &(o)->value.gc->uv) #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 #define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0))
// 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)))
/* /*
** for internal debug only ** for internal debug only

View file

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

View file

@ -206,32 +206,3 @@ void luaS_free(lua_State* L, TString* ts)
L->global->strt.nuse--; L->global->strt.nuse--;
luaM_free(L, ts, sizestring(ts->len), ts->memcat); 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);
}

View file

@ -8,11 +8,7 @@
/* string size limit */ /* string size limit */
#define MAXSSIZE (1 << 30) #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 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_new(L, s) (luaS_newlstr(L, s, strlen(s)))
#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, (sizeof(s) / sizeof(char)) - 1)) #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 TString* luaS_newlstr(lua_State* L, const char* str, size_t l);
LUAI_FUNC void luaS_free(lua_State* L, TString* ts); 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_bufstart(lua_State* L, size_t size);
LUAI_FUNC TString* luaS_buffinish(lua_State* L, TString* ts); LUAI_FUNC TString* luaS_buffinish(lua_State* L, TString* ts);

View file

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

View file

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

37
VM/src/ludata.cpp Normal file
View 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
View 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);

View file

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

View file

@ -63,7 +63,8 @@
#define VM_KV(i) (LUAU_ASSERT(unsigned(i) < unsigned(cl->l.p->sizek)), &k[i]) #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_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 // NOTE: If debugging the Luau code, disable this macro to prevent timeouts from
// occurring when tracing code in Visual Studio / XCode // occurring when tracing code in Visual Studio / XCode
@ -120,7 +121,7 @@
*/ */
#if VM_USE_CGOTO #if VM_USE_CGOTO
#define VM_CASE(op) CASE_##op: #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)] #define VM_CONTINUE(op) goto* kDispatchTable[uint8_t(op)]
#else #else
#define VM_CASE(op) case op: #define VM_CASE(op) case op:
@ -325,7 +326,7 @@ static void luau_execute(lua_State* L)
// ... and singlestep logic :) // ... and singlestep logic :)
if (SingleStep) 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)); 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 #if VM_USE_CGOTO
VM_CONTINUE(*(uint8_t*)pc); VM_CONTINUE(LUAU_INSN_OP(*pc));
#endif #endif
} }
#if !VM_USE_CGOTO #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 = LUAU_INSN_OP(*pc);
size_t dispatchOp = *(uint8_t*)pc;
dispatchContinue: dispatchContinue:
switch (dispatchOp) switch (dispatchOp)
@ -2577,7 +2577,7 @@ static void luau_execute(lua_State* L)
// update hits with saturated add and patch the instruction in place // update hits with saturated add and patch the instruction in place
hits = (hits < (1 << 23) - 1) ? hits + 1 : hits; 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(); VM_NEXT();
} }

View file

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

View file

@ -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) static void callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1, const TValue* p2)
{ {
ptrdiff_t result = savestack(L, res); 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 // * 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 // * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers // 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) 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 // * 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 // * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers // stack and checkstack may invalidate those pointers

View file

@ -451,15 +451,16 @@ function raytraceScene()
end end
function arrayToCanvasCommands(pixels) 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 for y = 0,size-1 do
s = s .. "["; table.insert(s, "[");
for x = 0,size-1 do 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 end
s = s .. "],"; table.insert(s, "],");
end 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\
\n\ \n\
var size = ' .. size .. ';\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.setFillColor(l[0], l[1], l[2], 1);\n\
canvas.fillRect(x, y, 1, 1);\n\ canvas.fillRect(x, y, 1, 1);\n\
}\n\ }\n\
}</scr' .. 'ipt></body></html>'; }</script></body></html>');
return s; return table.concat(s);
end end
testOutput = arrayToCanvasCommands(raytraceScene()); testOutput = arrayToCanvasCommands(raytraceScene());

View file

@ -29,3 +29,5 @@ pages:
url: /profile url: /profile
- title: Library - title: Library
url: /library url: /library
- title: Grammar
url: /grammar

82
docs/_pages/grammar.md Normal file
View 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
```

View file

@ -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`. 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 ## 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. 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). 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 ## 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. 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.

View file

@ -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. 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 ## 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`. 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`.

View file

@ -287,6 +287,66 @@ In type annotations, this is written as `...T`:
type F = (...number) -> ...string 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 ## 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))`. 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))`.

View 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.

View file

@ -38,7 +38,7 @@ feature_row3:
- -
title: Analysis title: Analysis
excerpt: > 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 title: Performance

View file

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

View file

@ -4,6 +4,8 @@
Add bit32.countlz (count left zeroes) and bit32.countrz (count right zeroes) to accelerate bit scanning Add bit32.countlz (count left zeroes) and bit32.countrz (count right zeroes) to accelerate bit scanning
**Status**: Implemented
## Motivation ## 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: 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:

View file

@ -48,9 +48,12 @@ type A<T, U = V, V = T> = ... -- not allowed
Default type parameter values are also allowed for type packs: Default type parameter values are also allowed for type packs:
```lua ```lua
type A<T, U... = ...string> -- ok, variadic type pack type A<T, U... = ...string> -- ok, variadic type pack
type C<T, U... = string> -- ok, type pack with one element type B<T, U... = ()> -- ok, type pack with no elements
type D<T..., U... = T...> -- ok 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> = ... type All<T = string, U = number> = ...
local a: All -- ok 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. If type is exported from a module, default type parameter values will still be available when module is imported.

View 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.

View file

@ -1,5 +1,7 @@
# Type alias type packs # Type alias type packs
**Status**: Implemented
## Summary ## Summary
Provide semantics for referencing type packs inside the body of a type alias declaration Provide semantics for referencing type packs inside the body of a type alias declaration

View file

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

View file

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

View file

@ -10,9 +10,6 @@
#include <sstream> #include <sstream>
#include <string_view> #include <string_view>
LUAU_FASTFLAG(LuauPreloadClosures)
LUAU_FASTFLAG(LuauGenericSpecialGlobals)
using namespace Luau; using namespace Luau;
static std::string compileFunction(const char* source, uint32_t id) static std::string compileFunction(const char* source, uint32_t id)
@ -74,20 +71,10 @@ TEST_CASE("BasicFunction")
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::compileOrThrow(bcb, "local function foo(a, b) return b end"); 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 DUPCLOSURE R0 K0
RETURN R0 0 RETURN R0 0
)"); )");
}
else
{
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
NEWCLOSURE R0 P0
RETURN R0 0
)");
}
CHECK_EQ("\n" + bcb.dumpFunction(0), R"( CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
RETURN R1 1 RETURN R1 1
@ -1057,6 +1044,18 @@ RETURN R0 1
CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"( CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"(
LOADN R0 20 LOADN R0 20
RETURN R0 1 RETURN R0 1
)");
// codegen for a true constant condition with non-constant expressions
CHECK_EQ("\n" + compileFunction0("return if true then {} else error()"), R"(
NEWTABLE R0 0 0
RETURN R0 1
)");
// codegen for a false constant condition with non-constant expressions
CHECK_EQ("\n" + compileFunction0("return if false then error() else {}"), R"(
NEWTABLE R0 0 0
RETURN R0 1
)"); )");
// codegen for a false (in this case 'nil') constant condition // codegen for a false (in this case 'nil') constant condition
@ -2360,6 +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") TEST_CASE("DebugSource")
{ {
const char* source = R"( const char* source = R"(
@ -2795,47 +2846,35 @@ CAPTURE UPVAL U1
RETURN R0 1 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 DUPCLOSURE R0 K0
CAPTURE VAL R0 CAPTURE VAL R0
RETURN R0 0 RETURN R0 0
)"); )");
// multi-level recursive capture // multi-level recursive capture
CHECK_EQ("\n" + compileFunction("local function foo() return function() return foo() end end", 1), R"( CHECK_EQ("\n" + compileFunction("local function foo() return function() return foo() end end", 1), R"(
DUPCLOSURE R0 K0 DUPCLOSURE R0 K0
CAPTURE UPVAL U0 CAPTURE UPVAL U0
RETURN R0 1 RETURN R0 1
)"); )");
// multi-level recursive capture where function isn't top-level // 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 // 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"( CHECK_EQ("\n" + compileFunction(R"(
local function foo() local function foo()
local function bar() local function bar()
return function() return bar() end return function() return bar() end
end end
end end
)", )",
1), 1),
R"( R"(
NEWCLOSURE R0 P0 NEWCLOSURE R0 P0
CAPTURE UPVAL U0 CAPTURE UPVAL U0
RETURN R0 1 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") TEST_CASE("OutOfLocals")
@ -3440,8 +3479,6 @@ local t = {
TEST_CASE("ConstantClosure") TEST_CASE("ConstantClosure")
{ {
ScopedFastFlag sff("LuauPreloadClosures", true);
// closures without upvalues are created when bytecode is loaded // closures without upvalues are created when bytecode is loaded
CHECK_EQ("\n" + compileFunction(R"( CHECK_EQ("\n" + compileFunction(R"(
return function() end return function() end
@ -3506,8 +3543,6 @@ RETURN R0 1
TEST_CASE("SharedClosure") TEST_CASE("SharedClosure")
{ {
ScopedFastFlag sff1("LuauPreloadClosures", true);
// closures can be shared even if functions refer to upvalues, as long as upvalues are top-level // closures can be shared even if functions refer to upvalues, as long as upvalues are top-level
CHECK_EQ("\n" + compileFunction(R"( CHECK_EQ("\n" + compileFunction(R"(
local val = ... 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(); TEST_SUITE_END();

View file

@ -78,38 +78,31 @@ static int lua_vector(lua_State* L)
static int lua_vector_dot(lua_State* L) static int lua_vector_dot(lua_State* L)
{ {
const float* a = lua_tovector(L, 1); const float* a = luaL_checkvector(L, 1);
const float* b = lua_tovector(L, 2); const float* b = luaL_checkvector(L, 2);
if (a && b) lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
{ return 1;
lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
return 1;
}
throw std::runtime_error("invalid arguments to vector:Dot");
} }
static int lua_vector_index(lua_State* L) static int lua_vector_index(lua_State* L)
{ {
const float* v = luaL_checkvector(L, 1);
const char* name = luaL_checkstring(L, 2); const char* name = luaL_checkstring(L, 2);
if (const float* v = lua_tovector(L, 1)) if (strcmp(name, "Magnitude") == 0)
{ {
if (strcmp(name, "Magnitude") == 0) lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
{ return 1;
lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
return 1;
}
if (strcmp(name, "Dot") == 0)
{
lua_pushcfunction(L, lua_vector_dot, "Dot");
return 1;
}
} }
throw std::runtime_error(Luau::format("%s is not a valid member of vector", name)); if (strcmp(name, "Dot") == 0)
{
lua_pushcfunction(L, lua_vector_dot, "Dot");
return 1;
}
luaL_error(L, "%s is not a valid member of vector", name);
} }
static int lua_vector_namecall(lua_State* L) static int lua_vector_namecall(lua_State* L)
@ -120,7 +113,7 @@ static int lua_vector_namecall(lua_State* L)
return lua_vector_dot(L); return lua_vector_dot(L);
} }
throw std::runtime_error(Luau::format("%s is not a valid method of vector", luaL_checkstring(L, 1))); luaL_error(L, "%s is not a valid method of vector", luaL_checkstring(L, 1));
} }
int lua_silence(lua_State* L) int lua_silence(lua_State* L)
@ -130,8 +123,8 @@ int lua_silence(lua_State* L)
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>; using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
static StateRef runConformance( static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr,
const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr, lua_State* initialLuaState = nullptr) lua_State* initialLuaState = nullptr, lua_CompileOptions* copts = nullptr)
{ {
std::string path = __FILE__; std::string path = __FILE__;
path.erase(path.find_last_of("\\/")); path.erase(path.find_last_of("\\/"));
@ -187,13 +180,8 @@ static StateRef runConformance(
std::string chunkname = "=" + std::string(name); 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; 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); int result = luau_load(L, chunkname.c_str(), bytecode, bytecodeSize, 0);
free(bytecode); free(bytecode);
@ -380,29 +368,37 @@ TEST_CASE("Vector")
{ {
ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true}; ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true};
runConformance("vector.lua", [](lua_State* L) { lua_CompileOptions copts = {};
lua_pushcfunction(L, lua_vector, "vector"); copts.optimizationLevel = 1;
lua_setglobal(L, "vector"); 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 #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 #else
lua_pushvector(L, 0.0f, 0.0f, 0.0f); lua_pushvector(L, 0.0f, 0.0f, 0.0f);
#endif #endif
luaL_newmetatable(L, "vector"); luaL_newmetatable(L, "vector");
lua_pushstring(L, "__index"); lua_pushstring(L, "__index");
lua_pushcfunction(L, lua_vector_index, nullptr); lua_pushcfunction(L, lua_vector_index, nullptr);
lua_settable(L, -3); lua_settable(L, -3);
lua_pushstring(L, "__namecall"); lua_pushstring(L, "__namecall");
lua_pushcfunction(L, lua_vector_namecall, nullptr); lua_pushcfunction(L, lua_vector_namecall, nullptr);
lua_settable(L, -3); lua_settable(L, -3);
lua_setreadonly(L, -1, true); lua_setreadonly(L, -1, true);
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
lua_pop(L, 1); lua_pop(L, 1);
}); },
nullptr, nullptr, &copts);
} }
static void populateRTTI(lua_State* L, Luau::TypeId type) static void populateRTTI(lua_State* L, Luau::TypeId type)
@ -506,6 +502,10 @@ TEST_CASE("Debugger")
breakhits = 0; breakhits = 0;
interruptedthread = nullptr; interruptedthread = nullptr;
lua_CompileOptions copts = {};
copts.optimizationLevel = 1;
copts.debugLevel = 2;
runConformance( runConformance(
"debugger.lua", "debugger.lua",
[](lua_State* L) { [](lua_State* L) {
@ -515,6 +515,9 @@ TEST_CASE("Debugger")
cb->debugbreak = [](lua_State* L, lua_Debug* ar) { cb->debugbreak = [](lua_State* L, lua_Debug* ar) {
breakhits++; breakhits++;
// make sure we can trace the stack for every breakpoint we hit
lua_debugtrace(L);
// for every breakpoint, we break on the first invocation and continue on second // for every breakpoint, we break on the first invocation and continue on second
// this allows us to easily step off breakpoints // this allows us to easily step off breakpoints
// (real implementaiton may require singlestepping) // (real implementaiton may require singlestepping)
@ -618,7 +621,8 @@ TEST_CASE("Debugger")
lua_resume(interruptedthread, nullptr, 0); lua_resume(interruptedthread, nullptr, 0);
interruptedthread = nullptr; interruptedthread = nullptr;
} }
}); },
nullptr, &copts);
CHECK(breakhits == 10); // 2 hits per breakpoint CHECK(breakhits == 10); // 2 hits per breakpoint
} }
@ -710,21 +714,52 @@ TEST_CASE("ApiFunctionCalls")
StateRef globalState = runConformance("apicalls.lua"); StateRef globalState = runConformance("apicalls.lua");
lua_State* L = globalState.get(); lua_State* L = globalState.get();
lua_getfield(L, LUA_GLOBALSINDEX, "add"); // lua_call
lua_pushnumber(L, 40); {
lua_pushnumber(L, 2); lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_call(L, 2, 1); lua_pushnumber(L, 40);
CHECK(lua_isnumber(L, -1)); lua_pushnumber(L, 2);
CHECK(lua_tonumber(L, -1) == 42); lua_call(L, 2, 1);
lua_pop(L, 1); CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
}
lua_getfield(L, LUA_GLOBALSINDEX, "add"); // lua_pcall
lua_pushnumber(L, 40); {
lua_pushnumber(L, 2); lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pcall(L, 2, 1, 0); lua_pushnumber(L, 40);
CHECK(lua_isnumber(L, -1)); lua_pushnumber(L, 2);
CHECK(lua_tonumber(L, -1) == 42); lua_pcall(L, 2, 1, 0);
lua_pop(L, 1); CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
}
// lua_equal with a sleeping thread wake up
{
ScopedFastFlag luauActivateBeforeExec("LuauActivateBeforeExec", true);
lua_State* L2 = lua_newthread(L);
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
lua_pushnumber(L2, 42);
lua_pcall(L2, 1, 1, 0);
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
lua_pushnumber(L2, 42);
lua_pcall(L2, 1, 1, 0);
// Reset GC
lua_gc(L2, LUA_GCCOLLECT, 0);
// Try to mark 'L2' as sleeping
// Can't control GC precisely, even in tests
lua_gc(L2, LUA_GCSTEP, 8);
CHECK(lua_equal(L2, -1, -2) == 1);
lua_pop(L2, 2);
}
} }
static bool endsWith(const std::string& str, const std::string& suffix) static bool endsWith(const std::string& str, const std::string& suffix)
@ -738,8 +773,6 @@ static bool endsWith(const std::string& str, const std::string& suffix)
#if !LUA_USE_LONGJMP #if !LUA_USE_LONGJMP
TEST_CASE("ExceptionObject") TEST_CASE("ExceptionObject")
{ {
ScopedFastFlag sff("LuauExceptionMessageFix", true);
struct ExceptionResult struct ExceptionResult
{ {
bool exceptionGenerated; bool exceptionGenerated;
@ -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(); TEST_SUITE_END();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2303,8 +2303,6 @@ TEST_CASE_FIXTURE(Fixture, "capture_comments")
TEST_CASE_FIXTURE(Fixture, "capture_broken_comment_at_the_start_of_the_file") TEST_CASE_FIXTURE(Fixture, "capture_broken_comment_at_the_start_of_the_file")
{ {
ScopedFastFlag sff{"LuauCaptureBrokenCommentSpans", true};
ParseOptions options; ParseOptions options;
options.captureComments = true; 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") TEST_CASE_FIXTURE(Fixture, "capture_broken_comment")
{ {
ScopedFastFlag sff{"LuauCaptureBrokenCommentSpans", true};
ParseOptions options; ParseOptions options;
options.captureComments = true; options.captureComments = true;
@ -2518,8 +2514,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_if_else_expression")
TEST_CASE_FIXTURE(Fixture, "parse_type_pack_type_parameters") TEST_CASE_FIXTURE(Fixture, "parse_type_pack_type_parameters")
{ {
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
AstStat* stat = parse(R"( AstStat* stat = parse(R"(
type Packed<T...> = () -> T... type Packed<T...> = () -> T...

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

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more