Merge branch 'upstream' into merge

This commit is contained in:
Arseny Kapoulkine 2022-03-11 08:36:10 -08:00
commit bf74a347c3
26 changed files with 1137 additions and 2168 deletions

View file

@ -14,61 +14,6 @@ namespace Luau
using TypeOrPackId = const void*;
// Log of where what TypeIds we are rebinding and what they used to be
// Remove with LuauUseCommitTxnLog
struct DEPRECATED_TxnLog
{
DEPRECATED_TxnLog()
: originalSeenSize(0)
, ownedSeen()
, sharedSeen(&ownedSeen)
{
}
explicit DEPRECATED_TxnLog(std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
: originalSeenSize(sharedSeen->size())
, ownedSeen()
, sharedSeen(sharedSeen)
{
}
DEPRECATED_TxnLog(const DEPRECATED_TxnLog&) = delete;
DEPRECATED_TxnLog& operator=(const DEPRECATED_TxnLog&) = delete;
DEPRECATED_TxnLog(DEPRECATED_TxnLog&&) = default;
DEPRECATED_TxnLog& operator=(DEPRECATED_TxnLog&&) = default;
void operator()(TypeId a);
void operator()(TypePackId a);
void operator()(TableTypeVar* a);
void rollback();
void concat(DEPRECATED_TxnLog rhs);
bool haveSeen(TypeId lhs, TypeId rhs);
void pushSeen(TypeId lhs, TypeId rhs);
void popSeen(TypeId lhs, TypeId rhs);
bool haveSeen(TypePackId lhs, TypePackId rhs);
void pushSeen(TypePackId lhs, TypePackId rhs);
void popSeen(TypePackId lhs, TypePackId rhs);
private:
std::vector<std::pair<TypeId, TypeVar>> typeVarChanges;
std::vector<std::pair<TypePackId, TypePackVar>> typePackChanges;
std::vector<std::pair<TableTypeVar*, std::optional<TypeId>>> tableChanges;
size_t originalSeenSize;
bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs);
void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs);
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
public:
std::vector<std::pair<TypeOrPackId, TypeOrPackId>> ownedSeen; // used to avoid infinite recursion when types are cyclic
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen; // shared with all the descendent logs
};
// Pending state for a TypeVar. Generated by a TxnLog and committed via
// TxnLog::commit.
struct PendingType

View file

@ -105,7 +105,6 @@ private:
const TypePack* tp = nullptr;
size_t currentIndex = 0;
// Only used if LuauUseCommittingTxnLog is true.
const TxnLog* log;
};

View file

@ -45,7 +45,6 @@ struct Unifier
TypeArena* const types;
Mode mode;
DEPRECATED_TxnLog DEPRECATED_log;
TxnLog log;
ErrorVec errors;
Location location;

View file

@ -13,9 +13,6 @@
#include <unordered_set>
#include <utility>
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false);
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = {
@ -240,28 +237,9 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
UnifierSharedState unifierState(&iceReporter);
Unifier unifier(typeArena, Mode::Strict, Location(), Variance::Covariant, unifierState);
if (FFlag::LuauAutocompleteAvoidMutation && !FFlag::LuauUseCommittingTxnLog)
{
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
superTy = clone(superTy, *typeArena, seenTypes, seenTypePacks, cloneState);
subTy = clone(subTy, *typeArena, seenTypes, seenTypePacks, cloneState);
auto errors = unifier.canUnify(subTy, superTy);
return errors.empty();
}
else
{
unifier.tryUnify(subTy, superTy);
bool ok = unifier.errors.empty();
if (!FFlag::LuauUseCommittingTxnLog)
unifier.DEPRECATED_log.rollback();
return ok;
}
unifier.tryUnify(subTy, superTy);
bool ok = unifier.errors.empty();
return ok;
};
auto typeAtPosition = findExpectedTypeAt(module, node, position);
@ -403,28 +381,14 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
auto indexIt = mtable->props.find("__index");
if (indexIt != mtable->props.end())
{
if (FFlag::LuauMissingFollowACMetatables)
TypeId followed = follow(indexIt->second.type);
if (get<TableTypeVar>(followed) || get<MetatableTypeVar>(followed))
autocompleteProps(module, typeArena, followed, indexType, nodes, result, seen);
else if (auto indexFunction = get<FunctionTypeVar>(followed))
{
TypeId followed = follow(indexIt->second.type);
if (get<TableTypeVar>(followed) || get<MetatableTypeVar>(followed))
autocompleteProps(module, typeArena, followed, indexType, nodes, result, seen);
else if (auto indexFunction = get<FunctionTypeVar>(followed))
{
std::optional<TypeId> indexFunctionResult = first(indexFunction->retType);
if (indexFunctionResult)
autocompleteProps(module, typeArena, *indexFunctionResult, indexType, nodes, result, seen);
}
}
else
{
if (get<TableTypeVar>(indexIt->second.type) || get<MetatableTypeVar>(indexIt->second.type))
autocompleteProps(module, typeArena, indexIt->second.type, indexType, nodes, result, seen);
else if (auto indexFunction = get<FunctionTypeVar>(indexIt->second.type))
{
std::optional<TypeId> indexFunctionResult = first(indexFunction->retType);
if (indexFunctionResult)
autocompleteProps(module, typeArena, *indexFunctionResult, indexType, nodes, result, seen);
}
std::optional<TypeId> indexFunctionResult = first(indexFunction->retType);
if (indexFunctionResult)
autocompleteProps(module, typeArena, *indexFunctionResult, indexType, nodes, result, seen);
}
}
}

View file

