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

View file

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

View file

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

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 DuplicateGenericParameter& error);
std::ostream& operator<<(std::ostream& lhs, const CannotInferBinaryOperation& error);
std::ostream& operator<<(std::ostream& lhs, const SwappedGenericTypeParameter& error);
std::ostream& operator<<(std::ostream& lhs, const OptionalValueAccess& error);
std::ostream& operator<<(std::ostream& lhs, const MissingUnionProperty& error);
std::ostream& operator<<(std::ostream& lhs, const TypesAreUnrelated& error);
std::ostream& operator<<(std::ostream& lhs, const TableState& tv);
std::ostream& operator<<(std::ostream& lhs, const TypeVar& tv);

View file

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

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
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression
void dump(TypeId ty);
void dump(TypePackId ty);
std::string dump(TypeId ty);
std::string dump(TypePackId ty);
std::string generateName(size_t n);

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& 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).
// Note: the binding may be null.
// TODO: remove second return value with FFlagLuauUpdateFunctionNameBinding
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExpr& expr);
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr);
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprGlobal& expr);
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr);
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr);
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName);
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level);
std::pair<TypeId, ScopePtr> checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
std::optional<Location> originalNameLoc, std::optional<TypeId> expectedType);
void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function);
@ -174,7 +175,7 @@ struct TypeChecker
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExprCall& expr);
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
std::optional<ExprResult<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
TypePackId argPack, TypePack* args, const std::vector<Location>& argLocations, const ExprResult<TypePackId>& argListResult,
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const ExprResult<TypePackId>& argListResult,
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors);
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
const std::vector<OverloadErrorEntry>& errors);
@ -277,7 +278,7 @@ public:
[[noreturn]] void ice(const std::string& message);
ScopePtr childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
ScopePtr childScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
ScopePtr childScope(const ScopePtr& parent, const Location& location);
// Wrapper for merge(l, r, toUnion) but without the lambda junk.
void merge(RefinementMap& l, const RefinementMap& r);
@ -297,7 +298,6 @@ private:
private:
Unifier mkUnifier(const Location& location);
Unifier mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location);
// These functions are only safe to call when we are in the process of typechecking a module.

View file

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

View file

@ -24,7 +24,7 @@ struct TypeLevel
int level = 0;
int subLevel = 0;
// Returns true if the typelevel "this" is "bigger" than rhs
// Returns true if the level of "this" belongs to an equal or larger scope than that of rhs
bool subsumes(const TypeLevel& rhs) const
{
if (level < rhs.level)
@ -38,6 +38,15 @@ struct TypeLevel
return false;
}
// Returns true if the level of "this" belongs to a larger (not equal) scope than that of rhs
bool subsumesStrict(const TypeLevel& rhs) const
{
if (level == rhs.level && subLevel == rhs.subLevel)
return false;
else
return subsumes(rhs);
}
TypeLevel incr() const
{
TypeLevel result;

View file

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

View file

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

View file

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

View file

@ -12,9 +12,10 @@
#include <unordered_set>
#include <utility>
LUAU_FASTFLAGVARIABLE(ElseElseIfCompletionImprovements, false);
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteFirstArg, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -190,7 +191,48 @@ static ParenthesesRecommendation getParenRecommendation(TypeId id, const std::ve
return ParenthesesRecommendation::None;
}
static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typeArena, AstNode* node, TypeId ty)
static std::optional<TypeId> findExpectedTypeAt(const Module& module, AstNode* node, Position position)
{
LUAU_ASSERT(FFlag::LuauAutocompleteFirstArg);
auto expr = node->asExpr();
if (!expr)
return std::nullopt;
// Extra care for first function call argument location
// When we don't have anything inside () yet, we also don't have an AST node to base our lookup
if (AstExprCall* exprCall = expr->as<AstExprCall>())
{
if (exprCall->args.size == 0 && exprCall->argLocation.contains(position))
{
auto it = module.astTypes.find(exprCall->func);
if (!it)
return std::nullopt;
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*it));
if (!ftv)
return std::nullopt;
auto [head, tail] = flatten(ftv->argTypes);
unsigned index = exprCall->self ? 1 : 0;
if (index < head.size())
return head[index];
return std::nullopt;
}
}
auto it = module.astExpectedTypes.find(expr);
if (!it)
return std::nullopt;
return *it;
}
static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typeArena, AstNode* node, Position position, TypeId ty)
{
ty = follow(ty);
@ -203,8 +245,9 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
{
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, nullptr);
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, nullptr);
CloneState cloneState;
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
auto errors = unifier.canUnify(expectedType, actualType);
return errors.empty();
@ -219,6 +262,19 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
}
};
TypeId expectedType;
if (FFlag::LuauAutocompleteFirstArg)
{
auto typeAtPosition = findExpectedTypeAt(module, node, position);
if (!typeAtPosition)
return TypeCorrectKind::None;
expectedType = follow(*typeAtPosition);
}
else
{
auto expr = node->asExpr();
if (!expr)
return TypeCorrectKind::None;
@ -227,8 +283,31 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
if (!it)
return TypeCorrectKind::None;
TypeId expectedType = follow(*it);
expectedType = follow(*it);
}
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;
@ -252,6 +331,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
return TypeCorrectKind::None;
}
}
enum class PropIndexType
{
@ -309,8 +389,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
if (result.count(name) == 0 && name != Parser::errorName)
{
Luau::TypeId type = Luau::follow(prop.type);
TypeCorrectKind typeCorrect =
indexType == PropIndexType::Key ? TypeCorrectKind::Correct : checkTypeCorrectKind(module, typeArena, nodes.back(), type);
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct
: checkTypeCorrectKind(module, typeArena, nodes.back(), {{}, {}}, type);
ParenthesesRecommendation parens =
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
@ -668,7 +748,20 @@ std::optional<const T*> returnFirstNonnullOptionOfType(const UnionTypeVar* utv)
return ret;
}
static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* node)
static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* node, Position position)
{
TypeId expectedType;
if (FFlag::LuauAutocompleteFirstArg)
{
auto typeAtPosition = findExpectedTypeAt(module, node, position);
if (!typeAtPosition)
return std::nullopt;
expectedType = follow(*typeAtPosition);
}
else
{
auto expr = node->asExpr();
if (!expr)
@ -678,7 +771,8 @@ static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* n
if (!it)
return std::nullopt;
TypeId expectedType = follow(*it);
expectedType = follow(*it);
}
if (get<FunctionTypeVar>(expectedType))
return true;
@ -1147,7 +1241,7 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
std::string n = toString(name);
if (!result.count(n))
{
TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, node, binding.typeId);
TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, node, position, binding.typeId);
result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt,
binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)};
@ -1157,9 +1251,10 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
scope = scope->parent;
}
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, typeChecker.nilType);
TypeCorrectKind correctForBoolean = checkTypeCorrectKind(module, typeArena, node, typeChecker.booleanType);
TypeCorrectKind correctForFunction = functionIsExpectedAt(module, node).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.nilType);
TypeCorrectKind correctForBoolean = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.booleanType);
TypeCorrectKind correctForFunction =
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
if (FFlag::LuauIfElseExpressionAnalysisSupport)
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false};
@ -1413,7 +1508,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (AstStatIf* statIf = node->as<AstStatIf>(); FFlag::ElseElseIfCompletionImprovements && statIf && !statIf->hasElse)
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->hasElse)
{
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
finder.ancestry};

View file

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

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
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauFixTonumberReturnType, false)
namespace Luau
{
@ -113,7 +115,6 @@ declare function gcinfo(): number
declare function error<T>(message: T, level: number?)
declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
@ -204,7 +205,14 @@ declare function gcinfo(): number
std::string getBuiltinDefinitionSource()
{
return kBuiltinDefinitionLuaSrc;
std::string result = kBuiltinDefinitionLuaSrc;
if (FFlag::LuauFixTonumberReturnType)
result += "declare function tonumber<T>(value: T, radix: number?): number?\n";
else
result += "declare function tonumber<T>(value: T, radix: number?): number\n";
return result;
}
} // namespace Luau

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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>
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false)
/*
* Prefix generic typenames with gen-
* Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4
* Fair warning: Setting this will break a lot of Luau unit tests.
*/
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false)
namespace Luau
{
@ -59,11 +66,8 @@ struct FindCyclicTypes
for (TypeId itp : ttv.instantiatedTypeParams)
visitTypeVar(itp, *this, seen);
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId itp : ttv.instantiatedTypePackParams)
visitTypeVar(itp, *this, seen);
}
return exhaustive;
}
@ -248,14 +252,12 @@ struct TypeVarStringifier
void stringify(const std::vector<TypeId>& types, const std::vector<TypePackId>& typePacks)
{
if (types.size() == 0 && (!FFlag::LuauTypeAliasPacks || typePacks.size() == 0))
if (types.size() == 0 && typePacks.size() == 0)
return;
if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size()))
if (types.size() || typePacks.size())
state.emit("<");
if (FFlag::LuauTypeAliasPacks)
{
bool first = true;
for (TypeId ty : types)
@ -287,26 +289,23 @@ struct TypeVarStringifier
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(">");
}
void operator()(TypeId ty, const Unifiable::Free& ftv)
{
state.result.invalid = true;
if (FFlag::DebugLuauVerboseTypeNames)
state.emit("free-");
state.emit(state.getName(ty));
if (FFlag::DebugLuauVerboseTypeNames)
{
state.emit("-");
state.emit(std::to_string(ftv.level.level));
}
}
void operator()(TypeId, const BoundTypeVar& btv)
@ -767,6 +766,16 @@ struct TypePackStringifier
else
state.emit(", ");
if (FFlag::LuauFunctionArgumentNameSize)
{
if (elemIndex < elemNames.size() && elemNames[elemIndex])
{
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
}
else
{
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
if (!elemNames.empty() && elemNames[elemIndex])
@ -774,6 +783,7 @@ struct TypePackStringifier
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
}
elemIndex++;
stringify(typeId);
@ -807,6 +817,8 @@ struct TypePackStringifier
void operator()(TypePackId tp, const GenericTypePack& pack)
{
if (FFlag::DebugLuauVerboseTypeNames)
state.emit("gen-");
if (pack.explicitName)
{
state.result.nameMap.typePacks[tp] = pack.name;
@ -822,7 +834,16 @@ struct TypePackStringifier
void operator()(TypePackId tp, const FreeTypePack& pack)
{
state.result.invalid = true;
if (FFlag::DebugLuauVerboseTypeNames)
state.emit("free-");
state.emit(state.getName(tp));
if (FFlag::DebugLuauVerboseTypeNames)
{
state.emit("-");
state.emit(std::to_string(pack.level.level));
}
state.emit("...");
}
@ -929,38 +950,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
result.name += ttv->name ? *ttv->name : *ttv->syntheticName;
if (FFlag::LuauTypeAliasPacks)
{
tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams);
}
else
{
if (ttv->instantiatedTypeParams.empty() && (!FFlag::LuauTypeAliasPacks || ttv->instantiatedTypePackParams.empty()))
return result;
result.name += "<";
bool first = true;
for (TypeId ty : ttv->instantiatedTypeParams)
{
if (!first)
result.name += ", ";
else
first = false;
tvs.stringify(ty);
}
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
{
result.truncated = true;
result.name += "... <TRUNCATED>";
}
else
{
result.name += ">";
}
}
return result;
}
@ -1161,19 +1151,39 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
s += ", ";
first = false;
if (FFlag::LuauFunctionArgumentNameSize)
{
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (argNameIter != ftv.argNames.end())
{
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
++argNameIter;
}
else
{
s += "_: ";
}
}
else
{
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (!ftv.argNames.empty())
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
s += toString_(*argPackIter);
}
s += toString_(*argPackIter);
++argPackIter;
if (!FFlag::LuauFunctionArgumentNameSize)
{
if (!ftv.argNames.empty())
{
LUAU_ASSERT(argNameIter != ftv.argNames.end());
++argNameIter;
}
}
}
if (argPackIter.tail())
{
@ -1197,20 +1207,24 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
return s;
}
void dump(TypeId ty)
std::string dump(TypeId ty)
{
ToStringOptions opts;
opts.exhaustive = true;
opts.functionTypeArguments = true;
printf("%s\n", toString(ty, opts).c_str());
std::string s = toString(ty, opts);
printf("%s\n", s.c_str());
return s;
}
void dump(TypePackId ty)
std::string dump(TypePackId ty)
{
ToStringOptions opts;
opts.exhaustive = true;
opts.functionTypeArguments = true;
printf("%s\n", toString(ty, opts).c_str());
std::string s = toString(ty, opts);
printf("%s\n", s.c_str());
return s;
}
std::string generateName(size_t i)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,9 +19,9 @@
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTFLAG(LuauTypeAliasPacks)
LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false)
LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAG(DebugLuauFreezeArena)
namespace Luau
{
@ -580,11 +580,25 @@ SingletonTypes::SingletonTypes()
, arena(new TypeArena)
{
TypeId stringMetatable = makeStringMetatable();
stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, makeStringMetatable()};
stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, stringMetatable};
persist(stringMetatable);
debugFreezeArena = FFlag::DebugLuauFreezeArena;
freeze(*arena);
}
SingletonTypes::~SingletonTypes()
{
// Destroy the arena with the same memory management flags it was created with
bool prevFlag = FFlag::DebugLuauFreezeArena;
FFlag::DebugLuauFreezeArena.value = debugFreezeArena;
unfreeze(*arena);
arena.reset(nullptr);
FFlag::DebugLuauFreezeArena.value = prevFlag;
}
TypeId SingletonTypes::makeStringMetatable()
{
const TypeId optionalNumber = arena->addType(UnionTypeVar{{nilType, numberType}});
@ -642,6 +656,9 @@ TypeId SingletonTypes::makeStringMetatable()
TypeId tableType = arena->addType(TableTypeVar{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
if (TableTypeVar* ttv = getMutable<TableTypeVar>(tableType))
ttv->name = "string";
return arena->addType(TableTypeVar{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
}
@ -671,7 +688,11 @@ TypePackId SingletonTypes::errorRecoveryTypePack(TypePackId guess)
return &errorTypePack_;
}
SingletonTypes singletonTypes;
SingletonTypes& getSingletonTypes()
{
static SingletonTypes singletonTypes;
return singletonTypes;
}
void persist(TypeId ty)
{
@ -720,6 +741,18 @@ void persist(TypeId ty)
for (TypeId opt : itv->parts)
queue.push_back(opt);
}
else if (auto mtv = get<MetatableTypeVar>(t))
{
queue.push_back(mtv->table);
queue.push_back(mtv->metatable);
}
else if (get<GenericTypeVar>(t) || get<AnyTypeVar>(t) || get<FreeTypeVar>(t) || get<SingletonTypeVar>(t) || get<PrimitiveTypeVar>(t))
{
}
else
{
LUAU_ASSERT(!"TypeId is not supported in a persist call");
}
}
}
@ -737,371 +770,19 @@ void persist(TypePackId tp)
if (p->tail)
persist(*p->tail);
}
else if (auto vtp = get<VariadicTypePack>(tp))
{
persist(vtp->ty);
}
namespace
else if (get<GenericTypePack>(tp))
{
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);
LUAU_ASSERT(!"TypePackId is not supported in a persist call");
}
}
void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName)
{
if (!tpToIndex.count(tp))
tpToIndex[tp] = nextIndex++;
if (linkName)
formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName);
else
formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]);
visitChildren(tp, tpToIndex[tp]);
}
void StateDot::startNode(int index)
{
formatAppend(result, "n%d [", index);
}
void StateDot::finishNode()
{
formatAppend(result, "];\n");
}
void StateDot::startNodeLabel()
{
formatAppend(result, "label=\"");
}
void StateDot::finishNodeLabel(TypeId ty)
{
if (opts.showPointers)
formatAppend(result, "\n0x%p", ty);
// additional common attributes can be added here as well
result += "\"";
}
void StateDot::finishNodeLabel(TypePackId tp)
{
if (opts.showPointers)
formatAppend(result, "\n0x%p", tp);
// additional common attributes can be added here as well
result += "\"";
}
void StateDot::visitChildren(TypeId ty, int index)
{
if (seenTy.count(ty))
return;
seenTy.insert(ty);
startNode(index);
startNodeLabel();
if (const BoundTypeVar* btv = get<BoundTypeVar>(ty))
{
formatAppend(result, "BoundTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(btv->boundTo, index);
}
else if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
formatAppend(result, "FunctionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(ftv->argTypes, index, "arg");
visitChild(ftv->retType, index, "ret");
}
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
if (ttv->name)
formatAppend(result, "TableTypeVar %s", ttv->name->c_str());
else if (ttv->syntheticName)
formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str());
else
formatAppend(result, "TableTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
if (ttv->boundTo)
return visitChild(*ttv->boundTo, index, "boundTo");
for (const auto& [name, prop] : ttv->props)
visitChild(prop.type, index, name.c_str());
if (ttv->indexer)
{
visitChild(ttv->indexer->indexType, index, "[index]");
visitChild(ttv->indexer->indexResultType, index, "[value]");
}
for (TypeId itp : ttv->instantiatedTypeParams)
visitChild(itp, index, "typeParam");
if (FFlag::LuauTypeAliasPacks)
{
for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp, index, "typePackParam");
}
}
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
formatAppend(result, "MetatableTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
visitChild(mtv->table, index, "table");
visitChild(mtv->metatable, index, "metatable");
}
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
formatAppend(result, "UnionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
for (TypeId opt : utv->options)
visitChild(opt, index);
}
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
formatAppend(result, "IntersectionTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
for (TypeId part : itv->parts)
visitChild(part, index);
}
else if (const GenericTypeVar* gtv = get<GenericTypeVar>(ty))
{
if (gtv->explicitName)
formatAppend(result, "GenericTypeVar %s", gtv->name.c_str());
else
formatAppend(result, "GenericTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
{
formatAppend(result, "FreeTypeVar %d", ftv->index);
finishNodeLabel(ty);
finishNode();
}
else if (get<AnyTypeVar>(ty))
{
formatAppend(result, "AnyTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (get<PrimitiveTypeVar>(ty))
{
formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str());
finishNodeLabel(ty);
finishNode();
}
else if (get<ErrorTypeVar>(ty))
{
formatAppend(result, "ErrorTypeVar %d", index);
finishNodeLabel(ty);
finishNode();
}
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty))
{
formatAppend(result, "ClassTypeVar %s", ctv->name.c_str());
finishNodeLabel(ty);
finishNode();
for (const auto& [name, prop] : ctv->props)
visitChild(prop.type, index, name.c_str());
if (ctv->parent)
visitChild(*ctv->parent, index, "[parent]");
if (ctv->metatable)
visitChild(*ctv->metatable, index, "[metatable]");
}
else
{
LUAU_ASSERT(!"unknown type kind");
finishNodeLabel(ty);
finishNode();
}
}
void StateDot::visitChildren(TypePackId tp, int index)
{
if (seenTp.count(tp))
return;
seenTp.insert(tp);
startNode(index);
startNodeLabel();
if (const BoundTypePack* btp = get<BoundTypePack>(tp))
{
formatAppend(result, "BoundTypePack %d", index);
finishNodeLabel(tp);
finishNode();
visitChild(btp->boundTo, index);
}
else if (const TypePack* tpp = get<TypePack>(tp))
{
formatAppend(result, "TypePack %d", index);
finishNodeLabel(tp);
finishNode();
for (TypeId tv : tpp->head)
visitChild(tv, index);
if (tpp->tail)
visitChild(*tpp->tail, index, "tail");
}
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
formatAppend(result, "VariadicTypePack %d", index);
finishNodeLabel(tp);
finishNode();
visitChild(vtp->ty, index);
}
else if (const FreeTypePack* ftp = get<FreeTypePack>(tp))
{
formatAppend(result, "FreeTypePack %d", ftp->index);
finishNodeLabel(tp);
finishNode();
}
else if (const GenericTypePack* gtp = get<GenericTypePack>(tp))
{
if (gtp->explicitName)
formatAppend(result, "GenericTypePack %s", gtp->name.c_str());
else
formatAppend(result, "GenericTypePack %d", gtp->index);
finishNodeLabel(tp);
finishNode();
}
else if (get<Unifiable::Error>(tp))
{
formatAppend(result, "ErrorTypePack %d", index);
finishNodeLabel(tp);
finishNode();
}
else
{
LUAU_ASSERT(!"unknown type pack kind");
finishNodeLabel(tp);
finishNode();
}
}
} // namespace
std::string toDot(TypeId ty, const ToDotOptions& opts)
{
StateDot state{opts};
state.result = "digraph graphname {\n";
state.visitChild(ty, 0);
state.result += "}";
return state.result;
}
std::string toDot(TypePackId tp, const ToDotOptions& opts)
{
StateDot state{opts};
state.result = "digraph graphname {\n";
state.visitChild(tp, 0);
state.result += "}";
return state.result;
}
std::string toDot(TypeId ty)
{
return toDot(ty, {});
}
std::string toDot(TypePackId tp)
{
return toDot(tp, {});
}
void dumpDot(TypeId ty)
{
printf("%s\n", toDot(ty).c_str());
}
void dumpDot(TypePackId tp)
{
printf("%s\n", toDot(tp).c_str());
}
const TypeLevel* getLevel(TypeId ty)
{
ty = follow(ty);
@ -1121,167 +802,6 @@ TypeLevel* getMutableLevel(TypeId ty)
return const_cast<TypeLevel*>(getLevel(ty));
}
struct QVarFinder
{
mutable DenseHashSet<const void*> seen;
QVarFinder()
: seen(nullptr)
{
}
bool hasSeen(const void* tv) const
{
if (seen.contains(tv))
return true;
seen.insert(tv);
return false;
}
bool hasGeneric(TypeId tid) const
{
if (hasSeen(&tid->ty))
return false;
return Luau::visit(*this, tid->ty);
}
bool hasGeneric(TypePackId tp) const
{
if (hasSeen(&tp->ty))
return false;
return Luau::visit(*this, tp->ty);
}
bool operator()(const Unifiable::Free&) const
{
return false;
}
bool operator()(const Unifiable::Bound<TypeId>& bound) const
{
return hasGeneric(bound.boundTo);
}
bool operator()(const Unifiable::Generic&) const
{
return true;
}
bool operator()(const Unifiable::Error&) const
{
return false;
}
bool operator()(const PrimitiveTypeVar&) const
{
return false;
}
bool operator()(const SingletonTypeVar&) const
{
return false;
}
bool operator()(const FunctionTypeVar& ftv) const
{
if (hasGeneric(ftv.argTypes))
return true;
return hasGeneric(ftv.retType);
}
bool operator()(const TableTypeVar& ttv) const
{
if (ttv.state == TableState::Generic)
return true;
if (ttv.indexer)
{
if (hasGeneric(ttv.indexer->indexType))
return true;
if (hasGeneric(ttv.indexer->indexResultType))
return true;
}
for (const auto& [_name, prop] : ttv.props)
{
if (hasGeneric(prop.type))
return true;
}
return false;
}
bool operator()(const MetatableTypeVar& mtv) const
{
return hasGeneric(mtv.table) || hasGeneric(mtv.metatable);
}
bool operator()(const ClassTypeVar& ctv) const
{
for (const auto& [name, prop] : ctv.props)
{
if (hasGeneric(prop.type))
return true;
}
if (ctv.parent)
return hasGeneric(*ctv.parent);
return false;
}
bool operator()(const AnyTypeVar&) const
{
return false;
}
bool operator()(const UnionTypeVar& utv) const
{
for (TypeId tid : utv.options)
if (hasGeneric(tid))
return true;
return false;
}
bool operator()(const IntersectionTypeVar& utv) const
{
for (TypeId tid : utv.parts)
if (hasGeneric(tid))
return true;
return false;
}
bool operator()(const LazyTypeVar&) const
{
return false;
}
bool operator()(const Unifiable::Bound<TypePackId>& bound) const
{
return hasGeneric(bound.boundTo);
}
bool operator()(const TypePack& pack) const
{
for (TypeId ty : pack.head)
if (hasGeneric(ty))
return true;
if (pack.tail)
return hasGeneric(*pack.tail);
return false;
}
bool operator()(const VariadicTypePack& pack) const
{
return hasGeneric(pack.ty);
}
};
const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name)
{
while (cls)
@ -1317,16 +837,6 @@ bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent)
return false;
}
bool hasGeneric(TypeId ty)
{
return Luau::visit(QVarFinder{}, ty->ty);
}
bool hasGeneric(TypePackId tp)
{
return Luau::visit(QVarFinder{}, tp->ty);
}
UnionTypeVarIterator::UnionTypeVarIterator(const UnionTypeVar* utv)
{
LUAU_ASSERT(utv);

View file

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

View file

@ -10,11 +10,8 @@
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false)
LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAliasPacks, false)
LUAU_FASTFLAGVARIABLE(LuauParseTypePackTypeParameters, false)
LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false)
@ -161,7 +158,7 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
{
std::vector<std::string> hotcomments;
while (isComment(p.lexer.current()) || (FFlag::LuauCaptureBrokenCommentSpans && p.lexer.current().type == Lexeme::BrokenComment))
while (isComment(p.lexer.current()) || p.lexer.current().type == Lexeme::BrokenComment)
{
const char* text = p.lexer.current().data;
unsigned int length = p.lexer.current().length;
@ -782,8 +779,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
AstType* type = parseTypeAnnotation();
return allocator.alloc<AstStatTypeAlias>(
Location(start, type->location), name->name, generics, FFlag::LuauTypeAliasPacks ? genericPacks : AstArray<AstName>{}, type, exported);
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, generics, genericPacks, type, exported);
}
AstDeclaredClassProp Parser::parseDeclaredClassMethod()
@ -1602,8 +1598,6 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
}
if (FFlag::LuauParseTypePackTypeParameters)
{
bool hasParameters = false;
AstArray<AstTypeOrPack> parameters{};
@ -1617,16 +1611,6 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
}
else
{
AstArray<AstTypeOrPack> generics = parseTypeParams();
Location end = lexer.previousLocation();
// false in 'hasParameterList' as it is not used without FFlagLuauTypeAliasPacks
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, false, generics), {}};
}
}
else if (lexer.current().type == '{')
{
return {parseTableTypeAnnotation(), {}};
@ -2413,14 +2397,11 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
nextLexeme();
while (true)
{
if (FFlag::LuauParseTypePackTypeParameters)
{
if (shouldParseTypePackAnnotation(lexer))
{
auto typePack = parseTypePackAnnotation();
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
parameters.push_back({{}, typePack});
}
else if (lexer.current().type == '(')
@ -2428,15 +2409,10 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
auto [type, typePack] = parseTypeOrPackAnnotation();
if (typePack)
{
if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them
parameters.push_back({{}, typePack});
}
else
{
parameters.push_back({type, {}});
}
}
else if (lexer.current().type == '>' && parameters.empty())
{
break;
@ -2445,11 +2421,6 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
{
parameters.push_back({parseTypeAnnotation(), {}});
}
}
else
{
parameters.push_back({parseTypeAnnotation(), {}});
}
if (lexer.current().type == ',')
nextLexeme();
@ -2808,7 +2779,7 @@ const Lexeme& Parser::nextLexeme()
const Lexeme& lexeme = lexer.next(/*skipComments*/ false);
// Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme.
// The parser will turn this into a proper syntax error.
if (FFlag::LuauCaptureBrokenCommentSpans && lexeme.type == Lexeme::BrokenComment)
if (lexeme.type == Lexeme::BrokenComment)
commentLocations.push_back(Comment{lexeme.type, lexeme.location});
if (isComment(lexeme))
commentLocations.push_back(Comment{lexeme.type, lexeme.location});

View file

@ -11,26 +11,33 @@
enum class ReportFormat
{
Default,
Luacheck
Luacheck,
Gnu,
};
static void report(ReportFormat format, const char* name, const Luau::Location& location, const char* type, const char* message)
static void report(ReportFormat format, const char* name, const Luau::Location& loc, const char* type, const char* message)
{
switch (format)
{
case ReportFormat::Default:
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message);
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, loc.begin.line + 1, loc.begin.column + 1, type, message);
break;
case ReportFormat::Luacheck:
{
// Note: luacheck's end column is inclusive but our end column is exclusive
// In addition, luacheck doesn't support multi-line messages, so if the error is multiline we'll fake end column as 100 and hope for the best
int columnEnd = (location.begin.line == location.end.line) ? location.end.column : 100;
int columnEnd = (loc.begin.line == loc.end.line) ? loc.end.column : 100;
fprintf(stdout, "%s:%d:%d-%d: (W0) %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, columnEnd, type, message);
// Use stdout to match luacheck behavior
fprintf(stdout, "%s:%d:%d-%d: (W0) %s: %s\n", name, loc.begin.line + 1, loc.begin.column + 1, columnEnd, type, message);
break;
}
case ReportFormat::Gnu:
// Note: GNU end column is inclusive but our end column is exclusive
fprintf(stderr, "%s:%d.%d-%d.%d: %s: %s\n", name, loc.begin.line + 1, loc.begin.column + 1, loc.end.line + 1, loc.end.column, type, message);
break;
}
}
@ -97,6 +104,7 @@ static void displayHelp(const char* argv0)
printf("\n");
printf("Available options:\n");
printf(" --formatter=plain: report analysis errors in Luacheck-compatible format\n");
printf(" --formatter=gnu: report analysis errors in GNU-compatible format\n");
}
static int assertionHandler(const char* expr, const char* file, int line)
@ -121,7 +129,7 @@ struct CliFileResolver : Luau::FileResolver
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
{
Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau";
if (!moduleExists(name))
if (!readFile(name))
{
// fall back to .lua if a module with .luau doesn't exist
name = std::string(expr->value.data, expr->value.size) + ".lua";
@ -132,27 +140,6 @@ struct CliFileResolver : Luau::FileResolver
return std::nullopt;
}
bool moduleExists(const Luau::ModuleName& name) const override
{
return !!readFile(name);
}
std::optional<Luau::ModuleName> fromAstFragment(Luau::AstExpr* expr) const override
{
return std::nullopt;
}
Luau::ModuleName concat(const Luau::ModuleName& lhs, std::string_view rhs) const override
{
return lhs + "/" + std::string(rhs);
}
std::optional<Luau::ModuleName> getParentModuleName(const Luau::ModuleName& name) const override
{
return std::nullopt;
}
};
struct CliConfigResolver : Luau::ConfigResolver
@ -222,6 +209,8 @@ int main(int argc, char** argv)
if (strcmp(argv[i], "--formatter=plain") == 0)
format = ReportFormat::Luacheck;
else if (strcmp(argv[i], "--formatter=gnu") == 0)
format = ReportFormat::Gnu;
else if (strcmp(argv[i], "--annotate") == 0)
annotate = true;
}

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