@ -10,6 +10,7 @@
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
LUAU_FASTFLAGVARIABLE(LuauTableCloneType, false)
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
/** FIXME: Many of these type definitions are not quite completely accurate.
*
@ -376,11 +377,19 @@ static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
TypeId mtTy = arena.addType(mtv);
AstExpr* targetExpr = expr.args.data[0];
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1)
{
const Name targetName(targetLocal->local->name.value);
scope->bindings[targetLocal->local] = Binding{mtTy, expr.location};
return ExprResult<TypePackId>{};
}
if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self)
{
AstExpr* targetExpr = expr.args.data[0];
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
{
const Name targetName(targetLocal->local->name.value);
scope->bindings[targetLocal->local] = Binding{mtTy, expr.location};
}
}
return ExprResult<TypePackId>{arena.addTypePack({mtTy})};

View file

@ -7,110 +7,9 @@
#include <algorithm>
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauUseCommittingTxnLog, false)
namespace Luau
{
void DEPRECATED_TxnLog::operator()(TypeId a)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
typeVarChanges.emplace_back(a, *a);
}
void DEPRECATED_TxnLog::operator()(TypePackId a)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
typePackChanges.emplace_back(a, *a);
}
void DEPRECATED_TxnLog::operator()(TableTypeVar* a)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
tableChanges.emplace_back(a, a->boundTo);
}
void DEPRECATED_TxnLog::rollback()
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
for (auto it = typeVarChanges.rbegin(); it != typeVarChanges.rend(); ++it)
std::swap(*asMutable(it->first), it->second);
for (auto it = typePackChanges.rbegin(); it != typePackChanges.rend(); ++it)
std::swap(*asMutable(it->first), it->second);
for (auto it = tableChanges.rbegin(); it != tableChanges.rend(); ++it)
std::swap(it->first->boundTo, it->second);
LUAU_ASSERT(originalSeenSize <= sharedSeen->size());
sharedSeen->resize(originalSeenSize);
}
void DEPRECATED_TxnLog::concat(DEPRECATED_TxnLog rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
typeVarChanges.insert(typeVarChanges.end(), rhs.typeVarChanges.begin(), rhs.typeVarChanges.end());
rhs.typeVarChanges.clear();
typePackChanges.insert(typePackChanges.end(), rhs.typePackChanges.begin(), rhs.typePackChanges.end());
rhs.typePackChanges.clear();
tableChanges.insert(tableChanges.end(), rhs.tableChanges.begin(), rhs.tableChanges.end());
rhs.tableChanges.clear();
}
bool DEPRECATED_TxnLog::haveSeen(TypeId lhs, TypeId rhs)
{
return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
}
void DEPRECATED_TxnLog::pushSeen(TypeId lhs, TypeId rhs)
{
pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
}
void DEPRECATED_TxnLog::popSeen(TypeId lhs, TypeId rhs)
{
popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
}
bool DEPRECATED_TxnLog::haveSeen(TypePackId lhs, TypePackId rhs)
{
return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
}
void DEPRECATED_TxnLog::pushSeen(TypePackId lhs, TypePackId rhs)
{
pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
}
void DEPRECATED_TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
{
popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
}
bool DEPRECATED_TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
}
void DEPRECATED_TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
sharedSeen->push_back(sortedPair);
}
void DEPRECATED_TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
LUAU_ASSERT(sortedPair == sharedSeen->back());
sharedSeen->pop_back();
}
const std::string nullPendingResult = "<nullptr>";
std::string toString(PendingType* pending)
@ -170,8 +69,6 @@ const TxnLog* TxnLog::empty()
void TxnLog::concat(TxnLog rhs)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
for (auto& [ty, rep] : rhs.typeVarChanges)
typeVarChanges[ty] = std::move(rep);
@ -181,8 +78,6 @@ void TxnLog::concat(TxnLog rhs)
void TxnLog::commit()
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
for (auto& [ty, rep] : typeVarChanges)
*asMutable(ty) = rep.get()->pending;
@ -194,16 +89,12 @@ void TxnLog::commit()
void TxnLog::clear()
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
typeVarChanges.clear();
typePackChanges.clear();
}
TxnLog TxnLog::inverse()
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
TxnLog inversed(sharedSeen);
for (auto& [ty, _rep] : typeVarChanges)
@ -247,8 +138,6 @@ void TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{
@ -265,16 +154,12 @@ bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
sharedSeen->push_back(sortedPair);
}
void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
LUAU_ASSERT(sortedPair == sharedSeen->back());
sharedSeen->pop_back();
@ -282,7 +167,6 @@ void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
PendingType* TxnLog::queue(TypeId ty)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(!ty->persistent);
// Explicitly don't look in ancestors. If we have discovered something new
@ -296,7 +180,6 @@ PendingType* TxnLog::queue(TypeId ty)
PendingTypePack* TxnLog::queue(TypePackId tp)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(!tp->persistent);
// Explicitly don't look in ancestors. If we have discovered something new

View file

@ -24,7 +24,6 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
@ -36,6 +35,7 @@ LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauStatFunctionSimplify, false)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
@ -43,6 +43,8 @@ LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree)
LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false)
LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false)
LUAU_FASTFLAGVARIABLE(LuauFixArgumentCountMismatchAmountWithGenericTypes, false)
LUAU_FASTFLAGVARIABLE(LuauFixIncorrectLineNumberDuplicateType, false)
namespace Luau
{
@ -652,18 +654,15 @@ ErrorVec TypeChecker::tryUnify_(Id subTy, Id superTy, const Location& location)
{
Unifier state = mkUnifier(location);
if (FFlag::LuauUseCommittingTxnLog && FFlag::DebugLuauFreezeDuringUnification)
if (FFlag::DebugLuauFreezeDuringUnification)
freeze(currentModule->internalTypes);
state.tryUnify(subTy, superTy);
if (FFlag::LuauUseCommittingTxnLog && FFlag::DebugLuauFreezeDuringUnification)
if (FFlag::DebugLuauFreezeDuringUnification)
unfreeze(currentModule->internalTypes);
if (!state.errors.empty() && !FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
if (state.errors.empty() && FFlag::LuauUseCommittingTxnLog)
if (state.errors.empty())
state.log.commit();
return state.errors;
@ -847,8 +846,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
state.tryUnify(valuePack, variablePack);
reportErrors(state.errors);
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
// In the code 'local T = {}', we wish to ascribe the name 'T' to the type of the table for error-reporting purposes.
// We also want to do this for 'local T = setmetatable(...)'.
@ -1040,8 +1038,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
Unifier state = mkUnifier(firstValue->location);
checkArgumentList(loopScope, state, argPack, iterFunc->argTypes, /*argLocations*/ {});
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
reportErrors(state.errors);
}
@ -1102,8 +1099,53 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
scope->bindings[name->local] = {anyIfNonstrict(quantify(funScope, ty, name->local->location)), name->local->location};
return;
}
else if (auto name = function.name->as<AstExprIndexName>(); name && FFlag::LuauStatFunctionSimplify)
{
TypeId exprTy = checkExpr(scope, *name->expr).type;
TableTypeVar* ttv = getMutableTableType(exprTy);
if (!ttv)
{
if (isTableIntersection(exprTy))
reportError(TypeError{function.location, CannotExtendTable{exprTy, CannotExtendTable::Property, name->index.value}});
else if (!get<ErrorTypeVar>(exprTy) && !get<AnyTypeVar>(exprTy))
reportError(TypeError{function.location, OnlyTablesCanHaveMethods{exprTy}});
}
else if (ttv->state == TableState::Sealed)
reportError(TypeError{function.location, CannotExtendTable{exprTy, CannotExtendTable::Property, name->index.value}});
ty = follow(ty);
if (ttv && ttv->state != TableState::Sealed)
ttv->props[name->index.value] = {ty, /* deprecated */ false, {}, name->indexLocation};
if (function.func->self)
{
const FunctionTypeVar* funTy = get<FunctionTypeVar>(ty);
if (!funTy)
ice("Methods should be functions");
std::optional<TypeId> arg0 = first(funTy->argTypes);
if (!arg0)
ice("Methods should always have at least 1 argument (self)");
}
checkFunctionBody(funScope, ty, *function.func);
if (ttv && ttv->state != TableState::Sealed)
ttv->props[name->index.value] = {follow(quantify(funScope, ty, name->indexLocation)), /* deprecated */ false, {}, name->indexLocation};
}
else if (FFlag::LuauStatFunctionSimplify)
{
LUAU_ASSERT(function.name->is<AstExprError>());
ty = follow(ty);
checkFunctionBody(funScope, ty, *function.func);
}
else if (function.func->self)
{
LUAU_ASSERT(!FFlag::LuauStatFunctionSimplify);
AstExprIndexName* indexName = function.name->as<AstExprIndexName>();
if (!indexName)
ice("member function declaration has malformed name expression");
@ -1141,6 +1183,8 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
}
else
{
LUAU_ASSERT(!FFlag::LuauStatFunctionSimplify);
TypeId leftType = checkLValueBinding(scope, *function.name);
checkFunctionBody(funScope, ty, *function.func);
@ -1217,6 +1261,9 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
if (FFlag::LuauFixIncorrectLineNumberDuplicateType)
scope->typeAliasLocations[name] = typealias.location;
}
}
else
@ -2102,9 +2149,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUn
Unifier state = mkUnifier(expr.location);
state.tryUnify(actualFunctionType, expectedFunctionType, /*isFunctionCall*/ true);
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
TypeId retType = first(retTypePack).value_or(nilType);
if (!state.errors.empty())
@ -2283,9 +2328,7 @@ TypeId TypeChecker::checkRelationalOperation(
if (!isEquality)
{
state.tryUnify(rhsType, lhsType);
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
}
bool needsMetamethod = !isEquality;
@ -2336,8 +2379,7 @@ TypeId TypeChecker::checkRelationalOperation(
return errorRecoveryType(booleanType);
}
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
}
}
@ -2347,8 +2389,7 @@ TypeId TypeChecker::checkRelationalOperation(
state.tryUnify(
instantiate(scope, actualFunctionType, expr.location), instantiate(scope, *metamethod, expr.location), /*isFunctionCall*/ true);
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
reportErrors(state.errors);
return booleanType;
@ -2464,25 +2505,15 @@ TypeId TypeChecker::checkBinaryOperation(
TypePackId fallbackArguments = freshTypePack(scope);
TypeId fallbackFunctionType = addType(FunctionTypeVar(scope->level, fallbackArguments, retTypePack));
state.errors.clear();
if (FFlag::LuauUseCommittingTxnLog)
{
state.log.clear();
}
else
{
state.DEPRECATED_log.rollback();
}
state.log.clear();
state.tryUnify(actualFunctionType, fallbackFunctionType, /*isFunctionCall*/ true);
if (FFlag::LuauUseCommittingTxnLog && state.errors.empty())
if (state.errors.empty())
state.log.commit();
else if (!state.errors.empty() && !FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
}
if (FFlag::LuauUseCommittingTxnLog && !hasErrors)
if (!hasErrors)
{
state.log.commit();
}
@ -2729,13 +2760,11 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
TypeId retType = indexer->indexResultType;
if (!state.errors.empty())
{
if (!FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
reportError(expr.location, UnknownProperty{lhs, name});
retType = errorRecoveryType(retType);
}
else if (FFlag::LuauUseCommittingTxnLog)
else
state.log.commit();
return retType;
@ -3209,7 +3238,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
}
// Returns the minimum number of arguments the argument list can accept.
static size_t getMinParameterCount(TypePackId tp)
static size_t getMinParameterCount_DEPRECATED(TypePackId tp)
{
size_t minCount = 0;
size_t optionalCount = 0;
@ -3235,6 +3264,32 @@ static size_t getMinParameterCount(TypePackId tp)
return minCount;
}
static size_t getMinParameterCount(TxnLog* log, TypePackId tp)
{
size_t minCount = 0;
size_t optionalCount = 0;
auto it = begin(tp, log);
auto endIter = end(tp);
while (it != endIter)
{
TypeId ty = *it;
if (isOptional(ty))
++optionalCount;
else
{
minCount += optionalCount;
optionalCount = 0;
minCount++;
}
++it;
}
return minCount;
}
void TypeChecker::checkArgumentList(
const ScopePtr& scope, Unifier& state, TypePackId argPack, TypePackId paramPack, const std::vector<Location>& argLocations)
{
@ -3248,396 +3303,199 @@ void TypeChecker::checkArgumentList(
size_t paramIndex = 0;
size_t minParams = getMinParameterCount(paramPack);
size_t minParams = FFlag::LuauFixIncorrectLineNumberDuplicateType ? 0 : getMinParameterCount_DEPRECATED(paramPack);
if (FFlag::LuauUseCommittingTxnLog)
while (true)
{
while (true)
state.location = paramIndex < argLocations.size() ? argLocations[paramIndex] : state.location;
if (argIter == endIter && paramIter == endIter)
{
state.location = paramIndex < argLocations.size() ? argLocations[paramIndex] : state.location;
std::optional<TypePackId> argTail = argIter.tail();
std::optional<TypePackId> paramTail = paramIter.tail();
if (argIter == endIter && paramIter == endIter)
// If we hit the end of both type packs simultaneously, then there are definitely no further type
// errors to report. All we need to do is tie up any free tails.
//
// If one side has a free tail and the other has none at all, we create an empty pack and bind the
// free tail to that.
if (argTail)
{
std::optional<TypePackId> argTail = argIter.tail();
std::optional<TypePackId> paramTail = paramIter.tail();
// If we hit the end of both type packs simultaneously, then there are definitely no further type
// errors to report. All we need to do is tie up any free tails.
//
// If one side has a free tail and the other has none at all, we create an empty pack and bind the
// free tail to that.
if (argTail)
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*argTail)))
{
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*argTail)))
{
if (paramTail)
state.tryUnify(*paramTail, *argTail);
else
state.log.replace(*argTail, TypePackVar(TypePack{{}}));
}
}
else if (paramTail)
{
// argTail is definitely empty
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*paramTail)))
state.log.replace(*paramTail, TypePackVar(TypePack{{}}));
}
return;
}
else if (argIter == endIter)
{
// Not enough arguments.
// Might be ok if we are forwarding a vararg along. This is a common thing to occur in nonstrict mode.
if (argIter.tail())
{
TypePackId tail = *argIter.tail();
if (state.log.getMutable<Unifiable::Error>(tail))
{
// Unify remaining parameters so we don't leave any free-types hanging around.
while (paramIter != endIter)
{
state.tryUnify(errorRecoveryType(anyType), *paramIter);
++paramIter;
}
return;
}
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{
while (paramIter != endIter)
{
state.tryUnify(vtp->ty, *paramIter);
++paramIter;
}
return;
}
else if (state.log.getMutable<FreeTypePack>(tail))
{
std::vector<TypeId> rest;
rest.reserve(std::distance(paramIter, endIter));
while (paramIter != endIter)
{
rest.push_back(*paramIter);
++paramIter;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
state.tryUnify(varPack, tail);
return;
}
}
// If any remaining unfulfilled parameters are nonoptional, this is a problem.
while (paramIter != endIter)
{
TypeId t = state.log.follow(*paramIter);
if (isOptional(t))
{
} // ok
else if (state.log.getMutable<ErrorTypeVar>(t))
{
} // ok
else if (isNonstrictMode() && state.log.getMutable<AnyTypeVar>(t))
{
} // ok
if (paramTail)
state.tryUnify(*paramTail, *argTail);
else
{
state.reportError(TypeError{state.location, CountMismatch{minParams, paramIndex}});
return;
}
++paramIter;
state.log.replace(*argTail, TypePackVar(TypePack{{}}));
}
}
else if (paramIter == endIter)
else if (paramTail)
{
// too many parameters passed
if (!paramIter.tail())
{
while (argIter != endIter)
{
// The use of unify here is deliberate. We don't want this unification
// to be undoable.
unify(errorRecoveryType(scope), *argIter, state.location);
++argIter;
}
// For this case, we want the error span to cover every errant extra parameter
Location location = state.location;
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
return;
}
TypePackId tail = state.log.follow(*paramIter.tail());
// argTail is definitely empty
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*paramTail)))
state.log.replace(*paramTail, TypePackVar(TypePack{{}}));
}
return;
}
else if (argIter == endIter)
{
// Not enough arguments.
// Might be ok if we are forwarding a vararg along. This is a common thing to occur in nonstrict mode.
if (argIter.tail())
{
TypePackId tail = *argIter.tail();
if (state.log.getMutable<Unifiable::Error>(tail))
{
// Function is variadic. Ok.
// Unify remaining parameters so we don't leave any free-types hanging around.
while (paramIter != endIter)
{
state.tryUnify(errorRecoveryType(anyType), *paramIter);
++paramIter;
}
return;
}
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{
// Function is variadic and requires that all subsequent parameters
// be compatible with a type.
size_t argIndex = paramIndex;
while (argIter != endIter)
while (paramIter != endIter)
{
Location location = state.location;
if (argIndex < argLocations.size())
location = argLocations[argIndex];
unify(*argIter, vtp->ty, location);
++argIter;
++argIndex;
state.tryUnify(vtp->ty, *paramIter);
++paramIter;
}
return;
}
else if (state.log.getMutable<FreeTypePack>(tail))
{
// Create a type pack out of the remaining argument types
// and unify it with the tail.
std::vector<TypeId> rest;
rest.reserve(std::distance(argIter, endIter));
while (argIter != endIter)
rest.reserve(std::distance(paramIter, endIter));
while (paramIter != endIter)
{
rest.push_back(*argIter);
++argIter;
rest.push_back(*paramIter);
++paramIter;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
state.tryUnify(varPack, tail);
return;
}
else if (state.log.getMutable<FreeTypePack>(tail))
{
state.log.replace(tail, TypePackVar(TypePack{{}}));
return;
}
else if (state.log.getMutable<GenericTypePack>(tail))
{
// For this case, we want the error span to cover every errant extra parameter
Location location = state.location;
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
// TODO: Better error message?
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
return;
}
}
else
// If any remaining unfulfilled parameters are nonoptional, this is a problem.
while (paramIter != endIter)
{
unifyWithInstantiationIfNeeded(scope, *argIter, *paramIter, state);
++argIter;
TypeId t = state.log.follow(*paramIter);
if (isOptional(t))
{
} // ok
else if (state.log.getMutable<ErrorTypeVar>(t))
{
} // ok
else if (isNonstrictMode() && state.log.getMutable<AnyTypeVar>(t))
{
} // ok
else
{
if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes)
minParams = getMinParameterCount(&state.log, paramPack);
state.reportError(TypeError{state.location, CountMismatch{minParams, paramIndex}});
return;
}
++paramIter;
}
++paramIndex;
}
}
else
{
while (true)
else if (paramIter == endIter)
{
state.location = paramIndex < argLocations.size() ? argLocations[paramIndex] : state.location;
if (argIter == endIter && paramIter == endIter)
// too many parameters passed
if (!paramIter.tail())
{
std::optional<TypePackId> argTail = argIter.tail();
std::optional<TypePackId> paramTail = paramIter.tail();
// If we hit the end of both type packs simultaneously, then there are definitely no further type
// errors to report. All we need to do is tie up any free tails.
//
// If one side has a free tail and the other has none at all, we create an empty pack and bind the
// free tail to that.
if (argTail)
while (argIter != endIter)
{
if (get<Unifiable::Free>(*argTail))
{
if (paramTail)
state.tryUnify(*paramTail, *argTail);
else
{
state.DEPRECATED_log(*argTail);
*asMutable(*argTail) = TypePack{{}};
}
}
// The use of unify here is deliberate. We don't want this unification
// to be undoable.
unify(errorRecoveryType(scope), *argIter, state.location);
++argIter;
}
else if (paramTail)
// For this case, we want the error span to cover every errant extra parameter
Location location = state.location;
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes)
minParams = getMinParameterCount(&state.log, paramPack);
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
return;
}
TypePackId tail = state.log.follow(*paramIter.tail());
if (state.log.getMutable<Unifiable::Error>(tail))
{
// Function is variadic. Ok.
return;
}
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{
// Function is variadic and requires that all subsequent parameters
// be compatible with a type.
size_t argIndex = paramIndex;
while (argIter != endIter)
{
// argTail is definitely empty
if (get<Unifiable::Free>(*paramTail))
{
state.DEPRECATED_log(*paramTail);
*asMutable(*paramTail) = TypePack{{}};
}
Location location = state.location;
if (argIndex < argLocations.size())
location = argLocations[argIndex];
unify(*argIter, vtp->ty, location);
++argIter;
++argIndex;
}
return;
}
else if (argIter == endIter)
else if (state.log.getMutable<FreeTypePack>(tail))
{
// Not enough arguments.
// Might be ok if we are forwarding a vararg along. This is a common thing to occur in nonstrict mode.
if (argIter.tail())
// Create a type pack out of the remaining argument types
// and unify it with the tail.
std::vector<TypeId> rest;
rest.reserve(std::distance(argIter, endIter));
while (argIter != endIter)
{
TypePackId tail = *argIter.tail();
if (get<Unifiable::Error>(tail))
{
// Unify remaining parameters so we don't leave any free-types hanging around.
while (paramIter != endIter)
{
state.tryUnify(*paramIter, errorRecoveryType(anyType));
++paramIter;
}
return;
}
else if (auto vtp = get<VariadicTypePack>(tail))
{
while (paramIter != endIter)
{
state.tryUnify(*paramIter, vtp->ty);
++paramIter;
}
return;
}
else if (get<FreeTypePack>(tail))
{
std::vector<TypeId> rest;
rest.reserve(std::distance(paramIter, endIter));
while (paramIter != endIter)
{
rest.push_back(*paramIter);
++paramIter;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
state.tryUnify(varPack, tail);
return;
}
rest.push_back(*argIter);
++argIter;
}
// If any remaining unfulfilled parameters are nonoptional, this is a problem.
while (paramIter != endIter)
{
TypeId t = follow(*paramIter);
if (isOptional(t))
{
} // ok
else if (get<ErrorTypeVar>(t))
{
} // ok
else if (isNonstrictMode() && get<AnyTypeVar>(t))
{
} // ok
else
{
state.reportError(TypeError{state.location, CountMismatch{minParams, paramIndex}});
return;
}
++paramIter;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
state.tryUnify(varPack, tail);
return;
}
else if (paramIter == endIter)
else if (state.log.getMutable<FreeTypePack>(tail))
{
// too many parameters passed
if (!paramIter.tail())
{
while (argIter != endIter)
{
unify(*argIter, errorRecoveryType(scope), state.location);
++argIter;
}
// For this case, we want the error span to cover every errant extra parameter
Location location = state.location;
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
return;
}
TypePackId tail = *paramIter.tail();
if (get<Unifiable::Error>(tail))
{
// Function is variadic. Ok.
return;
}
else if (auto vtp = get<VariadicTypePack>(tail))
{
// Function is variadic and requires that all subsequent parameters
// be compatible with a type.
size_t argIndex = paramIndex;
while (argIter != endIter)
{
Location location = state.location;
if (argIndex < argLocations.size())
location = argLocations[argIndex];
unify(*argIter, vtp->ty, location);
++argIter;
++argIndex;
}
return;
}
else if (get<FreeTypePack>(tail))
{
// Create a type pack out of the remaining argument types
// and unify it with the tail.
std::vector<TypeId> rest;
rest.reserve(std::distance(argIter, endIter));
while (argIter != endIter)
{
rest.push_back(*argIter);
++argIter;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
state.tryUnify(tail, varPack);
return;
}
else if (get<FreeTypePack>(tail))
{
if (FFlag::LuauUseCommittingTxnLog)
{
state.log.replace(tail, TypePackVar(TypePack{{}}));
}
else
{
state.DEPRECATED_log(tail);
*asMutable(tail) = TypePack{};
}
return;
}
else if (get<GenericTypePack>(tail))
{
// For this case, we want the error span to cover every errant extra parameter
Location location = state.location;
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
// TODO: Better error message?
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
return;
}
state.log.replace(tail, TypePackVar(TypePack{{}}));
return;
}
else
else if (state.log.getMutable<GenericTypePack>(tail))
{
unifyWithInstantiationIfNeeded(scope, *argIter, *paramIter, state);
++argIter;
++paramIter;
// For this case, we want the error span to cover every errant extra parameter
Location location = state.location;
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
// TODO: Better error message?
if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes)
minParams = getMinParameterCount(&state.log, paramPack);
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
return;
}
++paramIndex;
}
else
{
unifyWithInstantiationIfNeeded(scope, *argIter, *paramIter, state);
++argIter;
++paramIter;
}
++paramIndex;
}
}
@ -3882,9 +3740,6 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
checkArgumentList(scope, state, retPack, ftv->retType, /*argLocations*/ {});
if (!state.errors.empty())
{
if (!FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
return {};
}
@ -3912,14 +3767,10 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
overloadsThatDont.push_back(fn);
errors.emplace_back(std::move(state.errors), args->head, ftv);
if (!FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
}
else
{
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
if (isNonstrictMode() && !expr.self && expr.func->is<AstExprIndexName>() && ftv->hasSelf)
{
@ -3976,8 +3827,7 @@ bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCal
if (editedState.errors.empty())
{
if (FFlag::LuauUseCommittingTxnLog)
editedState.log.commit();
editedState.log.commit();
reportError(TypeError{expr.location, FunctionDoesNotTakeSelf{}});
// This is a little bit suspect: If this overload would work with a . replaced by a :
@ -3987,8 +3837,6 @@ bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCal
// checkArgumentList(scope, editedState, retPack, ftv->retType, retLocations, CountMismatch::Return);
return true;
}
else if (!FFlag::LuauUseCommittingTxnLog)
editedState.DEPRECATED_log.rollback();
}
else if (ftv->hasSelf)
{
@ -4010,8 +3858,7 @@ bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCal
if (editedState.errors.empty())
{
if (FFlag::LuauUseCommittingTxnLog)
editedState.log.commit();
editedState.log.commit();
reportError(TypeError{expr.location, FunctionRequiresSelf{}});
// This is a little bit suspect: If this overload would work with a : replaced by a .
@ -4021,8 +3868,6 @@ bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCal
// checkArgumentList(scope, editedState, retPack, ftv->retType, retLocations, CountMismatch::Return);
return true;
}
else if (!FFlag::LuauUseCommittingTxnLog)
editedState.DEPRECATED_log.rollback();
}
}
}
@ -4082,7 +3927,7 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast
checkArgumentList(scope, state, argPack, ftv->argTypes, argLocations);
}
if (FFlag::LuauUseCommittingTxnLog && state.errors.empty())
if (state.errors.empty())
state.log.commit();
if (i > 0)
@ -4092,9 +3937,6 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast
s += "and ";
s += toString(overload);
if (!FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
}
if (overloadsThatMatchArgCount.size() == 0)
@ -4168,24 +4010,16 @@ ExprResult<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const L
// just performed. There's not a great way to pass that into checkExpr. Instead, we store
// the inverse of the current log, and commit it. When we're done, we'll commit all the
// inverses. This isn't optimal, and a better solution is welcome here.
if (FFlag::LuauUseCommittingTxnLog)
{
inverseLogs.push_back(state.log.inverse());
state.log.commit();
}
inverseLogs.push_back(state.log.inverse());
state.log.commit();
}
tp->head.push_back(actualType);
}
}
if (FFlag::LuauUseCommittingTxnLog)
{
for (TxnLog& log : inverseLogs)
log.commit();
}
else
state.DEPRECATED_log.rollback();
for (TxnLog& log : inverseLogs)
log.commit();
return {pack, predicates};
}
@ -4294,8 +4128,7 @@ bool TypeChecker::unify(TypeId subTy, TypeId superTy, const Location& location,
Unifier state = mkUnifier(location);
state.tryUnify(subTy, superTy, options.isFunctionCall);
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
reportErrors(state.errors);
@ -4308,8 +4141,7 @@ bool TypeChecker::unify(TypePackId subTy, TypePackId superTy, const Location& lo
state.ctx = ctx;
state.tryUnify(subTy, superTy);
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
reportErrors(state.errors);
@ -4321,8 +4153,7 @@ bool TypeChecker::unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId s
Unifier state = mkUnifier(location);
unifyWithInstantiationIfNeeded(scope, subTy, superTy, state);
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
reportErrors(state.errors);
@ -4352,31 +4183,18 @@ void TypeChecker::unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId s
if (subTy == instantiated)
{
// Instantiating the argument made no difference, so just report any child errors
if (FFlag::LuauUseCommittingTxnLog)
state.log.concat(std::move(child.log));
else
state.DEPRECATED_log.concat(std::move(child.DEPRECATED_log));
state.log.concat(std::move(child.log));
state.errors.insert(state.errors.end(), child.errors.begin(), child.errors.end());
}
else
{
if (!FFlag::LuauUseCommittingTxnLog)
child.DEPRECATED_log.rollback();
state.tryUnify(instantiated, superTy, /*isFunctionCall*/ false);
}
}
else
{
if (FFlag::LuauUseCommittingTxnLog)
{
state.log.concat(std::move(child.log));
}
else
{
state.DEPRECATED_log.concat(std::move(child.DEPRECATED_log));
}
state.log.concat(std::move(child.log));
}
}
}
@ -4540,7 +4358,7 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log)
{
Instantiation instantiation{FFlag::LuauUseCommittingTxnLog ? log : TxnLog::empty(), &currentModule->internalTypes, scope->level};
Instantiation instantiation{log, &currentModule->internalTypes, scope->level};
std::optional<TypeId> instantiated = instantiation.substitute(ty);
if (instantiated.has_value())
return *instantiated;

View file

@ -5,8 +5,6 @@
#include <stdexcept>
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
namespace Luau
{
@ -51,16 +49,8 @@ TypePackIterator::TypePackIterator(TypePackId typePack, const TxnLog* log)
{
while (tp && tp->head.empty())
{
if (FFlag::LuauUseCommittingTxnLog)
{
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
}
else
{
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
}
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
}
}
@ -71,16 +61,8 @@ TypePackIterator& TypePackIterator::operator++()
++currentIndex;
while (tp && currentIndex >= tp->head.size())
{
if (FFlag::LuauUseCommittingTxnLog)
{
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
}
else
{
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
}
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
currentIndex = 0;
}

File diff suppressed because it is too large Load diff

View file

@ -229,19 +229,46 @@ LUA_API void lua_setthreaddata(lua_State* L, void* data);
enum lua_GCOp
{
/* stop and resume incremental garbage collection */
LUA_GCSTOP,
LUA_GCRESTART,
/* run a full GC cycle; not recommended for latency sensitive applications */
LUA_GCCOLLECT,
/* return the heap size in KB and the remainder in bytes */
LUA_GCCOUNT,
LUA_GCCOUNTB,
/* return 1 if GC is active (not stopped); note that GC may not be actively collecting even if it's running */
LUA_GCISRUNNING,
// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
// explicit GC steps allow to perform some amount of work at custom points to offset the need for GC assists
// note that GC might also be paused for some duration (until bytes allocated meet the threshold)
// if an explicit step is performed during this pause, it will trigger the start of the next collection cycle
/*
** perform an explicit GC step, with the step size specified in KB
**
** garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
** explicit GC steps allow to perform some amount of work at custom points to offset the need for GC assists
** note that GC might also be paused for some duration (until bytes allocated meet the threshold)
** if an explicit step is performed during this pause, it will trigger the start of the next collection cycle
*/
LUA_GCSTEP,
/*
** tune GC parameters G (goal), S (step multiplier) and step size (usually best left ignored)
**
** garbage collection is incremental and tries to maintain the heap size to balance memory and performance overhead
** this overhead is determined by G (goal) which is the ratio between total heap size and the amount of live data in it
** G is specified in percentages; by default G=200% which means that the heap is allowed to grow to ~2x the size of live data.
**
** collector tries to collect S% of allocated bytes by interrupting the application after step size bytes were allocated.
** when S is too small, collector may not be able to catch up and the effective goal that can be reached will be larger.
** S is specified in percentages; by default S=200% which means that collector will run at ~2x the pace of allocations.
**
** it is recommended to set S in the interval [100 / (G - 100), 100 + 100 / (G - 100))] with a minimum value of 150%; for example:
** - for G=200%, S should be in the interval [150%, 200%]
** - for G=150%, S should be in the interval [200%, 300%]
** - for G=125%, S should be in the interval [400%, 500%]
*/
LUA_GCSETGOAL,
LUA_GCSETSTEPMUL,
LUA_GCSETSTEPSIZE,

View file

@ -59,33 +59,6 @@
#define LUA_IDSIZE 256
#endif
/*
@@ LUAI_GCGOAL defines the desired top heap size in relation to the live heap
@* size at the end of the GC cycle
** CHANGE it if you want the GC to run faster or slower (higher values
** 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
@* relative to memory allocation.
** Every LUAI_GCSTEPSIZE KB allocated, incremental collector collects LUAI_GCSTEPSIZE
** times LUAI_GCSTEPMUL% bytes.
** 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

View file

@ -54,6 +54,8 @@ LUALIB_API lua_State* luaL_newstate(void);
LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint);
LUALIB_API const char* luaL_typename(lua_State* L, int idx);
/*
** ===============================================================
** some useful macros
@ -66,8 +68,6 @@ LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname,
#define luaL_checkstring(L, n) (luaL_checklstring(L, (n), NULL))
#define luaL_optstring(L, n, d) (luaL_optlstring(L, (n), (d), NULL))
#define luaL_typename(L, i) lua_typename(L, lua_type(L, (i)))
#define luaL_getmetatable(L, n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
#define luaL_opt(L, f, n, d) (lua_isnoneornil(L, (n)) ? (d) : f(L, (n)))

View file

@ -11,6 +11,8 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauMorePreciseLuaLTypeName, false)
/* convert a stack index to positive */
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
@ -333,6 +335,19 @@ const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
return NULL;
}
const char* luaL_typename(lua_State* L, int idx)
{
if (DFFlag::LuauMorePreciseLuaLTypeName)
{
const TValue* obj = luaA_toobject(L, idx);
return luaT_objtypename(L, obj);
}
else
{
return lua_typename(L, lua_type(L, idx));
}
}
/*
** {======================================================
** Generic Buffer manipulation

View file

@ -10,6 +10,8 @@
#include <stdio.h>
#include <stdlib.h>
LUAU_DYNAMIC_FASTFLAG(LuauMorePreciseLuaLTypeName)
static void writestring(const char* s, size_t l)
{
fwrite(s, 1, l, stdout);
@ -186,7 +188,14 @@ static int luaB_gcinfo(lua_State* L)
static int luaB_type(lua_State* L)
{
luaL_checkany(L, 1);
lua_pushstring(L, luaL_typename(L, 1));
if (DFFlag::LuauMorePreciseLuaLTypeName)
{
lua_pushstring(L, lua_typename(L, lua_type(L, 1)));
}
else
{
lua_pushstring(L, luaL_typename(L, 1));
}
return 1;
}

View file

@ -6,6 +6,13 @@
#include "lobject.h"
#include "lstate.h"
/*
** Default settings for GC tunables (settable via lua_gc)
*/
#define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */
#define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */
#define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */
/*
** Possible states of the Garbage Collector
*/

View file

@ -14,7 +14,6 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAG(LuauTableCloneType)
using namespace Luau;
@ -1912,14 +1911,9 @@ local bar: @1= foo
CHECK(!ac.entryMap.count("foo"));
}
// Switch back to TEST_CASE_FIXTURE with regular ACFixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("type_correct_function_no_parenthesis")
TEST_CASE_FIXTURE(ACFixture, "type_correct_function_no_parenthesis")
{
ScopedFastFlag sff_LuauUseCommittingTxnLog = ScopedFastFlag("LuauUseCommittingTxnLog", true);
ACFixture fix;
fix.check(R"(
check(R"(
local function target(a: (number) -> number) return a(4) end
local function bar1(a: number) return -a end
local function bar2(a: string) return a .. 'x' end
@ -1927,7 +1921,7 @@ local function bar2(a: string) return a .. 'x' end
return target(b@1
)");
auto ac = fix.autocomplete('1');
auto ac = autocomplete('1');
CHECK(ac.entryMap.count("bar1"));
CHECK(ac.entryMap["bar1"].typeCorrect == TypeCorrectKind::Correct);
@ -1937,8 +1931,6 @@ return target(b@1
TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
check(R"(
local function bar(a: number) return -a end
local abc = b@1
@ -1952,8 +1944,6 @@ local abc = b@1
TEST_CASE_FIXTURE(ACFixture, "function_result_passed_to_function_has_parentheses")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
check(R"(
local function foo() return 1 end
local function bar(a: number) return -a end
@ -1978,14 +1968,9 @@ local fp: @1= f
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
}
// Switch back to TEST_CASE_FIXTURE with regular ACFixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("type_correct_keywords")
TEST_CASE_FIXTURE(ACFixture, "type_correct_keywords")
{
ScopedFastFlag sff_LuauUseCommittingTxnLog = ScopedFastFlag("LuauUseCommittingTxnLog", true);
ACFixture fix;
fix.check(R"(
check(R"(
local function a(x: boolean) end
local function b(x: number?) end
local function c(x: (number) -> string) end
@ -2002,26 +1987,26 @@ local dc = d(f@4)
local ec = e(f@5)
)");
auto ac = fix.autocomplete('1');
auto ac = autocomplete('1');
CHECK(ac.entryMap.count("tru"));
CHECK(ac.entryMap["tru"].typeCorrect == TypeCorrectKind::None);
CHECK(ac.entryMap["true"].typeCorrect == TypeCorrectKind::Correct);
CHECK(ac.entryMap["false"].typeCorrect == TypeCorrectKind::Correct);
ac = fix.autocomplete('2');
ac = autocomplete('2');
CHECK(ac.entryMap.count("ni"));
CHECK(ac.entryMap["ni"].typeCorrect == TypeCorrectKind::None);
CHECK(ac.entryMap["nil"].typeCorrect == TypeCorrectKind::Correct);
ac = fix.autocomplete('3');
ac = autocomplete('3');
CHECK(ac.entryMap.count("false"));
CHECK(ac.entryMap["false"].typeCorrect == TypeCorrectKind::None);
CHECK(ac.entryMap["function"].typeCorrect == TypeCorrectKind::Correct);
ac = fix.autocomplete('4');
ac = autocomplete('4');
CHECK(ac.entryMap["function"].typeCorrect == TypeCorrectKind::Correct);
ac = fix.autocomplete('5');
ac = autocomplete('5');
CHECK(ac.entryMap["function"].typeCorrect == TypeCorrectKind::Correct);
}
@ -2512,23 +2497,21 @@ local t = {
CHECK(ac.entryMap.count("second"));
}
TEST_CASE("autocomplete_documentation_symbols")
TEST_CASE_FIXTURE(Fixture, "autocomplete_documentation_symbols")
{
Fixture fix(FFlag::LuauUseCommittingTxnLog);
fix.loadDefinition(R"(
loadDefinition(R"(
declare y: {
x: number,
}
)");
fix.fileResolver.source["Module/A"] = R"(
fileResolver.source["Module/A"] = R"(
local a = y.
)";
fix.frontend.check("Module/A");
frontend.check("Module/A");
auto ac = autocomplete(fix.frontend, "Module/A", Position{1, 21}, nullCallback);
auto ac = autocomplete(frontend, "Module/A", Position{1, 21}, nullCallback);
REQUIRE(ac.entryMap.count("x"));
CHECK_EQ(ac.entryMap["x"].documentationSymbol, "@test/global/y.x");
@ -2646,8 +2629,6 @@ local a: A<(number, s@1>
TEST_CASE_FIXTURE(ACFixture, "autocomplete_first_function_arg_expected_type")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
check(R"(
local function foo1() return 1 end
local function foo2() return "1" end
@ -2720,7 +2701,6 @@ type A<T... = ...@1> = () -> T
TEST_CASE_FIXTURE(ACFixture, "autocomplete_oop_implicit_self")
{
ScopedFastFlag flag("LuauMissingFollowACMetatables", true);
check(R"(
--!strict
local Class = {}
@ -2764,8 +2744,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_on_string_singletons")
TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses_2")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
check(R"(
local bar: ((number) -> number) & (number, number) -> number)
local abc = b@1

View file

@ -7,6 +7,8 @@
using namespace Luau;
LUAU_FASTFLAG(LuauFixIncorrectLineNumberDuplicateType)
TEST_SUITE_BEGIN("TypeAliases");
TEST_CASE_FIXTURE(Fixture, "cyclic_function_type_in_type_alias")
@ -241,6 +243,27 @@ TEST_CASE_FIXTURE(Fixture, "export_type_and_type_alias_are_duplicates")
CHECK_EQ(dtd->name, "Foo");
}
TEST_CASE_FIXTURE(Fixture, "reported_location_is_correct_when_type_alias_are_duplicates")
{
CheckResult result = check(R"(
type A = string
type B = number
type C = string
type B = number
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto dtd = get<DuplicateTypeDefinition>(result.errors[0]);
REQUIRE(dtd);
CHECK_EQ(dtd->name, "B");
if (FFlag::LuauFixIncorrectLineNumberDuplicateType)
CHECK_EQ(dtd->previousLocation.begin.line + 1, 3);
else
CHECK_EQ(dtd->previousLocation.begin.line + 1, 1);
}
TEST_CASE_FIXTURE(Fixture, "stringify_optional_parameterized_alias")
{
CheckResult result = check(R"(

View file

@ -8,8 +8,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
TEST_SUITE_BEGIN("BuiltinTests");
TEST_CASE_FIXTURE(Fixture, "math_things_are_defined")
@ -443,28 +441,19 @@ TEST_CASE_FIXTURE(Fixture, "os_time_takes_optional_date_table")
CHECK_EQ(*typeChecker.numberType, *requireType("n3"));
}
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("thread_is_a_type")
TEST_CASE_FIXTURE(Fixture, "thread_is_a_type")
{
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
CheckResult result = check(R"(
local co = coroutine.create(function() end)
)");
// Replace with LUAU_REQUIRE_NO_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() == 0);
CHECK_EQ(*fix.typeChecker.threadType, *fix.requireType("co"));
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*typeChecker.threadType, *requireType("co"));
}
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("coroutine_resume_anything_goes")
TEST_CASE_FIXTURE(Fixture, "coroutine_resume_anything_goes")
{
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
CheckResult result = check(R"(
local function nifty(x, y)
print(x, y)
local z = coroutine.yield(1, 2)
@ -477,17 +466,12 @@ TEST_CASE("coroutine_resume_anything_goes")
local answer = coroutine.resume(co, 3)
)");
// Replace with LUAU_REQUIRE_NO_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() == 0);
LUAU_REQUIRE_NO_ERRORS(result);
}
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("coroutine_wrap_anything_goes")
TEST_CASE_FIXTURE(Fixture, "coroutine_wrap_anything_goes")
{
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
CheckResult result = check(R"(
--!nonstrict
local function nifty(x, y)
print(x, y)
@ -501,8 +485,7 @@ TEST_CASE("coroutine_wrap_anything_goes")
local answer = f(3)
)");
// Replace with LUAU_REQUIRE_NO_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() == 0);
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "setmetatable_should_not_mutate_persisted_types")
@ -961,4 +944,18 @@ TEST_CASE_FIXTURE(Fixture, "table_freeze_is_generic")
CHECK_EQ("*unknown*", toString(requireType("d")));
}
TEST_CASE_FIXTURE(Fixture, "set_metatable_needs_arguments")
{
ScopedFastFlag sff{"LuauSetMetaTableArgsCheck", true};
CheckResult result = check(R"(
local a = {b=setmetatable}
a.b()
a:b()
a:b({})
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(result.errors[0], (TypeError{Location{{2, 0}, {2, 5}}, CountMismatch{2, 0}}));
CHECK_EQ(result.errors[1], (TypeError{Location{{3, 0}, {3, 5}}, CountMismatch{2, 1}}));
}
TEST_SUITE_END();

View file

@ -8,6 +8,8 @@
using namespace Luau;
LUAU_FASTFLAG(LuauFixArgumentCountMismatchAmountWithGenericTypes)
TEST_SUITE_BEGIN("GenericsTests");
TEST_CASE_FIXTURE(Fixture, "check_generic_function")
@ -786,4 +788,47 @@ local TheDispatcher: Dispatcher = {
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
{
CheckResult result = check(R"(
function test(a: number)
return 1
end
function wrapper<A...>(f: (A...) -> number, ...: A...)
end
wrapper(test)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes)
CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function expects 2 arguments, but only 1 is specified)");
else
CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function expects 1 argument, but 1 is specified)");
}
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
{
CheckResult result = check(R"(
function test2(a: number, b: string)
return 1
end
function wrapper<A...>(f: (A...) -> number, ...: A...)
end
wrapper(test2, 1, "", 3)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes)
CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function expects 3 arguments, but 4 are specified)");
else
CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function expects 1 argument, but 4 are specified)");
}
TEST_SUITE_END();

View file

@ -2334,4 +2334,54 @@ TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a
REQUIRE_EQ("{| [any]: any, x: number, y: number |} | {| y: number |}", toString(requireType("b")));
}
TEST_CASE_FIXTURE(Fixture, "unifying_tables_shouldnt_uaf1")
{
ScopedFastFlag sff{"LuauTxnLogCheckForInvalidation", true};
CheckResult result = check(R"(
-- This example produced a UAF at one point, caused by pointers to table types becoming
-- invalidated by child unifiers. (Calling log.concat can cause pointers to become invalid.)
type _Entry = {
a: number,
middle: (self: _Entry) -> (),
z: number
}
export type AnyEntry = _Entry
local Entry = {}
Entry.__index = Entry
function Entry:dispose()
self:middle()
forgetChildren(self) -- unify free with sealed AnyEntry
end
function forgetChildren(parent: AnyEntry)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "unifying_tables_shouldnt_uaf2")
{
ScopedFastFlag sff{"LuauTxnLogCheckForInvalidation", true};
CheckResult result = check(R"(
-- Another example that UAFd, this time found by fuzzing.
local _
do
_._ *= (_[{n0=_[{[{[_]=_,}]=_,}],}])[_]
_ = (_.n0)
end
_._ *= (_[false])[_]
_ = (_.cos)
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -5144,7 +5144,6 @@ end
TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
{
ScopedFastFlag committingTxnLog{"LuauUseCommittingTxnLog", true};
ScopedFastFlag subtypingVariance{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
@ -5355,4 +5354,41 @@ end
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "function_decl_quantify_right_type")
{
ScopedFastFlag statFunctionSimplify{"LuauStatFunctionSimplify", true};
fileResolver.source["game/isAMagicMock"] = R"(
--!nonstrict
return function(value)
return false
end
)";
CheckResult result = check(R"(
--!nonstrict
local MagicMock = {}
MagicMock.is = require(game.isAMagicMock)
function MagicMock.is(value)
return false
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "function_decl_non_self_sealed_overwrite")
{
ScopedFastFlag statFunctionSimplify{"LuauStatFunctionSimplify", true};
CheckResult result = check(R"(
function string.len(): number
return 1
end
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -9,8 +9,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
struct TryUnifyFixture : Fixture
{
TypeArena arena;
@ -43,8 +41,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
state.tryUnify(&functionTwo, &functionOne);
CHECK(state.errors.empty());
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
CHECK_EQ(functionOne, functionTwo);
}
@ -86,8 +83,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
CHECK(state.errors.empty());
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
state.log.commit();
CHECK_EQ(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
}
@ -110,9 +106,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
CHECK_EQ(1, state.errors.size());
if (!FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
}
@ -217,34 +210,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "cli_41095_concat_log_in_sealed_table_unifica
CHECK_EQ(toString(result.errors[1]), "Available overloads: ({a}, a) -> (); and ({a}, number, a) -> ()");
}
TEST_CASE("undo_new_prop_on_unsealed_table")
{
ScopedFastFlag flags[] = {
{"LuauTableSubtypingVariance2", true},
// This test makes no sense with a committing TxnLog.
{"LuauUseCommittingTxnLog", false},
};
// I am not sure how to make this happen in Luau code.
TryUnifyFixture fix;
TypeId unsealedTable = fix.arena.addType(TableTypeVar{TableState::Unsealed, TypeLevel{}});
TypeId sealedTable =
fix.arena.addType(TableTypeVar{{{"prop", Property{getSingletonTypes().numberType}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
const TableTypeVar* ttv = get<TableTypeVar>(unsealedTable);
REQUIRE(ttv);
fix.state.tryUnify(sealedTable, unsealedTable);
// To be honest, it's really quite spooky here that we're amending an unsealed table in this case.
CHECK(!ttv->props.empty());
fix.state.DEPRECATED_log.rollback();
CHECK(ttv->props.empty());
}
TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
{
TypePackId threeNumbers = arena.addTypePack(TypePack{{typeChecker.numberType, typeChecker.numberType, typeChecker.numberType}, std::nullopt});
@ -267,11 +232,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
TEST_CASE_FIXTURE(TryUnifyFixture, "cli_50320_follow_in_any_unification")
{
ScopedFastFlag sffs[] = {
{"LuauUseCommittingTxnLog", true},
{"LuauFollowWithCommittingTxnLogInAnyUnification", true},
};
TypePackVar free{FreeTypePack{TypeLevel{}}};
TypePackVar target{TypePack{}};

View file

@ -9,8 +9,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
TEST_SUITE_BEGIN("TypePackTests");
TEST_CASE_FIXTURE(Fixture, "infer_multi_return")
@ -264,13 +262,9 @@ TEST_CASE_FIXTURE(Fixture, "variadic_pack_syntax")
CHECK_EQ(toString(requireType("foo")), "(...number) -> ()");
}
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("type_pack_hidden_free_tail_infinite_growth")
TEST_CASE_FIXTURE(Fixture, "type_pack_hidden_free_tail_infinite_growth")
{
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
CheckResult result = check(R"(
--!nonstrict
if _ then
_[function(l0)end],l0 = _
@ -282,8 +276,7 @@ elseif _ then
end
)");
// Switch back to LUAU_REQUIRE_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() > 0);
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "variadic_argument_tail")

View file

@ -7,7 +7,6 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauEqConstraint)
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
using namespace Luau;
@ -282,19 +281,16 @@ local c = b:foo(1, 2)
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
}
TEST_CASE("optional_union_follow")
TEST_CASE_FIXTURE(Fixture, "optional_union_follow")
{
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
CheckResult result = check(R"(
local y: number? = 2
local x = y
local function f(a: number, b: typeof(x), c: typeof(x)) return -a end
return f()
)");
REQUIRE_EQ(result.errors.size(), 1);
// LUAU_REQUIRE_ERROR_COUNT(1, result);
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);

107
tools/LuauVisualize.py Normal file
View file

@ -0,0 +1,107 @@
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
# HACK: LLDB's python API doesn't afford anything helpful for getting at variadic template parameters.
# We're forced to resort to parsing names as strings.
def templateParams(s):
depth = 0
start = s.find('<') + 1
result = []
for i, c in enumerate(s[start:], start):
if c == '<':
depth += 1
elif c == '>':
if depth == 0:
result.append(s[start: i].strip())
break
depth -= 1
elif c == ',' and depth == 0:
result.append(s[start: i].strip())
start = i + 1
return result
def getType(target, typeName):
stars = 0
typeName = typeName.strip()
while typeName.endswith('*'):
stars += 1
typeName = typeName[:-1]
if typeName.startswith('const '):
typeName = typeName[6:]
ty = target.FindFirstType(typeName.strip())
for _ in range(stars):
ty = ty.GetPointerType()
return ty
def luau_variant_summary(valobj, internal_dict, options):
type_id = valobj.GetChildMemberWithName("typeid").GetValueAsUnsigned()
storage = valobj.GetChildMemberWithName("storage")
params = templateParams(valobj.GetType().GetCanonicalType().GetName())
stored_type = params[type_id]
value = storage.Cast(stored_type.GetPointerType()).Dereference()
return stored_type.GetDisplayTypeName() + " [" + value.GetValue() + "]"
class LuauVariantSyntheticChildrenProvider:
node_names = ["type", "value"]
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.type_index = None
self.current_type = None
self.type_params = []
self.stored_value = None
def num_children(self):
return len(self.node_names)
def has_children(self):
return True
def get_child_index(self, name):
try:
return self.node_names.index(name)
except ValueError:
return -1
def get_child_at_index(self, index):
try:
node = self.node_names[index]
except IndexError:
return None
if node == "type":
if self.current_type:
return self.valobj.CreateValueFromExpression(node, f"(const char*)\"{self.current_type.GetDisplayTypeName()}\"")
else:
return self.valobj.CreateValueFromExpression(node, "(const char*)\"<unknown type>\"")
elif node == "value":
if self.stored_value is not None:
if self.current_type is not None:
return self.valobj.CreateValueFromData(node, self.stored_value.GetData(), self.current_type)
else:
return self.valobj.CreateValueExpression(node, "(const char*)\"<unknown type>\"")
else:
return self.valobj.CreateValueFromExpression(node, "(const char*)\"<no stored value>\"")
else:
return None
def update(self):
self.type_index = self.valobj.GetChildMemberWithName("typeid").GetValueAsSigned()
self.type_params = templateParams(self.valobj.GetType().GetCanonicalType().GetName())
if len(self.type_params) > self.type_index:
self.current_type = getType(self.valobj.GetTarget(), self.type_params[self.type_index])
if self.current_type:
storage = self.valobj.GetChildMemberWithName("storage")
self.stored_value = storage.Cast(self.current_type.GetPointerType()).Dereference()
else:
self.stored_value = None
else:
self.current_type = None
self.stored_value = None
return False

View file

@ -0,0 +1,2 @@
type synthetic add -x "^Luau::Variant<.+>$" -l LuauVisualize.LuauVariantSyntheticChildrenProvider
type summary add -x "^Luau::Variant<.+>$" -l LuauVisualize.luau_variant_summary