View file

@ -110,12 +110,12 @@ void profilerStop()
gProfiler.thread.join();
}
void profilerDump(const char* name)
void profilerDump(const char* path)
{
FILE* f = fopen(name, "wb");
FILE* f = fopen(path, "wb");
if (!f)
{
fprintf(stderr, "Error opening profile %s\n", name);
fprintf(stderr, "Error opening profile %s\n", path);
return;
}
@ -129,7 +129,7 @@ void profilerDump(const char* name)
fclose(f);
printf("Profiler dump written to %s (total runtime %.3f seconds, %lld samples, %lld stacks)\n", name, double(total) / 1e6,
printf("Profiler dump written to %s (total runtime %.3f seconds, %lld samples, %lld stacks)\n", path, double(total) / 1e6,
static_cast<long long>(gProfiler.samples.load()), static_cast<long long>(gProfiler.data.size()));
uint64_t totalgc = 0;

View file

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

View file

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

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.
For features that result in observable change of language syntax or semantics, you'd need to [create an RFC](https://github.com/Roblox/luau/blob/master/rfcs/README.md) to make sure that the feature is needed and well designed.
Similarly to the above, please create an issue first so that we can see if we should go through with an RFC process.
Finally, please note that Luau tries to carry a minimal feature set. All features must be evaluated not just for the benefits that they provide, but also for the downsides/costs in terms of language simplicity, maintainability, cross-feature interaction etc.
As such, feature requests may not be accepted, or may get to an RFC stage and get rejected there - don't expect Luau to gain a feature just because another programming language has it.
As such, feature requests may not be accepted even if a comprehensive RFC is written - don't expect Luau to gain a feature just because another programming language has it.
## Code style

View file

@ -10,7 +10,6 @@
#include <bitset>
#include <math.h>
LUAU_FASTFLAGVARIABLE(LuauPreloadClosures, false)
LUAU_FASTFLAG(LuauIfElseExpressionBaseSupport)
LUAU_FASTFLAGVARIABLE(LuauBit32CountBuiltin, false)
@ -321,13 +320,15 @@ struct Compiler
compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i));
}
setDebugLine(expr->func);
setDebugLineEnd(expr->func);
if (expr->self)
{
AstExprIndexName* fi = expr->func->as<AstExprIndexName>();
LUAU_ASSERT(fi);
setDebugLine(fi->indexLocation);
BytecodeBuilder::StringRef iname = sref(fi->index);
int32_t cid = bytecode.addConstantString(iname);
if (cid < 0)
@ -460,8 +461,6 @@ struct Compiler
bool shared = false;
if (FFlag::LuauPreloadClosures)
{
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
// objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it
// is used)
@ -475,7 +474,6 @@ struct Compiler
shared = true;
}
}
}
if (!shared)
bytecode.emitAD(LOP_NEWCLOSURE, target, pid);
@ -1313,6 +1311,8 @@ struct Compiler
RegScope rs(this);
uint8_t reg = compileExprAuto(expr->expr, rs);
setDebugLine(expr->indexLocation);
BytecodeBuilder::StringRef iname = sref(expr->index);
int32_t cid = bytecode.addConstantString(iname);
if (cid < 0)
@ -2710,6 +2710,12 @@ struct Compiler
bytecode.setDebugLine(node->location.begin.line + 1);
}
void setDebugLine(const Location& location)
{
if (options.debugLevel >= 1)
bytecode.setDebugLine(location.begin.line + 1);
}
void setDebugLineEnd(AstNode* node)
{
if (options.debugLevel >= 1)
@ -3650,7 +3656,7 @@ struct Compiler
{
if (options.vectorLib)
{
if (builtin.object == options.vectorLib && builtin.method == options.vectorCtor)
if (builtin.isMethod(options.vectorLib, options.vectorCtor))
return LBF_VECTOR;
}
else

View file

@ -27,7 +27,7 @@ TESTS_SOURCES=$(wildcard tests/*.cpp)
TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o)
TESTS_TARGET=$(BUILD)/luau-tests
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Profiler.cpp CLI/Repl.cpp
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp
REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o)
REPL_CLI_TARGET=$(BUILD)/luau
@ -35,7 +35,7 @@ ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Analyze.cpp
ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze
FUZZ_SOURCES=$(wildcard fuzz/*.cpp)
FUZZ_SOURCES=$(wildcard fuzz/*.cpp) fuzz/luau.pb.cpp
FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o)
TESTS_ARGS=
@ -128,10 +128,10 @@ luau-size: luau
# executable target aliases
luau: $(REPL_CLI_TARGET)
cp $^ $@
ln -fs $^ $@
luau-analyze: $(ANALYZE_CLI_TARGET)
cp $^ $@
ln -fs $^ $@
# executable targets
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET)
@ -167,8 +167,8 @@ fuzz/luau.pb.cpp: fuzz/luau.proto build/libprotobuf-mutator
cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=.
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator
$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator
$(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp
$(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp
build/libprotobuf-mutator:
git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator

View file

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

View file

@ -21,6 +21,7 @@
#define LUA_ENVIRONINDEX (-10001)
#define LUA_GLOBALSINDEX (-10002)
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i))
#define lua_ispseudo(i) ((i) <= LUA_REGISTRYINDEX)
/* thread status; 0 is OK */
enum lua_Status
@ -108,6 +109,7 @@ LUA_API int lua_isthreadreset(lua_State* L);
/*
** basic stack manipulation
*/
LUA_API int lua_absindex(lua_State* L, int idx);
LUA_API int lua_gettop(lua_State* L);
LUA_API void lua_settop(lua_State* L, int idx);
LUA_API void lua_pushvalue(lua_State* L, int idx);
@ -187,7 +189,7 @@ LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);
LUA_API int lua_getreadonly(lua_State* L, int idx);
LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled);
LUA_API void* lua_newuserdata(lua_State* L, size_t sz, int tag);
LUA_API void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag);
LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
LUA_API int lua_getmetatable(lua_State* L, int objindex);
LUA_API void lua_getfenv(lua_State* L, int idx);
@ -231,6 +233,7 @@ enum lua_GCOp
LUA_GCRESTART,
LUA_GCCOLLECT,
LUA_GCCOUNT,
LUA_GCCOUNTB,
LUA_GCISRUNNING,
// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
@ -285,6 +288,7 @@ LUA_API void lua_unref(lua_State* L, int ref);
#define lua_pop(L, n) lua_settop(L, -(n)-1)
#define lua_newtable(L) lua_createtable(L, 0, 0)
#define lua_newuserdata(L, s) lua_newuserdatatagged(L, s, 0)
#define lua_strlen(L, i) lua_objlen(L, (i))
@ -293,6 +297,7 @@ LUA_API void lua_unref(lua_State* L, int ref);
#define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isvector(L, n) (lua_type(L, (n)) == LUA_TVECTOR)
#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL)
@ -329,6 +334,10 @@ LUA_API const char* lua_setupvalue(lua_State* L, int funcindex, int n);
LUA_API void lua_singlestep(lua_State* L, int enabled);
LUA_API void lua_breakpoint(lua_State* L, int funcindex, int line, int enabled);
typedef void (*lua_Coverage)(void* context, const char* function, int linedefined, int depth, const int* hits, size_t size);
LUA_API void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback);
/* Warning: this function is not thread-safe since it stores the result in a shared global array! Only use for debugging. */
LUA_API const char* lua_debugtrace(lua_State* L);

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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));
}
static int getmaxline(Proto* p)
{
int result = -1;
for (int i = 0; i < p->sizecode; ++i)
{
int line = luaG_getline(p, i);
result = result < line ? line : result;
}
for (int i = 0; i < p->sizep; ++i)
{
int psize = getmaxline(p->p[i]);
result = result < psize ? psize : result;
}
return result;
}
static void getcoverage(Proto* p, int depth, int* buffer, size_t size, void* context, lua_Coverage callback)
{
memset(buffer, -1, size * sizeof(int));
for (int i = 0; i < p->sizecode; ++i)
{
Instruction insn = p->code[i];
if (LUAU_INSN_OP(insn) != LOP_COVERAGE)
continue;
int line = luaG_getline(p, i);
int hits = LUAU_INSN_E(insn);
LUAU_ASSERT(size_t(line) < size);
buffer[line] = buffer[line] < hits ? hits : buffer[line];
}
const char* debugname = p->debugname ? getstr(p->debugname) : NULL;
int linedefined = luaG_getline(p, 0);
callback(context, debugname, linedefined, depth, buffer, size);
for (int i = 0; i < p->sizep; ++i)
getcoverage(p->p[i], depth + 1, buffer, size, context, callback);
}
void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback)
{
const TValue* func = luaA_toobject(L, funcindex);
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
Proto* p = clvalue(func)->l.p;
size_t size = getmaxline(p) + 1;
if (size == 0)
return;
int* buffer = luaM_newarray(L, size, int, 0);
getcoverage(p, 0, buffer, size, context, callback);
luaM_freearray(L, buffer, size, int, 0);
}
static size_t append(char* buf, size_t bufsize, size_t offset, const char* data)
{
size_t size = strlen(data);

View file

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

View file

@ -8,12 +8,9 @@
#include "lfunc.h"
#include "lstring.h"
#include "ldo.h"
#include "ludata.h"
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauRescanGrayAgainForwardBarrier, false)
LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false)
LUAU_FASTFLAG(LuauArrayBoundary)
@ -61,10 +58,6 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
case GCSpropagate:
case GCSpropagateagain:
g->gcstats.currcycle.marktime += seconds;
// atomic step had to be performed during the switch and it's tracked separately
if (!FFlag::LuauSeparateAtomic && g->gcstate == GCSsweepstring)
g->gcstats.currcycle.marktime -= g->gcstats.currcycle.atomictime;
break;
case GCSatomic:
g->gcstats.currcycle.atomictime += seconds;
@ -490,7 +483,7 @@ static void freeobj(lua_State* L, GCObject* o)
luaS_free(L, gco2ts(o));
break;
case LUA_TUSERDATA:
luaS_freeudata(L, gco2u(o));
luaU_freeudata(L, gco2u(o));
break;
default:
LUAU_ASSERT(0);
@ -634,17 +627,9 @@ static size_t remarkupvals(global_State* g)
static size_t atomic(lua_State* L)
{
global_State* g = L->global;
size_t work = 0;
if (FFlag::LuauSeparateAtomic)
{
LUAU_ASSERT(g->gcstate == GCSatomic);
}
else
{
g->gcstate = GCSatomic;
}
size_t work = 0;
/* remark occasional upvalues of (maybe) dead threads */
work += remarkupvals(g);
/* traverse objects caught by write barrier and by 'remarkupvals' */
@ -668,11 +653,6 @@ static size_t atomic(lua_State* L)
g->sweepgc = &g->rootgc;
g->gcstate = GCSsweepstring;
if (!FFlag::LuauSeparateAtomic)
{
GC_INTERRUPT(GCSatomic);
}
return work;
}
@ -717,24 +697,9 @@ static size_t gcstep(lua_State* L, size_t limit)
}
if (!g->gray) /* no more `gray' objects */
{
if (FFlag::LuauSeparateAtomic)
{
g->gcstate = GCSatomic;
}
else
{
double starttimestamp = lua_clock();
g->gcstats.currcycle.atomicstarttimestamp = starttimestamp;
g->gcstats.currcycle.atomicstarttotalsizebytes = g->totalbytes;
atomic(L); /* finish mark phase */
LUAU_ASSERT(g->gcstate == GCSsweepstring);
g->gcstats.currcycle.atomictime += lua_clock() - starttimestamp;
}
}
break;
}
case GCSatomic:
@ -855,7 +820,7 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal)
void luaC_step(lua_State* L, bool assist)
{
global_State* g = L->global;
ptrdiff_t lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
size_t debt = g->totalbytes - g->GCthreshold;
@ -910,7 +875,7 @@ void luaC_fullgc(lua_State* L)
if (g->gcstate == GCSpause)
startGcCycleStats(g);
if (g->gcstate <= (FFlag::LuauSeparateAtomic ? GCSatomic : GCSpropagateagain))
if (g->gcstate <= GCSatomic)
{
/* reset sweep marks to sweep all elements (returning them to white) */
g->sweepstrgc = 0;
@ -988,7 +953,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
GCObject* o = obj2gco(t);
// in the second propagation stage, table assignment barrier works as a forward barrier
if (FFlag::LuauRescanGrayAgainForwardBarrier && g->gcstate == GCSpropagateagain)
if (g->gcstate == GCSpropagateagain)
{
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
reallymarkobject(g, v);
@ -1044,550 +1009,6 @@ void luaC_linkupval(lua_State* L, UpVal* uv)
}
}
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{
LUAU_ASSERT(!isdead(g, t));
if (keepinvariant(g))
{
/* basic incremental invariant: black can't point to white */
LUAU_ASSERT(!(isblack(f) && iswhite(t)));
}
}
static void validateref(global_State* g, GCObject* f, TValue* v)
{
if (iscollectable(v))
{
LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt);
validateobjref(g, f, gcvalue(v));
}
}
static void validatetable(global_State* g, Table* h)
{
int sizenode = 1 << h->lsizenode;
if (FFlag::LuauArrayBoundary)
LUAU_ASSERT(h->lastfree <= sizenode);
else
LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode);
if (h->metatable)
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
for (int i = 0; i < h->sizearray; ++i)
validateref(g, obj2gco(h), &h->array[i]);
for (int i = 0; i < sizenode; ++i)
{
LuaNode* n = &h->node[i];
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode);
if (!ttisnil(gval(n)))
{
TValue k = {};
k.tt = gkey(n)->tt;
k.value = gkey(n)->value;
validateref(g, obj2gco(h), &k);
validateref(g, obj2gco(h), gval(n));
}
}
}
static void validateclosure(global_State* g, Closure* cl)
{
validateobjref(g, obj2gco(cl), obj2gco(cl->env));
if (cl->isC)
{
for (int i = 0; i < cl->nupvalues; ++i)
validateref(g, obj2gco(cl), &cl->c.upvals[i]);
}
else
{
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
validateobjref(g, obj2gco(cl), obj2gco(cl->l.p));
for (int i = 0; i < cl->nupvalues; ++i)
validateref(g, obj2gco(cl), &cl->l.uprefs[i]);
}
}
static void validatestack(global_State* g, lua_State* l)
{
validateref(g, obj2gco(l), gt(l));
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
{
LUAU_ASSERT(l->stack <= ci->base);
LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top);
LUAU_ASSERT(ci->top <= l->stack_last);
}
// note: stack refs can violate gc invariant so we only check for liveness
for (StkId o = l->stack; o < l->top; ++o)
checkliveness(g, o);
if (l->namecall)
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
for (GCObject* uv = l->openupval; uv; uv = uv->gch.next)
{
LUAU_ASSERT(uv->gch.tt == LUA_TUPVAL);
LUAU_ASSERT(gco2uv(uv)->v != &gco2uv(uv)->u.value);
}
}
static void validateproto(global_State* g, Proto* f)
{
if (f->source)
validateobjref(g, obj2gco(f), obj2gco(f->source));
if (f->debugname)
validateobjref(g, obj2gco(f), obj2gco(f->debugname));
for (int i = 0; i < f->sizek; ++i)
validateref(g, obj2gco(f), &f->k[i]);
for (int i = 0; i < f->sizeupvalues; ++i)
if (f->upvalues[i])
validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i]));
for (int i = 0; i < f->sizep; ++i)
if (f->p[i])
validateobjref(g, obj2gco(f), obj2gco(f->p[i]));
for (int i = 0; i < f->sizelocvars; i++)
if (f->locvars[i].varname)
validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname));
}
static void validateobj(global_State* g, GCObject* o)
{
/* dead objects can only occur during sweep */
if (isdead(g, o))
{
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
return;
}
switch (o->gch.tt)
{
case LUA_TSTRING:
break;
case LUA_TTABLE:
validatetable(g, gco2h(o));
break;
case LUA_TFUNCTION:
validateclosure(g, gco2cl(o));
break;
case LUA_TUSERDATA:
if (gco2u(o)->metatable)
validateobjref(g, o, obj2gco(gco2u(o)->metatable));
break;
case LUA_TTHREAD:
validatestack(g, gco2th(o));
break;
case LUA_TPROTO:
validateproto(g, gco2p(o));
break;
case LUA_TUPVAL:
validateref(g, o, gco2uv(o)->v);
break;
default:
LUAU_ASSERT(!"unexpected object type");
}
}
static void validatelist(global_State* g, GCObject* o)
{
while (o)
{
validateobj(g, o);
o = o->gch.next;
}
}
static void validategraylist(global_State* g, GCObject* o)
{
if (!keepinvariant(g))
return;
while (o)
{
LUAU_ASSERT(isgray(o));
switch (o->gch.tt)
{
case LUA_TTABLE:
o = gco2h(o)->gclist;
break;
case LUA_TFUNCTION:
o = gco2cl(o)->gclist;
break;
case LUA_TTHREAD:
o = gco2th(o)->gclist;
break;
case LUA_TPROTO:
o = gco2p(o)->gclist;
break;
default:
LUAU_ASSERT(!"unknown object in gray list");
return;
}
}
}
void luaC_validate(lua_State* L)
{
global_State* g = L->global;
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
checkliveness(g, &g->registry);
for (int i = 0; i < LUA_T_COUNT; ++i)
if (g->mt[i])
LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i])));
validategraylist(g, g->weak);
validategraylist(g, g->gray);
validategraylist(g, g->grayagain);
for (int i = 0; i < g->strt.size; ++i)
validatelist(g, g->strt.hash[i]);
validatelist(g, g->rootgc);
validatelist(g, g->strbufgc);
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
{
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
LUAU_ASSERT(uv->v != &uv->u.value);
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
}
}
inline bool safejson(char ch)
{
return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"';
}
static void dumpref(FILE* f, GCObject* o)
{
fprintf(f, "\"%p\"", o);
}
static void dumprefs(FILE* f, TValue* data, size_t size)
{
bool first = true;
for (size_t i = 0; i < size; ++i)
{
if (iscollectable(&data[i]))
{
if (!first)
fputc(',', f);
first = false;
dumpref(f, gcvalue(&data[i]));
}
}
}
static void dumpstringdata(FILE* f, const char* data, size_t len)
{
for (size_t i = 0; i < len; ++i)
fputc(safejson(data[i]) ? data[i] : '?', f);
}
static void dumpstring(FILE* f, TString* ts)
{
fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len)));
dumpstringdata(f, ts->data, ts->len);
fprintf(f, "\"}");
}
static void dumptable(FILE* f, Table* h)
{
size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size));
if (h->node != &luaH_dummynode)
{
fprintf(f, ",\"pairs\":[");
bool first = true;
for (int i = 0; i < sizenode(h); ++i)
{
const LuaNode& n = h->node[i];
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
{
if (!first)
fputc(',', f);
first = false;
if (iscollectable(&n.key))
dumpref(f, gcvalue(&n.key));
else
fprintf(f, "null");
fputc(',', f);
if (iscollectable(&n.val))
dumpref(f, gcvalue(&n.val));
else
fprintf(f, "null");
}
}
fprintf(f, "]");
}
if (h->sizearray)
{
fprintf(f, ",\"array\":[");
dumprefs(f, h->array, h->sizearray);
fprintf(f, "]");
}
if (h->metatable)
{
fprintf(f, ",\"metatable\":");
dumpref(f, obj2gco(h->metatable));
}
fprintf(f, "}");
}
static void dumpclosure(FILE* f, Closure* cl)
{
fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat,
cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues)));
fprintf(f, ",\"env\":");
dumpref(f, obj2gco(cl->env));
if (cl->isC)
{
if (cl->nupvalues)
{
fprintf(f, ",\"upvalues\":[");
dumprefs(f, cl->c.upvals, cl->nupvalues);
fprintf(f, "]");
}
}
else
{
fprintf(f, ",\"proto\":");
dumpref(f, obj2gco(cl->l.p));
if (cl->nupvalues)
{
fprintf(f, ",\"upvalues\":[");
dumprefs(f, cl->l.uprefs, cl->nupvalues);
fprintf(f, "]");
}
}
fprintf(f, "}");
}
static void dumpudata(FILE* f, Udata* u)
{
fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag);
if (u->metatable)
{
fprintf(f, ",\"metatable\":");
dumpref(f, obj2gco(u->metatable));
}
fprintf(f, "}");
}
static void dumpthread(FILE* f, lua_State* th)
{
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
if (iscollectable(&th->l_gt))
{
fprintf(f, ",\"env\":");
dumpref(f, gcvalue(&th->l_gt));
}
Closure* tcl = 0;
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
{
if (ttisfunction(ci->func))
{
tcl = clvalue(ci->func);
break;
}
}
if (tcl && !tcl->isC && tcl->l.p->source)
{
Proto* p = tcl->l.p;
fprintf(f, ",\"source\":\"");
dumpstringdata(f, p->source->data, p->source->len);
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
}
if (th->top > th->stack)
{
fprintf(f, ",\"stack\":[");
dumprefs(f, th->stack, th->top - th->stack);
fprintf(f, "]");
}
fprintf(f, "}");
}
static void dumpproto(FILE* f, Proto* p)
{
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size));
if (p->source)
{
fprintf(f, ",\"source\":\"");
dumpstringdata(f, p->source->data, p->source->len);
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
}
if (p->sizek)
{
fprintf(f, ",\"constants\":[");
dumprefs(f, p->k, p->sizek);
fprintf(f, "]");
}
if (p->sizep)
{
fprintf(f, ",\"protos\":[");
for (int i = 0; i < p->sizep; ++i)
{
if (i != 0)
fputc(',', f);
dumpref(f, obj2gco(p->p[i]));
}
fprintf(f, "]");
}
fprintf(f, "}");
}
static void dumpupval(FILE* f, UpVal* uv)
{
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal)));
if (iscollectable(uv->v))
{
fprintf(f, ",\"object\":");
dumpref(f, gcvalue(uv->v));
}
fprintf(f, "}");
}
static void dumpobj(FILE* f, GCObject* o)
{
switch (o->gch.tt)
{
case LUA_TSTRING:
return dumpstring(f, gco2ts(o));
case LUA_TTABLE:
return dumptable(f, gco2h(o));
case LUA_TFUNCTION:
return dumpclosure(f, gco2cl(o));
case LUA_TUSERDATA:
return dumpudata(f, gco2u(o));
case LUA_TTHREAD:
return dumpthread(f, gco2th(o));
case LUA_TPROTO:
return dumpproto(f, gco2p(o));
case LUA_TUPVAL:
return dumpupval(f, gco2uv(o));
default:
LUAU_ASSERT(0);
}
}
static void dumplist(FILE* f, GCObject* o)
{
while (o)
{
dumpref(f, o);
fputc(':', f);
dumpobj(f, o);
fputc(',', f);
fputc('\n', f);
// thread has additional list containing collectable objects that are not present in rootgc
if (o->gch.tt == LUA_TTHREAD)
dumplist(f, gco2th(o)->openupval);
o = o->gch.next;
}
}
void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat))
{
global_State* g = L->global;
FILE* f = static_cast<FILE*>(file);
fprintf(f, "{\"objects\":{\n");
dumplist(f, g->rootgc);
dumplist(f, g->strbufgc);
for (int i = 0; i < g->strt.size; ++i)
dumplist(f, g->strt.hash[i]);
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
fprintf(f, "},\"roots\":{\n");
fprintf(f, "\"mainthread\":");
dumpref(f, obj2gco(g->mainthread));
fprintf(f, ",\"registry\":");
dumpref(f, gcvalue(&g->registry));
fprintf(f, "},\"stats\":{\n");
fprintf(f, "\"size\":%d,\n", int(g->totalbytes));
fprintf(f, "\"categories\":{\n");
for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++)
{
if (size_t bytes = g->memcatbytes[i])
{
if (categoryName)
fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes));
else
fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes));
}
}
fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing ,
fprintf(f, "}\n");
fprintf(f, "}}\n");
}
// measure the allocation rate in bytes/sec
// returns -1 if allocation rate cannot be measured
int64_t luaC_allocationrate(lua_State* L)
@ -1595,7 +1016,7 @@ int64_t luaC_allocationrate(lua_State* L)
global_State* g = L->global;
const double durationthreshold = 1e-3; // avoid measuring intervals smaller than 1ms
if (g->gcstate <= (FFlag::LuauSeparateAtomic ? GCSatomic : GCSpropagateagain))
if (g->gcstate <= GCSatomic)
{
double duration = lua_clock() - g->gcstats.lastcycle.endtimestamp;

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

View file

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

View file

@ -78,15 +78,7 @@ typedef struct lua_TValue
#define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th)
#define upvalue(o) check_exp(ttisupval(o), &(o)->value.gc->uv)
// beware bit magic: a value is false if it's nil or boolean false
// baseline implementation: (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0))
// we'd like a branchless version of this which helps with performance, and a very fast version
// so our strategy is to always read the boolean value (not using bvalue(o) because that asserts when type isn't boolean)
// we then combine it with type to produce 0/1 as follows:
// - when type is nil (0), & makes the result 0
// - when type is boolean (1), we effectively only look at the bottom bit, so result is 0 iff boolean value is 0
// - when type is different, it must have some of the top bits set - we keep all top bits of boolean value so the result is non-0
#define l_isfalse(o) (!(((o)->value.b | ~1) & ttype(o)))
#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0))
/*
** for internal debug only

View file

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

View file

@ -206,32 +206,3 @@ void luaS_free(lua_State* L, TString* ts)
L->global->strt.nuse--;
luaM_free(L, ts, sizestring(ts->len), ts->memcat);
}
Udata* luaS_newudata(lua_State* L, size_t s, int tag)
{
if (s > INT_MAX - sizeof(Udata))
luaM_toobig(L);
Udata* u = luaM_new(L, Udata, sizeudata(s), L->activememcat);
luaC_link(L, u, LUA_TUSERDATA);
u->len = int(s);
u->metatable = NULL;
LUAU_ASSERT(tag >= 0 && tag <= 255);
u->tag = uint8_t(tag);
return u;
}
void luaS_freeudata(lua_State* L, Udata* u)
{
LUAU_ASSERT(u->tag < LUA_UTAG_LIMIT || u->tag == UTAG_IDTOR);
void (*dtor)(void*) = nullptr;
if (u->tag == UTAG_IDTOR)
memcpy(&dtor, &u->data + u->len - sizeof(dtor), sizeof(dtor));
else if (u->tag)
dtor = L->global->udatagc[u->tag];
if (dtor)
dtor(u->data);
luaM_free(L, u, sizeudata(u->len), u->memcat);
}

View file

@ -8,11 +8,7 @@
/* string size limit */
#define MAXSSIZE (1 << 30)
/* special tag value is used for user data with inline dtors */
#define UTAG_IDTOR LUA_UTAG_LIMIT
#define sizestring(len) (offsetof(TString, data) + len + 1)
#define sizeudata(len) (offsetof(Udata, data) + len)
#define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s)))
#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, (sizeof(s) / sizeof(char)) - 1))
@ -26,8 +22,5 @@ LUAI_FUNC void luaS_resize(lua_State* L, int newsize);
LUAI_FUNC TString* luaS_newlstr(lua_State* L, const char* str, size_t l);
LUAI_FUNC void luaS_free(lua_State* L, TString* ts);
LUAI_FUNC Udata* luaS_newudata(lua_State* L, size_t s, int tag);
LUAI_FUNC void luaS_freeudata(lua_State* L, Udata* u);
LUAI_FUNC TString* luaS_bufstart(lua_State* L, size_t size);
LUAI_FUNC TString* luaS_buffinish(lua_State* L, TString* ts);

View file

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

View file

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

37
VM/src/ludata.cpp Normal file
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},
};
LUALIB_API int luaopen_utf8(lua_State* L)
int luaopen_utf8(lua_State* L)
{
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_UV(i) (LUAU_ASSERT(unsigned(i) < unsigned(cl->nupvalues)), &cl->l.uprefs[i])
#define VM_PATCH_C(pc, slot) ((uint8_t*)(pc))[3] = uint8_t(slot)
#define VM_PATCH_C(pc, slot) *const_cast<Instruction*>(pc) = ((uint8_t(slot) << 24) | (0x00ffffffu & *(pc)))
#define VM_PATCH_E(pc, slot) *const_cast<Instruction*>(pc) = ((uint32_t(slot) << 8) | (0x000000ffu & *(pc)))
// NOTE: If debugging the Luau code, disable this macro to prevent timeouts from
// occurring when tracing code in Visual Studio / XCode
@ -120,7 +121,7 @@
*/
#if VM_USE_CGOTO
#define VM_CASE(op) CASE_##op:
#define VM_NEXT() goto*(SingleStep ? &&dispatch : kDispatchTable[*(uint8_t*)pc])
#define VM_NEXT() goto*(SingleStep ? &&dispatch : kDispatchTable[LUAU_INSN_OP(*pc)])
#define VM_CONTINUE(op) goto* kDispatchTable[uint8_t(op)]
#else
#define VM_CASE(op) case op:
@ -325,7 +326,7 @@ static void luau_execute(lua_State* L)
// ... and singlestep logic :)
if (SingleStep)
{
if (L->global->cb.debugstep && !luau_skipstep(*(uint8_t*)pc))
if (L->global->cb.debugstep && !luau_skipstep(LUAU_INSN_OP(*pc)))
{
VM_PROTECT(luau_callhook(L, L->global->cb.debugstep, NULL));
@ -335,13 +336,12 @@ static void luau_execute(lua_State* L)
}
#if VM_USE_CGOTO
VM_CONTINUE(*(uint8_t*)pc);
VM_CONTINUE(LUAU_INSN_OP(*pc));
#endif
}
#if !VM_USE_CGOTO
// Note: this assumes that LUAU_INSN_OP() decodes the first byte (aka least significant byte in the little endian encoding)
size_t dispatchOp = *(uint8_t*)pc;
size_t dispatchOp = LUAU_INSN_OP(*pc);
dispatchContinue:
switch (dispatchOp)
@ -2577,7 +2577,7 @@ static void luau_execute(lua_State* L)
// update hits with saturated add and patch the instruction in place
hits = (hits < (1 << 23) - 1) ? hits + 1 : hits;
((uint32_t*)pc)[-1] = LOP_COVERAGE | (uint32_t(hits) << 8);
VM_PATCH_E(pc - 1, hits);
VM_NEXT();
}

View file

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

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)
{
ptrdiff_t result = savestack(L, res);
// RBOLOX: using stack room beyond top is technically safe here, but for very complicated reasons:
// using stack room beyond top is technically safe here, but for very complicated reasons:
// * The stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers
@ -74,7 +74,7 @@ static void callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1
static void callTM(lua_State* L, const TValue* f, const TValue* p1, const TValue* p2, const TValue* p3)
{
// RBOLOX: using stack room beyond top is technically safe here, but for very complicated reasons:
// using stack room beyond top is technically safe here, but for very complicated reasons:
// * The stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers

View file

@ -451,15 +451,16 @@ function raytraceScene()
end
function arrayToCanvasCommands(pixels)
local s = '<!DOCTYPE html><html><head><title>Test</title></head><body><canvas id="renderCanvas" width="' .. size .. 'px" height="' .. size .. 'px"></canvas><scr' .. 'ipt>\nvar pixels = [';
local s = {};
table.insert(s, '<!DOCTYPE html><html><head><title>Test</title></head><body><canvas id="renderCanvas" width="' .. size .. 'px" height="' .. size .. 'px"></canvas><scr' .. 'ipt>\nvar pixels = [');
for y = 0,size-1 do
s = s .. "[";
table.insert(s, "[");
for x = 0,size-1 do
s = s .. "[" .. math.floor(pixels[y + 1][x + 1][1] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][2] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][3] * 255) .. "],";
table.insert(s, "[" .. math.floor(pixels[y + 1][x + 1][1] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][2] * 255) .. "," .. math.floor(pixels[y + 1][x + 1][3] * 255) .. "],");
end
s = s .. "],";
table.insert(s, "],");
end
s = s .. '];\n var canvas = document.getElementById("renderCanvas").getContext("2d");\n\
table.insert(s, '];\n var canvas = document.getElementById("renderCanvas").getContext("2d");\n\
\n\
\n\
var size = ' .. size .. ';\n\
@ -479,9 +480,9 @@ for (var y = 0; y < size; y++) {\n\
canvas.setFillColor(l[0], l[1], l[2], 1);\n\
canvas.fillRect(x, y, 1, 1);\n\
}\n\
}</scr' .. 'ipt></body></html>';
}</script></body></html>');
return s;
return table.concat(s);
end
testOutput = arrayToCanvasCommands(raytraceScene());

View file

@ -29,3 +29,5 @@ pages:
url: /profile
- title: 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`.
```
function coroutine.close(co: thread): (boolean, any?)
```
Closes the coroutine which puts coroutine in the dead state. The coroutine must be dead or suspended - in particular it can't be currently running. If the coroutine that's being closed was in an error state, returns `false` along with an error object; otherwise returns `true`. After closing, the coroutine can't be resumed and the coroutine stack becomes empty.
## bit32 library
All functions in the `bit32` library treat input numbers as 32-bit unsigned integers in `[0..4294967295]` range. The bit positions start at 0 where 0 corresponds to the least significant bit.
@ -700,6 +706,18 @@ function bit32.rshift(n: number, i: number): number
Shifts `n` to the right by `i` bits (if `i` is negative, a left shift is performed instead).
```
function bit32.countlz(n: number): number
```
Returns the number of consecutive zero bits in the 32-bit representation of `n` starting from the left-most (most significant) bit. Returns 32 if `n` is zero.
```
function bit32.countrz(n: number): number
```
Returns the number of consecutive zero bits in the 32-bit representation of `n` starting from the right-most (least significant) bit. Returns 32 if `n` is zero.
## utf8 library
Strings in Luau can contain arbitrary bytes; however, in many applications strings representing text contain UTF8 encoded data by convention, that can be inspected and manipulated using `utf8` library.

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.
## Closure caching
With optimized upvalue storage, creating new closures (function objects) is more efficient but still requires allocating a new object every time. This can be problematic for cases when functions are passed to algorithms like `table.sort` or functions like `pcall`, as it results in excessive allocation traffic which then leads to more work for garbage collector.
To make closure creation cheaper, Luau compiler implements closure caching - when multiple executions of the same function expression are guaranteed to result in the function object that is semantically identical, the compiler may cache the closure and always return the same object. This changes the function identity which may affect code that uses function objects as table keys, but preserves the calling semantics - compiler will only do this if calling the original (cached) function behaves the same way as a newly created function would. The heuristics used for this optimization are subject to change; currently, the compiler will cache closures that have no upvalues, or all upvalues are immutable (see previous section) and are declared at the module scope, as the module scope is (almost always) evaluated only once.
## Fast memory allocator
Similarly to LuaJIT, but unlike vanilla Lua, Luau implements a custom allocator that is highly specialized and tuned to the common allocation workloads we see. The allocator design is inspired by classic pool allocators as well as the excellent `mimalloc`, but through careful domain-specific tuning it beats all general purpose allocators we've tested, including `rpmalloc`, `mimalloc`, `jemalloc`, `ptmalloc` and `tcmalloc`.

View file

@ -287,6 +287,66 @@ In type annotations, this is written as `...T`:
type F = (...number) -> ...string
```
## Type packs
Multiple function return values as well as the function variadic parameter use a type pack to represent a list of types.
When a type alias is defined, generic type pack parameters can be used after the type parameters:
```lua
type Signal<T, U...> = { f: (T, U...) -> (), data: T }
```
> Keep in mind that `...T` is a variadic type pack (many elements of the same type `T`), while `U...` is a generic type pack that can contain zero or more types and they don't have to be the same.
It is also possible for a generic function to reference a generic type pack from the generics list:
```lua
local function call<T, U...>(s: Signal<T, U...>, ...: U...)
s.f(s.data, ...)
end
```
Generic types with type packs can be instantiated by providing a type pack:
```lua
local signal: Signal<string, (number, number, boolean)> = --
call(signal, 1, 2, false)
```
There are also other ways to instantiate types with generic type pack parameters:
```lua
type A<T, U...> = (T) -> U...
type B = A<number, ...string> -- with a variadic type pack
type C<S...> = A<number, S...> -- with a generic type pack
type D = A<number, ()> -- with an empty type pack
```
Trailing type pack argument can also be provided without parentheses by specifying variadic type arguments:
```lua
type List<Head, Rest...> = (Head, Rest...) -> ()
type B = List<number> -- Rest... is ()
type C = List<number, string, boolean> -- Rest is (string, boolean)
type Returns<T...> = () -> T...
-- When there are no type parameters, the list can be left empty
type D = Returns<> -- T... is ()
```
Type pack parameters are not limited to a single one, as many as required can be specified:
```lua
type Callback<Args..., Rets...> = { f: (Args...) -> Rets... }
type A = Callback<(number, string), ...number>
```
## Typing idiomatic OOP
One common pattern we see throughout Roblox is this OOP idiom. A downside with this pattern is that it does not automatically create a type binding for an instance of that class, so one has to write `type Account = typeof(Account.new("", 0))`.

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

View file

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

View file

@ -4,6 +4,8 @@
Add bit32.countlz (count left zeroes) and bit32.countrz (count right zeroes) to accelerate bit scanning
**Status**: Implemented
## Motivation
All CPUs have instructions to determine the position of first/last set bit in an integer. These instructions have a variety of uses, the popular ones being:

View file

@ -49,8 +49,11 @@ type A<T, U = V, V = T> = ... -- not allowed
Default type parameter values are also allowed for type packs:
```lua
type A<T, U... = ...string> -- ok, variadic type pack
type C<T, U... = string> -- ok, type pack with one element
type D<T..., U... = T...> -- ok
type B<T, U... = ()> -- ok, type pack with no elements
type C<T, U... = (string)> -- ok, type pack with one element
type D<T, U... = (string, number)> -- ok, type pack with two elements
type E<T, U... = (string, ...number)> -- ok, variadic type pack with a different first element
type F<T..., U... = T...> -- ok, same type pack as T...
```
---
@ -68,7 +71,7 @@ If all type parameters have a default type value, it is now possible to referenc
type All<T = string, U = number> = ...
local a: All -- ok
local b: All<> -- not allowed
local b: All<> -- ok as well
```
If type is exported from a module, default type parameter values will still be available when module is imported.

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

View file

@ -10,9 +10,6 @@
#include <sstream>
#include <string_view>
LUAU_FASTFLAG(LuauPreloadClosures)
LUAU_FASTFLAG(LuauGenericSpecialGlobals)
using namespace Luau;
static std::string compileFunction(const char* source, uint32_t id)
@ -74,20 +71,10 @@ TEST_CASE("BasicFunction")
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::compileOrThrow(bcb, "local function foo(a, b) return b end");
if (FFlag::LuauPreloadClosures)
{
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
DUPCLOSURE R0 K0
RETURN R0 0
)");
}
else
{
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
NEWCLOSURE R0 P0
RETURN R0 0
)");
}
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
RETURN R1 1
@ -1057,6 +1044,18 @@ RETURN R0 1
CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"(
LOADN R0 20
RETURN R0 1
)");
// codegen for a true constant condition with non-constant expressions
CHECK_EQ("\n" + compileFunction0("return if true then {} else error()"), R"(
NEWTABLE R0 0 0
RETURN R0 1
)");
// codegen for a false constant condition with non-constant expressions
CHECK_EQ("\n" + compileFunction0("return if false then error() else {}"), R"(
NEWTABLE R0 0 0
RETURN R0 1
)");
// codegen for a false (in this case 'nil') constant condition
@ -2360,6 +2359,58 @@ Foo:Bar(
)");
}
TEST_CASE("DebugLineInfoCallChain")
{
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
Luau::compileOrThrow(bcb, R"(
local Foo = ...
Foo
:Bar(1)
:Baz(2)
.Qux(3)
)");
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
2: GETVARARGS R0 1
5: LOADN R4 1
5: NAMECALL R2 R0 K0
5: CALL R2 2 1
6: LOADN R4 2
6: NAMECALL R2 R2 K1
6: CALL R2 2 1
7: GETTABLEKS R1 R2 K2
7: LOADN R2 3
7: CALL R1 1 0
8: RETURN R0 0
)");
}
TEST_CASE("DebugLineInfoFastCall")
{
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
Luau::compileOrThrow(bcb, R"(
local Foo, Bar = ...
return
math.max(
Foo,
Bar)
)");
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
2: GETVARARGS R0 2
5: FASTCALL2 18 R0 R1 +5
5: MOVE R3 R0
5: MOVE R4 R1
5: GETIMPORT R2 2
5: CALL R2 2 -1
5: RETURN R2 -1
)");
}
TEST_CASE("DebugSource")
{
const char* source = R"(
@ -2795,8 +2846,6 @@ CAPTURE UPVAL U1
RETURN R0 1
)");
if (FFlag::LuauPreloadClosures)
{
// recursive capture
CHECK_EQ("\n" + compileFunction("local function foo() return foo() end", 1), R"(
DUPCLOSURE R0 K0
@ -2827,16 +2876,6 @@ CAPTURE UPVAL U0
RETURN R0 1
)");
}
else
{
// recursive capture
CHECK_EQ("\n" + compileFunction("local function foo() return foo() end", 1), R"(
NEWCLOSURE R0 P0
CAPTURE VAL R0
RETURN R0 0
)");
}
}
TEST_CASE("OutOfLocals")
{
@ -3440,8 +3479,6 @@ local t = {
TEST_CASE("ConstantClosure")
{
ScopedFastFlag sff("LuauPreloadClosures", true);
// closures without upvalues are created when bytecode is loaded
CHECK_EQ("\n" + compileFunction(R"(
return function() end
@ -3506,8 +3543,6 @@ RETURN R0 1
TEST_CASE("SharedClosure")
{
ScopedFastFlag sff1("LuauPreloadClosures", true);
// closures can be shared even if functions refer to upvalues, as long as upvalues are top-level
CHECK_EQ("\n" + compileFunction(R"(
local val = ...
@ -3742,4 +3777,108 @@ RETURN R0 0
)");
}
TEST_CASE("ConstantsNoFolding")
{
const char* source = "return nil, true, 42, 'hello'";
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
options.optimizationLevel = 0;
Luau::compileOrThrow(bcb, source, options);
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
LOADNIL R0
LOADB R1 1
LOADK R2 K0
LOADK R3 K1
RETURN R0 4
)");
}
TEST_CASE("VectorFastCall")
{
const char* source = "return Vector3.new(1, 2, 3)";
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
options.vectorLib = "Vector3";
options.vectorCtor = "new";
Luau::compileOrThrow(bcb, source, options);
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
LOADN R1 1
LOADN R2 2
LOADN R3 3
FASTCALL 54 +2
GETIMPORT R0 2
CALL R0 3 -1
RETURN R0 -1
)");
}
TEST_CASE("TypeAssertion")
{
// validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated
CHECK_EQ("\n" + compileFunction0(R"(
print(foo() :: typeof(error("compile time")))
)"),
R"(
GETIMPORT R0 1
GETIMPORT R1 3
CALL R1 0 1
CALL R0 1 0
RETURN R0 0
)");
// note that above, foo() is treated as single-arg function; removing type assertion changes the bytecode
CHECK_EQ("\n" + compileFunction0(R"(
print(foo())
)"),
R"(
GETIMPORT R0 1
GETIMPORT R1 3
CALL R1 0 -1
CALL R0 -1 0
RETURN R0 0
)");
}
TEST_CASE("Arithmetics")
{
// basic arithmetics codegen with non-constants
CHECK_EQ("\n" + compileFunction0(R"(
local a, b = ...
return a + b, a - b, a / b, a * b, a % b, a ^ b
)"),
R"(
GETVARARGS R0 2
ADD R2 R0 R1
SUB R3 R0 R1
DIV R4 R0 R1
MUL R5 R0 R1
MOD R6 R0 R1
POW R7 R0 R1
RETURN R2 6
)");
// basic arithmetics codegen with constants on the right side
// note that we don't simplify these expressions as we don't know the type of a
CHECK_EQ("\n" + compileFunction0(R"(
local a = ...
return a + 1, a - 1, a / 1, a * 1, a % 1, a ^ 1
)"),
R"(
GETVARARGS R0 1
ADDK R1 R0 K0
SUBK R2 R0 K0
DIVK R3 R0 K0
MULK R4 R0 K0
MODK R5 R0 K0
POWK R6 R0 K0
RETURN R1 6
)");
}
TEST_SUITE_END();

View file

@ -78,24 +78,18 @@ static int lua_vector(lua_State* L)
static int lua_vector_dot(lua_State* L)
{
const float* a = lua_tovector(L, 1);
const float* b = lua_tovector(L, 2);
const float* a = luaL_checkvector(L, 1);
const float* b = luaL_checkvector(L, 2);
if (a && b)
{
lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
return 1;
}
throw std::runtime_error("invalid arguments to vector:Dot");
}
static int lua_vector_index(lua_State* L)
{
const float* v = luaL_checkvector(L, 1);
const char* name = luaL_checkstring(L, 2);
if (const float* v = lua_tovector(L, 1))
{
if (strcmp(name, "Magnitude") == 0)
{
lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
@ -107,9 +101,8 @@ static int lua_vector_index(lua_State* L)
lua_pushcfunction(L, lua_vector_dot, "Dot");
return 1;
}
}
throw std::runtime_error(Luau::format("%s is not a valid member of vector", name));
luaL_error(L, "%s is not a valid member of vector", name);
}
static int lua_vector_namecall(lua_State* L)
@ -120,7 +113,7 @@ static int lua_vector_namecall(lua_State* L)
return lua_vector_dot(L);
}
throw std::runtime_error(Luau::format("%s is not a valid method of vector", luaL_checkstring(L, 1)));
luaL_error(L, "%s is not a valid method of vector", luaL_checkstring(L, 1));
}
int lua_silence(lua_State* L)
@ -130,8 +123,8 @@ int lua_silence(lua_State* L)
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
static StateRef runConformance(
const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr, lua_State* initialLuaState = nullptr)
static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr,
lua_State* initialLuaState = nullptr, lua_CompileOptions* copts = nullptr)
{
std::string path = __FILE__;
path.erase(path.find_last_of("\\/"));
@ -187,13 +180,8 @@ static StateRef runConformance(
std::string chunkname = "=" + std::string(name);
lua_CompileOptions copts = {};
copts.optimizationLevel = 1; // default
copts.debugLevel = 2; // for debugger tests
copts.vectorCtor = "vector"; // for vector tests
size_t bytecodeSize = 0;
char* bytecode = luau_compile(source.data(), source.size(), &copts, &bytecodeSize);
char* bytecode = luau_compile(source.data(), source.size(), copts, &bytecodeSize);
int result = luau_load(L, chunkname.c_str(), bytecode, bytecodeSize, 0);
free(bytecode);
@ -380,7 +368,14 @@ TEST_CASE("Vector")
{
ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true};
runConformance("vector.lua", [](lua_State* L) {
lua_CompileOptions copts = {};
copts.optimizationLevel = 1;
copts.debugLevel = 1;
copts.vectorCtor = "vector";
runConformance(
"vector.lua",
[](lua_State* L) {
lua_pushcfunction(L, lua_vector, "vector");
lua_setglobal(L, "vector");
@ -402,7 +397,8 @@ TEST_CASE("Vector")
lua_setreadonly(L, -1, true);
lua_setmetatable(L, -2);
lua_pop(L, 1);
});
},
nullptr, nullptr, &copts);
}
static void populateRTTI(lua_State* L, Luau::TypeId type)
@ -506,6 +502,10 @@ TEST_CASE("Debugger")
breakhits = 0;
interruptedthread = nullptr;
lua_CompileOptions copts = {};
copts.optimizationLevel = 1;
copts.debugLevel = 2;
runConformance(
"debugger.lua",
[](lua_State* L) {
@ -515,6 +515,9 @@ TEST_CASE("Debugger")
cb->debugbreak = [](lua_State* L, lua_Debug* ar) {
breakhits++;
// make sure we can trace the stack for every breakpoint we hit
lua_debugtrace(L);
// for every breakpoint, we break on the first invocation and continue on second
// this allows us to easily step off breakpoints
// (real implementaiton may require singlestepping)
@ -618,7 +621,8 @@ TEST_CASE("Debugger")
lua_resume(interruptedthread, nullptr, 0);
interruptedthread = nullptr;
}
});
},
nullptr, &copts);
CHECK(breakhits == 10); // 2 hits per breakpoint
}
@ -710,6 +714,8 @@ TEST_CASE("ApiFunctionCalls")
StateRef globalState = runConformance("apicalls.lua");
lua_State* L = globalState.get();
// lua_call
{
lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pushnumber(L, 40);
lua_pushnumber(L, 2);
@ -717,7 +723,10 @@ TEST_CASE("ApiFunctionCalls")
CHECK(lua_isnumber(L, -1));
CHECK(lua_tonumber(L, -1) == 42);
lua_pop(L, 1);
}
// lua_pcall
{
lua_getfield(L, LUA_GLOBALSINDEX, "add");
lua_pushnumber(L, 40);
lua_pushnumber(L, 2);
@ -727,6 +736,32 @@ TEST_CASE("ApiFunctionCalls")
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)
{
if (suffix.length() > str.length())
@ -738,8 +773,6 @@ static bool endsWith(const std::string& str, const std::string& suffix)
#if !LUA_USE_LONGJMP
TEST_CASE("ExceptionObject")
{
ScopedFastFlag sff("LuauExceptionMessageFix", true);
struct ExceptionResult
{
bool exceptionGenerated;
@ -838,4 +871,46 @@ TEST_CASE("TagMethodError")
});
}
TEST_CASE("Coverage")
{
lua_CompileOptions copts = {};
copts.optimizationLevel = 1;
copts.debugLevel = 1;
copts.coverageLevel = 2;
runConformance(
"coverage.lua",
[](lua_State* L) {
lua_pushcfunction(
L,
[](lua_State* L) -> int {
luaL_argexpected(L, lua_isLfunction(L, 1), 1, "function");
lua_newtable(L);
lua_getcoverage(L, 1, L, [](void* context, const char* function, int linedefined, int depth, const int* hits, size_t size) {
lua_State* L = static_cast<lua_State*>(context);
lua_newtable(L);
lua_pushstring(L, function);
lua_setfield(L, -2, "name");
for (size_t i = 0; i < size; ++i)
if (hits[i] != -1)
{
lua_pushinteger(L, hits[i]);
lua_rawseti(L, -2, int(i));
}
lua_rawseti(L, -2, lua_objlen(L, -2) + 1);
});
return 1;
},
"getcoverage");
lua_setglobal(L, "getcoverage");
},
nullptr, nullptr, &copts);
}
TEST_SUITE_END();

View file

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

View file

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

View file

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

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);
}
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsExpr")
{
LintResult result = lint(R"(
local correct, opaque = ...
if correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", false)}) then
end
)");
REQUIRE_EQ(result.warnings.size(), 1);
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 4");
CHECK_EQ(result.warnings[0].location.begin.line + 1, 5);
}
TEST_CASE_FIXTURE(Fixture, "DuplicateLocal")
{
LintResult result = lint(R"(

View file

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

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

366
tests/ToDot.test.cpp Normal file
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")
{
ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true);
ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true);
std::string code = R"(
type Packed<T...> = (T...)->(T...)
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")
{
ScopedFastFlag sff1{"LuauSubstitutionDontReplaceIgnoredTypes", true};
CheckResult result = check(R"(
type Array<T> = { [number]: T }
type Tuple<T, V> = Array<T | V>

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