diff --git a/Analysis/include/Luau/TxnLog.h b/Analysis/include/Luau/TxnLog.h index f810538..c8ebaae 100644 --- a/Analysis/include/Luau/TxnLog.h +++ b/Analysis/include/Luau/TxnLog.h @@ -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>* 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> typeVarChanges; - std::vector> typePackChanges; - std::vector>> 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> ownedSeen; // used to avoid infinite recursion when types are cyclic - std::vector>* sharedSeen; // shared with all the descendent logs -}; - // Pending state for a TypeVar. Generated by a TxnLog and committed via // TxnLog::commit. struct PendingType diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index c74bad1..946be35 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -105,7 +105,6 @@ private: const TypePack* tp = nullptr; size_t currentIndex = 0; - // Only used if LuauUseCommittingTxnLog is true. const TxnLog* log; }; diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 4c0462f..71958f4 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -45,7 +45,6 @@ struct Unifier TypeArena* const types; Mode mode; - DEPRECATED_TxnLog DEPRECATED_log; TxnLog log; ErrorVec errors; Location location; diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index c3de8d0..e94c432 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -13,9 +13,6 @@ #include #include -LUAU_FASTFLAG(LuauUseCommittingTxnLog) -LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false); -LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false); LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false); static const std::unordered_set 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(followed) || get(followed)) + autocompleteProps(module, typeArena, followed, indexType, nodes, result, seen); + else if (auto indexFunction = get(followed)) { - TypeId followed = follow(indexIt->second.type); - if (get(followed) || get(followed)) - autocompleteProps(module, typeArena, followed, indexType, nodes, result, seen); - else if (auto indexFunction = get(followed)) - { - std::optional indexFunctionResult = first(indexFunction->retType); - if (indexFunctionResult) - autocompleteProps(module, typeArena, *indexFunctionResult, indexType, nodes, result, seen); - } - } - else - { - if (get(indexIt->second.type) || get(indexIt->second.type)) - autocompleteProps(module, typeArena, indexIt->second.type, indexType, nodes, result, seen); - else if (auto indexFunction = get(indexIt->second.type)) - { - std::optional indexFunctionResult = first(indexFunction->retType); - if (indexFunctionResult) - autocompleteProps(module, typeArena, *indexFunctionResult, indexType, nodes, result, seen); - } + std::optional indexFunctionResult = first(indexFunction->retType); + if (indexFunctionResult) + autocompleteProps(module, typeArena, *indexFunctionResult, indexType, nodes, result, seen); } } } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index e4e5dab..bf9ef30 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -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> magicFunctionSetMetaTable( TypeId mtTy = arena.addType(mtv); - AstExpr* targetExpr = expr.args.data[0]; - if (AstExprLocal* targetLocal = targetExpr->as()) + if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1) { - const Name targetName(targetLocal->local->name.value); - scope->bindings[targetLocal->local] = Binding{mtTy, expr.location}; + return ExprResult{}; + } + + if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self) + { + AstExpr* targetExpr = expr.args.data[0]; + if (AstExprLocal* targetLocal = targetExpr->as()) + { + const Name targetName(targetLocal->local->name.value); + scope->bindings[targetLocal->local] = Binding{mtTy, expr.location}; + } } return ExprResult{arena.addTypePack({mtTy})}; diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index c7bf1e6..876f5f0 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -7,110 +7,9 @@ #include #include -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 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 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 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 = ""; 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 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 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 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 diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 8e6b3b5..3fe4c90 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -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(); 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(exprTy) && !get(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(ty); + if (!funTy) + ice("Methods should be functions"); + + std::optional 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()); + + ty = follow(ty); + + checkFunctionBody(funScope, ty, *function.func); + } else if (function.func->self) { + LUAU_ASSERT(!FFlag::LuauStatFunctionSimplify); + AstExprIndexName* indexName = function.name->as(); 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 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 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& 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 argTail = argIter.tail(); + std::optional 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 argTail = argIter.tail(); - std::optional 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(state.log.follow(*argTail))) { - if (state.log.getMutable(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(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(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(tail)) - { - while (paramIter != endIter) - { - state.tryUnify(vtp->ty, *paramIter); - ++paramIter; - } - - return; - } - else if (state.log.getMutable(tail)) - { - std::vector 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(t)) - { - } // ok - else if (isNonstrictMode() && state.log.getMutable(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(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(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(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(tail)) { - // Create a type pack out of the remaining argument types - // and unify it with the tail. std::vector 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(tail)) - { - state.log.replace(tail, TypePackVar(TypePack{{}})); - return; - } - else if (state.log.getMutable(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(t)) + { + } // ok + else if (isNonstrictMode() && state.log.getMutable(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 argTail = argIter.tail(); - std::optional 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(*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(tail)) + { + // Function is variadic. Ok. + return; + } + else if (auto vtp = state.log.getMutable(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(*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(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 rest; + rest.reserve(std::distance(argIter, endIter)); + while (argIter != endIter) { - TypePackId tail = *argIter.tail(); - if (get(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(tail)) - { - while (paramIter != endIter) - { - state.tryUnify(*paramIter, vtp->ty); - ++paramIter; - } - - return; - } - else if (get(tail)) - { - std::vector 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(t)) - { - } // ok - else if (isNonstrictMode() && get(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(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(tail)) - { - // Function is variadic. Ok. - return; - } - else if (auto vtp = get(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(tail)) - { - // Create a type pack out of the remaining argument types - // and unify it with the tail. - std::vector 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(tail)) - { - if (FFlag::LuauUseCommittingTxnLog) - { - state.log.replace(tail, TypePackVar(TypePack{{}})); - } - else - { - state.DEPRECATED_log(tail); - *asMutable(tail) = TypePack{}; - } - - return; - } - else if (get(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(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> 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> 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() && 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 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(), ¤tModule->internalTypes, scope->level}; + Instantiation instantiation{log, ¤tModule->internalTypes, scope->level}; std::optional instantiated = instantiation.substitute(ty); if (instantiated.has_value()) return *instantiated; diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index b15548a..91123f4 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -5,8 +5,6 @@ #include -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(currentTypePack) : nullptr; - } - else - { - currentTypePack = tp->tail ? follow(*tp->tail) : nullptr; - tp = currentTypePack ? get(currentTypePack) : nullptr; - } + currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr; + tp = currentTypePack ? log->getMutable(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(currentTypePack) : nullptr; - } - else - { - currentTypePack = tp->tail ? follow(*tp->tail) : nullptr; - tp = currentTypePack ? get(currentTypePack) : nullptr; - } + currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr; + tp = currentTypePack ? log->getMutable(currentTypePack) : nullptr; currentIndex = 0; } diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 6c29486..7b781f2 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -15,30 +15,28 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); LUAU_FASTFLAG(LuauImmutableTypes) -LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000); LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false); LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false) -LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false) LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree, false) LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter, false) -LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, true) +LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, false) +LUAU_FASTFLAGVARIABLE(LuauTxnLogCheckForInvalidation, false) +LUAU_FASTFLAGVARIABLE(LuauTxnLogDontRetryForIndexers, false) namespace Luau { struct PromoteTypeLevels { - DEPRECATED_TxnLog& DEPRECATED_log; TxnLog& log; const TypeArena* typeArena = nullptr; TypeLevel minLevel; - explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel) - : DEPRECATED_log(DEPRECATED_log) - , log(log) + PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel) + : log(log) , typeArena(typeArena) , minLevel(minLevel) { @@ -50,15 +48,7 @@ struct PromoteTypeLevels LUAU_ASSERT(t); if (minLevel.subsumesStrict(t->level)) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.changeLevel(ty, minLevel); - } - else - { - DEPRECATED_log(ty); - t->level = minLevel; - } + log.changeLevel(ty, minLevel); } } @@ -81,10 +71,10 @@ struct PromoteTypeLevels { // Surprise, it's actually a BoundTypeVar that hasn't been committed yet. // Calling getMutable on this will trigger an assertion. - if (FFlag::LuauUseCommittingTxnLog && !log.is(ty)) + if (!log.is(ty)) return true; - promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable(ty) : getMutable(ty)); + promote(ty, log.getMutable(ty)); return true; } @@ -94,7 +84,7 @@ struct PromoteTypeLevels if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) return false; - promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable(ty) : getMutable(ty)); + promote(ty, log.getMutable(ty)); return true; } @@ -107,7 +97,7 @@ struct PromoteTypeLevels if (ttv.state != TableState::Free && ttv.state != TableState::Generic) return true; - promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable(ty) : getMutable(ty)); + promote(ty, log.getMutable(ty)); return true; } @@ -115,33 +105,33 @@ struct PromoteTypeLevels { // Surprise, it's actually a BoundTypePack that hasn't been committed yet. // Calling getMutable on this will trigger an assertion. - if (FFlag::LuauUseCommittingTxnLog && !log.is(tp)) + if (!log.is(tp)) return true; - promote(tp, FFlag::LuauUseCommittingTxnLog ? log.getMutable(tp) : getMutable(tp)); + promote(tp, log.getMutable(tp)); return true; } }; -static void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty) +static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty) { // Type levels of types from other modules are already global, so we don't need to promote anything inside if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) return; - PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel}; + PromoteTypeLevels ptl{log, typeArena, minLevel}; DenseHashSet seen{nullptr}; visitTypeVarOnce(ty, ptl, seen); } // TODO: use this and make it static. -void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp) +void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp) { // Type levels of types from other modules are already global, so we don't need to promote anything inside if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena) return; - PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel}; + PromoteTypeLevels ptl{log, typeArena, minLevel}; DenseHashSet seen{nullptr}; visitTypeVarOnce(tp, ptl, seen); } @@ -251,7 +241,7 @@ struct SkipCacheForType bool Widen::isDirty(TypeId ty) { - return FFlag::LuauUseCommittingTxnLog ? log->is(ty) : bool(get(ty)); + return log->is(ty); } bool Widen::isDirty(TypePackId) @@ -262,7 +252,7 @@ bool Widen::isDirty(TypePackId) TypeId Widen::clean(TypeId ty) { LUAU_ASSERT(isDirty(ty)); - auto stv = FFlag::LuauUseCommittingTxnLog ? log->getMutable(ty) : getMutable(ty); + auto stv = log->getMutable(ty); LUAU_ASSERT(stv); if (get(stv)) @@ -284,11 +274,11 @@ bool Widen::ignoreChildren(TypeId ty) { // Sometimes we unify ("hi") -> free1 with (free2) -> free3, so don't ignore functions. // TODO: should we be doing this? we would need to rework how checkCallOverload does the unification. - if (FFlag::LuauUseCommittingTxnLog ? log->is(ty) : bool(get(ty))) + if (log->is(ty)) return false; // We only care about unions. - return !(FFlag::LuauUseCommittingTxnLog ? log->is(ty) : bool(get(ty))); + return !log->is(ty); } static std::optional hasUnificationTooComplex(const ErrorVec& errors) @@ -335,7 +325,6 @@ Unifier::Unifier(TypeArena* types, Mode mode, std::vector(superTy); - auto subFree = getMutable(subTy); - - if (FFlag::LuauUseCommittingTxnLog) - { - superFree = log.getMutable(superTy); - subFree = log.getMutable(subTy); - } + auto superFree = log.getMutable(superTy); + auto subFree = log.getMutable(subTy); if (superFree && subFree && superFree->level.subsumes(subFree->level)) { occursCheck(subTy, superTy); // The occurrence check might have caused superTy no longer to be a free type - bool occursFailed = false; - if (FFlag::LuauUseCommittingTxnLog) - occursFailed = bool(log.getMutable(subTy)); - else - occursFailed = bool(get(subTy)); + bool occursFailed = bool(log.getMutable(subTy)); if (!occursFailed) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.replace(subTy, BoundTypeVar(superTy)); - } - else - { - DEPRECATED_log(subTy); - *asMutable(subTy) = BoundTypeVar(superTy); - } + log.replace(subTy, BoundTypeVar(superTy)); } return; } else if (superFree && subFree) { - if (!FFlag::LuauErrorRecoveryType && !FFlag::LuauUseCommittingTxnLog) - { - DEPRECATED_log(superTy); - subFree->level = min(subFree->level, superFree->level); - } - occursCheck(superTy, subTy); - bool occursFailed = false; - if (FFlag::LuauUseCommittingTxnLog) - occursFailed = bool(log.getMutable(superTy)); - else - occursFailed = bool(get(superTy)); - - if (!FFlag::LuauErrorRecoveryType && !FFlag::LuauUseCommittingTxnLog) - { - *asMutable(superTy) = BoundTypeVar(subTy); - return; - } + bool occursFailed = bool(log.getMutable(superTy)); if (!occursFailed) { - if (FFlag::LuauUseCommittingTxnLog) + if (superFree->level.subsumes(subFree->level)) { - if (superFree->level.subsumes(subFree->level)) - { - log.changeLevel(subTy, superFree->level); - } + log.changeLevel(subTy, superFree->level); + } - log.replace(superTy, BoundTypeVar(subTy)); - } - else - { - DEPRECATED_log(superTy); - *asMutable(superTy) = BoundTypeVar(subTy); - subFree->level = min(subFree->level, superFree->level); - } + log.replace(superTy, BoundTypeVar(subTy)); } return; @@ -460,14 +398,10 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool TypeLevel superLevel = superFree->level; occursCheck(superTy, subTy); - bool occursFailed = false; - if (FFlag::LuauUseCommittingTxnLog) - occursFailed = bool(log.getMutable(superTy)); - else - occursFailed = bool(get(superTy)); + bool occursFailed = bool(log.getMutable(superTy)); // Unification can't change the level of a generic. - auto subGeneric = FFlag::LuauUseCommittingTxnLog ? log.getMutable(subTy) : get(subTy); + auto subGeneric = log.getMutable(subTy); if (subGeneric && !subGeneric->level.subsumes(superLevel)) { // TODO: a more informative error message? CLI-39912 @@ -478,18 +412,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool // The occurrence check might have caused superTy no longer to be a free type if (!occursFailed) { - if (FFlag::LuauUseCommittingTxnLog) - { - promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy); - log.replace(superTy, BoundTypeVar(widen(subTy))); - } - else - { - promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy); - - DEPRECATED_log(superTy); - *asMutable(superTy) = BoundTypeVar(widen(subTy)); - } + promoteTypeLevels(log, types, superLevel, subTy); + log.replace(superTy, BoundTypeVar(widen(subTy))); } return; @@ -499,14 +423,10 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool TypeLevel subLevel = subFree->level; occursCheck(subTy, superTy); - bool occursFailed = false; - if (FFlag::LuauUseCommittingTxnLog) - occursFailed = bool(log.getMutable(subTy)); - else - occursFailed = bool(get(subTy)); + bool occursFailed = bool(log.getMutable(subTy)); // Unification can't change the level of a generic. - auto superGeneric = FFlag::LuauUseCommittingTxnLog ? log.getMutable(superTy) : get(superTy); + auto superGeneric = log.getMutable(superTy); if (superGeneric && !superGeneric->level.subsumes(subFree->level)) { // TODO: a more informative error message? CLI-39912 @@ -516,18 +436,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool if (!occursFailed) { - if (FFlag::LuauUseCommittingTxnLog) - { - promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy); - log.replace(subTy, BoundTypeVar(superTy)); - } - else - { - promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy); - - DEPRECATED_log(subTy); - *asMutable(subTy) = BoundTypeVar(superTy); - } + promoteTypeLevels(log, types, subLevel, superTy); + log.replace(subTy, BoundTypeVar(superTy)); } return; @@ -550,55 +460,38 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool // Here, we assume that the types unify. If they do not, we will find out as we roll back // the stack. - if (FFlag::LuauUseCommittingTxnLog) - { - if (log.haveSeen(superTy, subTy)) - return; + if (log.haveSeen(superTy, subTy)) + return; - log.pushSeen(superTy, subTy); - } - else - { - if (DEPRECATED_log.haveSeen(superTy, subTy)) - return; + log.pushSeen(superTy, subTy); - DEPRECATED_log.pushSeen(superTy, subTy); - } - - if (const UnionTypeVar* uv = FFlag::LuauUseCommittingTxnLog ? log.getMutable(subTy) : get(subTy)) + if (const UnionTypeVar* uv = log.getMutable(subTy)) { tryUnifyUnionWithType(subTy, uv, superTy); } - else if (const UnionTypeVar* uv = FFlag::LuauUseCommittingTxnLog ? log.getMutable(superTy) : get(superTy)) + else if (const UnionTypeVar* uv = log.getMutable(superTy)) { tryUnifyTypeWithUnion(subTy, superTy, uv, cacheEnabled, isFunctionCall); } - else if (const IntersectionTypeVar* uv = - FFlag::LuauUseCommittingTxnLog ? log.getMutable(superTy) : get(superTy)) + else if (const IntersectionTypeVar* uv = log.getMutable(superTy)) { tryUnifyTypeWithIntersection(subTy, superTy, uv); } - else if (const IntersectionTypeVar* uv = - FFlag::LuauUseCommittingTxnLog ? log.getMutable(subTy) : get(subTy)) + else if (const IntersectionTypeVar* uv = log.getMutable(subTy)) { tryUnifyIntersectionWithType(subTy, uv, superTy, cacheEnabled, isFunctionCall); } - else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable(superTy) && log.getMutable(subTy)) || - (!FFlag::LuauUseCommittingTxnLog && get(superTy) && get(subTy))) + else if (log.getMutable(superTy) && log.getMutable(subTy)) tryUnifyPrimitives(subTy, superTy); - else if (FFlag::LuauSingletonTypes && - ((FFlag::LuauUseCommittingTxnLog ? log.getMutable(superTy) : get(superTy)) || - (FFlag::LuauUseCommittingTxnLog ? log.getMutable(superTy) : get(superTy))) && - (FFlag::LuauUseCommittingTxnLog ? log.getMutable(subTy) : get(subTy))) + else if (FFlag::LuauSingletonTypes && (log.getMutable(superTy) || log.getMutable(superTy)) && + log.getMutable(subTy)) tryUnifySingletons(subTy, superTy); - else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable(superTy) && log.getMutable(subTy)) || - (!FFlag::LuauUseCommittingTxnLog && get(superTy) && get(subTy))) + else if (log.getMutable(superTy) && log.getMutable(subTy)) tryUnifyFunctions(subTy, superTy, isFunctionCall); - else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable(superTy) && log.getMutable(subTy)) || - (!FFlag::LuauUseCommittingTxnLog && get(superTy) && get(subTy))) + else if (log.getMutable(superTy) && log.getMutable(subTy)) { tryUnifyTables(subTy, superTy, isIntersection); @@ -607,29 +500,23 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool } // tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical. - else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable(superTy)) || - (!FFlag::LuauUseCommittingTxnLog && get(superTy))) + else if (log.getMutable(superTy)) tryUnifyWithMetatable(subTy, superTy, /*reversed*/ false); - else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable(subTy)) || - (!FFlag::LuauUseCommittingTxnLog && get(subTy))) + else if (log.getMutable(subTy)) tryUnifyWithMetatable(superTy, subTy, /*reversed*/ true); - else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable(superTy)) || - (!FFlag::LuauUseCommittingTxnLog && get(superTy))) + else if (log.getMutable(superTy)) tryUnifyWithClass(subTy, superTy, /*reversed*/ false); // Unification of nonclasses with classes is almost, but not quite symmetrical. // The order in which we perform this test is significant in the case that both types are classes. - else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable(subTy)) || (!FFlag::LuauUseCommittingTxnLog && get(subTy))) + else if (log.getMutable(subTy)) tryUnifyWithClass(subTy, superTy, /*reversed*/ true); else reportError(TypeError{location, TypeMismatch{superTy, subTy}}); - if (FFlag::LuauUseCommittingTxnLog) - log.popSeen(superTy, subTy); - else - DEPRECATED_log.popSeen(superTy, subTy); + log.popSeen(superTy, subTy); } void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId superTy) @@ -660,28 +547,12 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter) { - if (!FFlag::LuauUseCommittingTxnLog) - innerState.DEPRECATED_log.rollback(); } else { - if (FFlag::LuauUseCommittingTxnLog) + if (i == count - 1) { - if (i == count - 1) - { - log.concat(std::move(innerState.log)); - } - } - else - { - if (i != count - 1) - { - innerState.DEPRECATED_log.rollback(); - } - else - { - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - } + log.concat(std::move(innerState.log)); } ++i; @@ -692,7 +563,7 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter) { auto tryBind = [this, subTy](TypeId superOption) { - superOption = FFlag::LuauUseCommittingTxnLog ? log.follow(superOption) : follow(superOption); + superOption = log.follow(superOption); // just skip if the superOption is not free-ish. auto ttv = log.getMutable(superOption); @@ -701,36 +572,17 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId // Since we have already checked if S <: T, checking it again will not queue up the type for replacement. // So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set. - if (FFlag::LuauUseCommittingTxnLog) + if (log.haveSeen(subTy, superOption)) { - if (log.haveSeen(subTy, superOption)) - { - // TODO: would it be nice for TxnLog::replace to do this? - if (log.is(superOption)) - log.bindTable(superOption, subTy); - else - log.replace(superOption, *subTy); - } - } - else - { - if (DEPRECATED_log.haveSeen(subTy, superOption)) - { - if (auto ttv = getMutable(superOption)) - { - DEPRECATED_log(ttv); - ttv->boundTo = subTy; - } - else - { - DEPRECATED_log(superOption); - *asMutable(superOption) = BoundTypeVar(subTy); - } - } + // TODO: would it be nice for TxnLog::replace to do this? + if (log.is(superOption)) + log.bindTable(superOption, subTy); + else + log.replace(superOption, *subTy); } }; - if (auto utv = (FFlag::LuauUseCommittingTxnLog ? log.getMutable(superTy) : get(superTy))) + if (auto utv = log.getMutable(superTy)) { for (TypeId ty : utv) tryBind(ty); @@ -815,10 +667,7 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp if (innerState.errors.empty()) { found = true; - if (FFlag::LuauUseCommittingTxnLog) - log.concat(std::move(innerState.log)); - else - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); + log.concat(std::move(innerState.log)); break; } @@ -833,9 +682,6 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp if (!failedOption) failedOption = {innerState.errors.front()}; } - - if (!FFlag::LuauUseCommittingTxnLog) - innerState.DEPRECATED_log.rollback(); } if (unificationTooComplex) @@ -870,10 +716,7 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I firstFailedOption = {innerState.errors.front()}; } - if (FFlag::LuauUseCommittingTxnLog) - log.concat(std::move(innerState.log)); - else - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); + log.concat(std::move(innerState.log)); } if (unificationTooComplex) @@ -915,19 +758,13 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV if (innerState.errors.empty()) { found = true; - if (FFlag::LuauUseCommittingTxnLog) - log.concat(std::move(innerState.log)); - else - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); + log.concat(std::move(innerState.log)); break; } else if (auto e = hasUnificationTooComplex(innerState.errors)) { unificationTooComplex = e; } - - if (!FFlag::LuauUseCommittingTxnLog) - innerState.DEPRECATED_log.rollback(); } if (unificationTooComplex) @@ -971,78 +808,6 @@ void Unifier::cacheResult(TypeId subTy, TypeId superTy) sharedState.cachedUnify.insert({subTy, superTy}); } -struct DEPRECATED_WeirdIter -{ - TypePackId packId; - const TypePack* pack; - size_t index; - bool growing; - TypeLevel level; - - DEPRECATED_WeirdIter(TypePackId packId) - : packId(packId) - , pack(get(packId)) - , index(0) - , growing(false) - { - while (pack && pack->head.empty() && pack->tail) - { - packId = *pack->tail; - pack = get(packId); - } - } - - DEPRECATED_WeirdIter(const DEPRECATED_WeirdIter&) = default; - - const TypeId& operator*() - { - LUAU_ASSERT(good()); - return pack->head[index]; - } - - bool good() const - { - return pack != nullptr && index < pack->head.size(); - } - - bool advance() - { - if (!pack) - return good(); - - if (index < pack->head.size()) - ++index; - - if (growing || index < pack->head.size()) - return good(); - - if (pack->tail) - { - packId = follow(*pack->tail); - pack = get(packId); - index = 0; - } - - return good(); - } - - bool canGrow() const - { - return nullptr != get(packId); - } - - void grow(TypePackId newTail) - { - LUAU_ASSERT(canGrow()); - level = get(packId)->level; - *asMutable(packId) = Unifiable::Bound(newTail); - packId = newTail; - pack = get(newTail); - index = 0; - growing = true; - } -}; - struct WeirdIter { TypePackId packId; @@ -1141,9 +906,6 @@ ErrorVec Unifier::canUnify(TypeId subTy, TypeId superTy) Unifier s = makeChildUnifier(); s.tryUnify_(subTy, superTy); - if (!FFlag::LuauUseCommittingTxnLog) - s.DEPRECATED_log.rollback(); - return s.errors; } @@ -1152,9 +914,6 @@ ErrorVec Unifier::canUnify(TypePackId subTy, TypePackId superTy, bool isFunction Unifier s = makeChildUnifier(); s.tryUnify_(subTy, superTy, isFunctionCall); - if (!FFlag::LuauUseCommittingTxnLog) - s.DEPRECATED_log.rollback(); - return s.errors; } @@ -1200,419 +959,207 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal return; } - if (FFlag::LuauUseCommittingTxnLog) + superTp = log.follow(superTp); + subTp = log.follow(subTp); + + while (auto tp = log.getMutable(subTp)) { - superTp = log.follow(superTp); - subTp = log.follow(subTp); + if (tp->head.empty() && tp->tail) + subTp = log.follow(*tp->tail); + else + break; + } - while (auto tp = log.getMutable(subTp)) + while (auto tp = log.getMutable(superTp)) + { + if (tp->head.empty() && tp->tail) + superTp = log.follow(*tp->tail); + else + break; + } + + if (superTp == subTp) + return; + + if (FFlag::LuauTxnLogSeesTypePacks2 && log.haveSeen(superTp, subTp)) + return; + + if (log.getMutable(superTp)) + { + occursCheck(superTp, subTp); + + if (!log.getMutable(superTp)) { - if (tp->head.empty() && tp->tail) - subTp = log.follow(*tp->tail); - else - break; + log.replace(superTp, Unifiable::Bound(subTp)); } + } + else if (log.getMutable(subTp)) + { + occursCheck(subTp, superTp); - while (auto tp = log.getMutable(superTp)) + if (!log.getMutable(subTp)) { - if (tp->head.empty() && tp->tail) - superTp = log.follow(*tp->tail); - else - break; + log.replace(subTp, Unifiable::Bound(superTp)); } + } + else if (log.getMutable(superTp)) + tryUnifyWithAny(subTp, superTp); + else if (log.getMutable(subTp)) + tryUnifyWithAny(superTp, subTp); + else if (log.getMutable(superTp)) + tryUnifyVariadics(subTp, superTp, false); + else if (log.getMutable(subTp)) + tryUnifyVariadics(superTp, subTp, true); + else if (log.getMutable(superTp) && log.getMutable(subTp)) + { + auto superTpv = log.getMutable(superTp); + auto subTpv = log.getMutable(subTp); - if (superTp == subTp) - return; + // If the size of two heads does not match, but both packs have free tail + // We set the sentinel variable to say so to avoid growing it forever. + auto [superTypes, superTail] = logAwareFlatten(superTp, log); + auto [subTypes, subTail] = logAwareFlatten(subTp, log); - if (FFlag::LuauTxnLogSeesTypePacks2 && log.haveSeen(superTp, subTp)) - return; + bool noInfiniteGrowth = (superTypes.size() != subTypes.size()) && (superTail && log.getMutable(*superTail)) && + (subTail && log.getMutable(*subTail)); - if (log.getMutable(superTp)) + auto superIter = WeirdIter(superTp, log); + auto subIter = WeirdIter(subTp, log); + + auto mkFreshType = [this](TypeLevel level) { + return types->freshType(level); + }; + + const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt}); + + int loopCount = 0; + + do { - occursCheck(superTp, subTp); + if (FInt::LuauTypeInferTypePackLoopLimit > 0 && loopCount >= FInt::LuauTypeInferTypePackLoopLimit) + ice("Detected possibly infinite TypePack growth"); - if (!log.getMutable(superTp)) + ++loopCount; + + if (superIter.good() && subIter.growing) { - log.replace(superTp, Unifiable::Bound(subTp)); + subIter.pushType(mkFreshType(subIter.level)); } - } - else if (log.getMutable(subTp)) - { - occursCheck(subTp, superTp); - if (!log.getMutable(subTp)) + if (subIter.good() && superIter.growing) { - log.replace(subTp, Unifiable::Bound(superTp)); + superIter.pushType(mkFreshType(superIter.level)); } - } - else if (log.getMutable(superTp)) - tryUnifyWithAny(subTp, superTp); - else if (log.getMutable(subTp)) - tryUnifyWithAny(superTp, subTp); - else if (log.getMutable(superTp)) - tryUnifyVariadics(subTp, superTp, false); - else if (log.getMutable(subTp)) - tryUnifyVariadics(superTp, subTp, true); - else if (log.getMutable(superTp) && log.getMutable(subTp)) - { - auto superTpv = log.getMutable(superTp); - auto subTpv = log.getMutable(subTp); - // If the size of two heads does not match, but both packs have free tail - // We set the sentinel variable to say so to avoid growing it forever. - auto [superTypes, superTail] = logAwareFlatten(superTp, log); - auto [subTypes, subTail] = logAwareFlatten(subTp, log); - - bool noInfiniteGrowth = (superTypes.size() != subTypes.size()) && (superTail && log.getMutable(*superTail)) && - (subTail && log.getMutable(*subTail)); - - auto superIter = WeirdIter(superTp, log); - auto subIter = WeirdIter(subTp, log); - - auto mkFreshType = [this](TypeLevel level) { - return types->freshType(level); - }; - - const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt}); - - int loopCount = 0; - - do + if (superIter.good() && subIter.good()) { - if (FInt::LuauTypeInferTypePackLoopLimit > 0 && loopCount >= FInt::LuauTypeInferTypePackLoopLimit) - ice("Detected possibly infinite TypePack growth"); + tryUnify_(*subIter, *superIter); - ++loopCount; + if (!errors.empty() && !firstPackErrorPos) + firstPackErrorPos = loopCount; - if (superIter.good() && subIter.growing) + superIter.advance(); + subIter.advance(); + continue; + } + + // If both are at the end, we're done + if (!superIter.good() && !subIter.good()) + { + if (subTpv->tail && superTpv->tail) { - subIter.pushType(mkFreshType(subIter.level)); + tryUnify_(*subTpv->tail, *superTpv->tail); + break; } - if (subIter.good() && superIter.growing) + const bool lFreeTail = superTpv->tail && log.getMutable(log.follow(*superTpv->tail)) != nullptr; + const bool rFreeTail = subTpv->tail && log.getMutable(log.follow(*subTpv->tail)) != nullptr; + if (lFreeTail) + tryUnify_(emptyTp, *superTpv->tail); + else if (rFreeTail) + tryUnify_(emptyTp, *subTpv->tail); + + break; + } + + // If both tails are free, bind one to the other and call it a day + if (superIter.canGrow() && subIter.canGrow()) + return tryUnify_(*subIter.pack->tail, *superIter.pack->tail); + + // If just one side is free on its tail, grow it to fit the other side. + // FIXME: The tail-most tail of the growing pack should be the same as the tail-most tail of the non-growing pack. + if (superIter.canGrow()) + superIter.grow(types->addTypePack(TypePackVar(TypePack{}))); + else if (subIter.canGrow()) + subIter.grow(types->addTypePack(TypePackVar(TypePack{}))); + else + { + // A union type including nil marks an optional argument + if (superIter.good() && isOptional(*superIter)) { - superIter.pushType(mkFreshType(superIter.level)); - } - - if (superIter.good() && subIter.good()) - { - tryUnify_(*subIter, *superIter); - - if (!errors.empty() && !firstPackErrorPos) - firstPackErrorPos = loopCount; - superIter.advance(); + continue; + } + else if (subIter.good() && isOptional(*subIter)) + { subIter.advance(); continue; } - // If both are at the end, we're done - if (!superIter.good() && !subIter.good()) + // In nonstrict mode, any also marks an optional argument. + else if (superIter.good() && isNonstrictMode() && log.getMutable(log.follow(*superIter))) { - if (subTpv->tail && superTpv->tail) - { - tryUnify_(*subTpv->tail, *superTpv->tail); - break; - } - - const bool lFreeTail = superTpv->tail && log.getMutable(log.follow(*superTpv->tail)) != nullptr; - const bool rFreeTail = subTpv->tail && log.getMutable(log.follow(*subTpv->tail)) != nullptr; - if (lFreeTail) - tryUnify_(emptyTp, *superTpv->tail); - else if (rFreeTail) - tryUnify_(emptyTp, *subTpv->tail); - - break; + superIter.advance(); + continue; } - // If both tails are free, bind one to the other and call it a day - if (superIter.canGrow() && subIter.canGrow()) - return tryUnify_(*subIter.pack->tail, *superIter.pack->tail); - - // If just one side is free on its tail, grow it to fit the other side. - // FIXME: The tail-most tail of the growing pack should be the same as the tail-most tail of the non-growing pack. - if (superIter.canGrow()) - superIter.grow(types->addTypePack(TypePackVar(TypePack{}))); - else if (subIter.canGrow()) - subIter.grow(types->addTypePack(TypePackVar(TypePack{}))); - else + if (log.getMutable(superIter.packId)) { - // A union type including nil marks an optional argument - if (superIter.good() && isOptional(*superIter)) - { - superIter.advance(); - continue; - } - else if (subIter.good() && isOptional(*subIter)) - { - subIter.advance(); - continue; - } - - // In nonstrict mode, any also marks an optional argument. - else if (superIter.good() && isNonstrictMode() && log.getMutable(log.follow(*superIter))) - { - superIter.advance(); - continue; - } - - if (log.getMutable(superIter.packId)) - { - tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index)); - return; - } - - if (log.getMutable(subIter.packId)) - { - tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index)); - return; - } - - if (!isFunctionCall && subIter.good()) - { - // Sometimes it is ok to pass too many arguments - return; - } - - // This is a bit weird because we don't actually know expected vs actual. We just know - // subtype vs supertype. If we are checking the values returned by a function, we swap - // these to produce the expected error message. - size_t expectedSize = size(superTp); - size_t actualSize = size(subTp); - if (ctx == CountMismatch::Result) - std::swap(expectedSize, actualSize); - reportError(TypeError{location, CountMismatch{expectedSize, actualSize, ctx}}); - - while (superIter.good()) - { - tryUnify_(*superIter, getSingletonTypes().errorRecoveryType()); - superIter.advance(); - } - - while (subIter.good()) - { - tryUnify_(*subIter, getSingletonTypes().errorRecoveryType()); - subIter.advance(); - } - + tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index)); return; } - } while (!noInfiniteGrowth); - } - else - { - reportError(TypeError{location, GenericError{"Failed to unify type packs"}}); - } + if (log.getMutable(subIter.packId)) + { + tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index)); + return; + } + + if (!isFunctionCall && subIter.good()) + { + // Sometimes it is ok to pass too many arguments + return; + } + + // This is a bit weird because we don't actually know expected vs actual. We just know + // subtype vs supertype. If we are checking the values returned by a function, we swap + // these to produce the expected error message. + size_t expectedSize = size(superTp); + size_t actualSize = size(subTp); + if (ctx == CountMismatch::Result) + std::swap(expectedSize, actualSize); + reportError(TypeError{location, CountMismatch{expectedSize, actualSize, ctx}}); + + while (superIter.good()) + { + tryUnify_(*superIter, getSingletonTypes().errorRecoveryType()); + superIter.advance(); + } + + while (subIter.good()) + { + tryUnify_(*subIter, getSingletonTypes().errorRecoveryType()); + subIter.advance(); + } + + return; + } + + } while (!noInfiniteGrowth); } else { - superTp = follow(superTp); - subTp = follow(subTp); - - while (auto tp = get(subTp)) - { - if (tp->head.empty() && tp->tail) - subTp = follow(*tp->tail); - else - break; - } - - while (auto tp = get(superTp)) - { - if (tp->head.empty() && tp->tail) - superTp = follow(*tp->tail); - else - break; - } - - if (superTp == subTp) - return; - - if (FFlag::LuauTxnLogSeesTypePacks2 && DEPRECATED_log.haveSeen(superTp, subTp)) - return; - - if (get(superTp)) - { - occursCheck(superTp, subTp); - - if (!get(superTp)) - { - DEPRECATED_log(superTp); - *asMutable(superTp) = Unifiable::Bound(subTp); - } - } - else if (get(subTp)) - { - occursCheck(subTp, superTp); - - if (!get(subTp)) - { - DEPRECATED_log(subTp); - *asMutable(subTp) = Unifiable::Bound(superTp); - } - } - - else if (get(superTp)) - tryUnifyWithAny(subTp, superTp); - - else if (get(subTp)) - tryUnifyWithAny(superTp, subTp); - - else if (get(superTp)) - tryUnifyVariadics(subTp, superTp, false); - else if (get(subTp)) - tryUnifyVariadics(superTp, subTp, true); - - else if (get(superTp) && get(subTp)) - { - auto superTpv = get(superTp); - auto subTpv = get(subTp); - - // If the size of two heads does not match, but both packs have free tail - // We set the sentinel variable to say so to avoid growing it forever. - auto [superTypes, superTail] = flatten(superTp); - auto [subTypes, subTail] = flatten(subTp); - - bool noInfiniteGrowth = - (superTypes.size() != subTypes.size()) && (superTail && get(*superTail)) && (subTail && get(*subTail)); - - auto superIter = DEPRECATED_WeirdIter{superTp}; - auto subIter = DEPRECATED_WeirdIter{subTp}; - - auto mkFreshType = [this](TypeLevel level) { - return types->freshType(level); - }; - - const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt}); - - int loopCount = 0; - - do - { - if (FInt::LuauTypeInferTypePackLoopLimit > 0 && loopCount >= FInt::LuauTypeInferTypePackLoopLimit) - ice("Detected possibly infinite TypePack growth"); - - ++loopCount; - - if (superIter.good() && subIter.growing) - asMutable(subIter.pack)->head.push_back(mkFreshType(subIter.level)); - - if (subIter.good() && superIter.growing) - asMutable(superIter.pack)->head.push_back(mkFreshType(superIter.level)); - - if (superIter.good() && subIter.good()) - { - tryUnify_(*subIter, *superIter); - - if (!errors.empty() && !firstPackErrorPos) - firstPackErrorPos = loopCount; - - superIter.advance(); - subIter.advance(); - continue; - } - - // If both are at the end, we're done - if (!superIter.good() && !subIter.good()) - { - if (subTpv->tail && superTpv->tail) - { - tryUnify_(*subTpv->tail, *superTpv->tail); - break; - } - - const bool lFreeTail = superTpv->tail && get(follow(*superTpv->tail)) != nullptr; - const bool rFreeTail = subTpv->tail && get(follow(*subTpv->tail)) != nullptr; - if (lFreeTail) - tryUnify_(emptyTp, *superTpv->tail); - else if (rFreeTail) - tryUnify_(emptyTp, *subTpv->tail); - - break; - } - - // If both tails are free, bind one to the other and call it a day - if (superIter.canGrow() && subIter.canGrow()) - return tryUnify_(*subIter.pack->tail, *superIter.pack->tail); - - // If just one side is free on its tail, grow it to fit the other side. - // FIXME: The tail-most tail of the growing pack should be the same as the tail-most tail of the non-growing pack. - if (superIter.canGrow()) - superIter.grow(types->addTypePack(TypePackVar(TypePack{}))); - - else if (subIter.canGrow()) - subIter.grow(types->addTypePack(TypePackVar(TypePack{}))); - - else - { - // A union type including nil marks an optional argument - if (superIter.good() && isOptional(*superIter)) - { - superIter.advance(); - continue; - } - else if (subIter.good() && isOptional(*subIter)) - { - subIter.advance(); - continue; - } - - // In nonstrict mode, any also marks an optional argument. - else if (superIter.good() && isNonstrictMode() && get(follow(*superIter))) - { - superIter.advance(); - continue; - } - - if (get(superIter.packId)) - { - tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index)); - return; - } - - if (get(subIter.packId)) - { - tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index)); - return; - } - - if (!isFunctionCall && subIter.good()) - { - // Sometimes it is ok to pass too many arguments - return; - } - - // This is a bit weird because we don't actually know expected vs actual. We just know - // subtype vs supertype. If we are checking the values returned by a function, we swap - // these to produce the expected error message. - size_t expectedSize = size(superTp); - size_t actualSize = size(subTp); - if (ctx == CountMismatch::Result) - std::swap(expectedSize, actualSize); - reportError(TypeError{location, CountMismatch{expectedSize, actualSize, ctx}}); - - while (superIter.good()) - { - tryUnify_(*superIter, getSingletonTypes().errorRecoveryType()); - superIter.advance(); - } - - while (subIter.good()) - { - tryUnify_(*subIter, getSingletonTypes().errorRecoveryType()); - subIter.advance(); - } - - return; - } - - } while (!noInfiniteGrowth); - } - else - { - reportError(TypeError{location, GenericError{"Failed to unify type packs"}}); - } + reportError(TypeError{location, GenericError{"Failed to unify type packs"}}); } } @@ -1650,14 +1197,8 @@ void Unifier::tryUnifySingletons(TypeId subTy, TypeId superTy) void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall) { - FunctionTypeVar* superFunction = getMutable(superTy); - FunctionTypeVar* subFunction = getMutable(subTy); - - if (FFlag::LuauUseCommittingTxnLog) - { - superFunction = log.getMutable(superTy); - subFunction = log.getMutable(subTy); - } + FunctionTypeVar* superFunction = log.getMutable(superTy); + FunctionTypeVar* subFunction = log.getMutable(subTy); if (!superFunction || !subFunction) ice("passed non-function types to unifyFunction"); @@ -1680,20 +1221,14 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal for (size_t i = 0; i < numGenerics; i++) { - if (FFlag::LuauUseCommittingTxnLog) - log.pushSeen(superFunction->generics[i], subFunction->generics[i]); - else - DEPRECATED_log.pushSeen(superFunction->generics[i], subFunction->generics[i]); + log.pushSeen(superFunction->generics[i], subFunction->generics[i]); } if (FFlag::LuauTxnLogSeesTypePacks2) { for (size_t i = 0; i < numGenericPacks; i++) { - if (FFlag::LuauUseCommittingTxnLog) - log.pushSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); - else - DEPRECATED_log.pushSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); + log.pushSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); } } @@ -1734,14 +1269,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}}); } - if (FFlag::LuauUseCommittingTxnLog) - { - log.concat(std::move(innerState.log)); - } - else - { - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - } + log.concat(std::move(innerState.log)); } else { @@ -1754,33 +1282,19 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal if (!FFlag::LuauImmutableTypes) { - if (FFlag::LuauUseCommittingTxnLog) + if (superFunction->definition && !subFunction->definition && !subTy->persistent) { - if (superFunction->definition && !subFunction->definition && !subTy->persistent) - { - PendingType* newSubTy = log.queue(subTy); - FunctionTypeVar* newSubFtv = getMutable(newSubTy); - LUAU_ASSERT(newSubFtv); - newSubFtv->definition = superFunction->definition; - } - else if (!superFunction->definition && subFunction->definition && !superTy->persistent) - { - PendingType* newSuperTy = log.queue(superTy); - FunctionTypeVar* newSuperFtv = getMutable(newSuperTy); - LUAU_ASSERT(newSuperFtv); - newSuperFtv->definition = subFunction->definition; - } + PendingType* newSubTy = log.queue(subTy); + FunctionTypeVar* newSubFtv = getMutable(newSubTy); + LUAU_ASSERT(newSubFtv); + newSubFtv->definition = superFunction->definition; } - else + else if (!superFunction->definition && subFunction->definition && !superTy->persistent) { - if (superFunction->definition && !subFunction->definition && !subTy->persistent) - { - subFunction->definition = superFunction->definition; - } - else if (!superFunction->definition && subFunction->definition && !superTy->persistent) - { - superFunction->definition = subFunction->definition; - } + PendingType* newSuperTy = log.queue(superTy); + FunctionTypeVar* newSuperFtv = getMutable(newSuperTy); + LUAU_ASSERT(newSuperFtv); + newSuperFtv->definition = subFunction->definition; } } @@ -1790,19 +1304,13 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal { for (int i = int(numGenericPacks) - 1; 0 <= i; i--) { - if (FFlag::LuauUseCommittingTxnLog) - log.popSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); - else - DEPRECATED_log.popSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); + log.popSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); } } for (int i = int(numGenerics) - 1; 0 <= i; i--) { - if (FFlag::LuauUseCommittingTxnLog) - log.popSeen(superFunction->generics[i], subFunction->generics[i]); - else - DEPRECATED_log.popSeen(superFunction->generics[i], subFunction->generics[i]); + log.popSeen(superFunction->generics[i], subFunction->generics[i]); } } @@ -1833,14 +1341,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) if (!FFlag::LuauTableSubtypingVariance2) return DEPRECATED_tryUnifyTables(subTy, superTy, isIntersection); - TableTypeVar* superTable = getMutable(superTy); - TableTypeVar* subTable = getMutable(subTy); - - if (FFlag::LuauUseCommittingTxnLog) - { - superTable = log.getMutable(superTy); - subTable = log.getMutable(subTy); - } + TableTypeVar* superTable = log.getMutable(superTy); + TableTypeVar* subTable = log.getMutable(subTy); if (!superTable || !subTable) ice("passed non-table types to unifyTables"); @@ -1855,8 +1357,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) { auto subIter = subTable->props.find(propName); - bool isAny = - FFlag::LuauUseCommittingTxnLog ? log.getMutable(log.follow(superProp.type)) : get(follow(superProp.type)); + bool isAny = log.getMutable(log.follow(superProp.type)); if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) && !isAny) missingProperties.push_back(propName); @@ -1877,8 +1378,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) { auto superIter = superTable->props.find(propName); - bool isAny = - FFlag::LuauUseCommittingTxnLog ? log.getMutable(log.follow(subProp.type)) : get(follow(subProp.type)); + bool isAny = log.is(log.follow(subProp.type)); if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny))) extraProperties.push_back(propName); } @@ -1906,18 +1406,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) checkChildUnifierTypeMismatch(innerState.errors, name, superTy, subTy); - if (FFlag::LuauUseCommittingTxnLog) - { - if (innerState.errors.empty()) - log.concat(std::move(innerState.log)); - } - else - { - if (innerState.errors.empty()) - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - else - innerState.DEPRECATED_log.rollback(); - } + if (innerState.errors.empty()) + log.concat(std::move(innerState.log)); } else if (subTable->indexer && maybeString(subTable->indexer->indexType)) { @@ -1931,18 +1421,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) checkChildUnifierTypeMismatch(innerState.errors, name, superTy, subTy); - if (FFlag::LuauUseCommittingTxnLog) - { - if (innerState.errors.empty()) - log.concat(std::move(innerState.log)); - } - else - { - if (innerState.errors.empty()) - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - else - innerState.DEPRECATED_log.rollback(); - } + if (innerState.errors.empty()) + log.concat(std::move(innerState.log)); } else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && (isOptional(prop.type) || get(follow(prop.type)))) // This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }` @@ -1953,22 +1433,30 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) } else if (subTable->state == TableState::Free) { - if (FFlag::LuauUseCommittingTxnLog) - { - PendingType* pendingSub = log.queue(subTy); - TableTypeVar* ttv = getMutable(pendingSub); - LUAU_ASSERT(ttv); - ttv->props[name] = prop; - subTable = ttv; - } - else - { - DEPRECATED_log(subTy); - subTable->props[name] = prop; - } + PendingType* pendingSub = log.queue(subTy); + TableTypeVar* ttv = getMutable(pendingSub); + LUAU_ASSERT(ttv); + ttv->props[name] = prop; + subTable = ttv; } else missingProperties.push_back(name); + + if (FFlag::LuauTxnLogCheckForInvalidation) + { + // Recursive unification can change the txn log, and invalidate the old + // table. If we detect that this has happened, we start over, with the updated + // txn log. + TableTypeVar* newSuperTable = log.getMutable(superTy); + TableTypeVar* newSubTable = log.getMutable(subTy); + if (superTable != newSuperTable || subTable != newSubTable) + { + if (errors.empty()) + return tryUnifyTables(subTy, superTy, isIntersection); + else + return; + } + } } for (const auto& [name, prop] : subTable->props) @@ -1990,18 +1478,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) checkChildUnifierTypeMismatch(innerState.errors, name, superTy, subTy); - if (FFlag::LuauUseCommittingTxnLog) - { - if (innerState.errors.empty()) - log.concat(std::move(innerState.log)); - } - else - { - if (innerState.errors.empty()) - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - else - innerState.DEPRECATED_log.rollback(); - } + if (innerState.errors.empty()) + log.concat(std::move(innerState.log)); } else if (superTable->state == TableState::Unsealed) { @@ -2011,18 +1489,10 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) Property clone = prop; clone.type = deeplyOptional(clone.type); - if (FFlag::LuauUseCommittingTxnLog) - { - PendingType* pendingSuper = log.queue(superTy); - TableTypeVar* pendingSuperTtv = getMutable(pendingSuper); - pendingSuperTtv->props[name] = clone; - superTable = pendingSuperTtv; - } - else - { - DEPRECATED_log(superTy); - superTable->props[name] = clone; - } + PendingType* pendingSuper = log.queue(superTy); + TableTypeVar* pendingSuperTtv = getMutable(pendingSuper); + pendingSuperTtv->props[name] = clone; + superTable = pendingSuperTtv; } else if (variance == Covariant) { @@ -2032,21 +1502,29 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) } else if (superTable->state == TableState::Free) { - if (FFlag::LuauUseCommittingTxnLog) - { - PendingType* pendingSuper = log.queue(superTy); - TableTypeVar* pendingSuperTtv = getMutable(pendingSuper); - pendingSuperTtv->props[name] = prop; - superTable = pendingSuperTtv; - } - else - { - DEPRECATED_log(superTy); - superTable->props[name] = prop; - } + PendingType* pendingSuper = log.queue(superTy); + TableTypeVar* pendingSuperTtv = getMutable(pendingSuper); + pendingSuperTtv->props[name] = prop; + superTable = pendingSuperTtv; } else extraProperties.push_back(name); + + if (FFlag::LuauTxnLogCheckForInvalidation) + { + // Recursive unification can change the txn log, and invalidate the old + // table. If we detect that this has happened, we start over, with the updated + // txn log. + TableTypeVar* newSuperTable = log.getMutable(superTy); + TableTypeVar* newSubTable = log.getMutable(subTy); + if (superTable != newSuperTable || subTable != newSubTable) + { + if (errors.empty()) + return tryUnifyTables(subTy, superTy, isIntersection); + else + return; + } + } } // Unify indexers @@ -2060,18 +1538,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer); checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy); - if (FFlag::LuauUseCommittingTxnLog) - { - if (innerState.errors.empty()) - log.concat(std::move(innerState.log)); - } - else - { - if (innerState.errors.empty()) - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - else - innerState.DEPRECATED_log.rollback(); - } + if (innerState.errors.empty()) + log.concat(std::move(innerState.log)); } else if (superTable->indexer) { @@ -2081,15 +1549,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, 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. - if (FFlag::LuauUseCommittingTxnLog) - { - log.changeIndexer(subTy, superTable->indexer); - } - else - { - DEPRECATED_log(subTy); - subTable->indexer = superTable->indexer; - } + log.changeIndexer(subTy, superTable->indexer); } } else if (subTable->indexer && variance == Invariant) @@ -2097,15 +1557,29 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) // Symmetric if we are invariant if (superTable->state == TableState::Unsealed || superTable->state == TableState::Free) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.changeIndexer(superTy, subTable->indexer); - } + log.changeIndexer(superTy, subTable->indexer); + } + } + + if (FFlag::LuauTxnLogDontRetryForIndexers) + { + // Changing the indexer can invalidate the table pointers. + superTable = log.getMutable(superTy); + subTable = log.getMutable(subTy); + } + else if (FFlag::LuauTxnLogCheckForInvalidation) + { + // Recursive unification can change the txn log, and invalidate the old + // table. If we detect that this has happened, we start over, with the updated + // txn log. + TableTypeVar* newSuperTable = log.getMutable(superTy); + TableTypeVar* newSubTable = log.getMutable(subTy); + if (superTable != newSuperTable || subTable != newSubTable) + { + if (errors.empty()) + return tryUnifyTables(subTy, superTy, isIntersection); else - { - DEPRECATED_log(superTy); - superTable->indexer = subTable->indexer; - } + return; } } @@ -2134,27 +1608,11 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) if (superTable->state == TableState::Free) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.bindTable(superTy, subTy); - } - else - { - DEPRECATED_log(superTable); - superTable->boundTo = subTy; - } + log.bindTable(superTy, subTy); } else if (subTable->state == TableState::Free) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.bindTable(subTy, superTy); - } - else - { - DEPRECATED_log(subTable); - subTable->boundTo = superTy; - } + log.bindTable(subTy, superTy); } } @@ -2197,14 +1655,8 @@ void Unifier::DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isInt Resetter resetter{&variance}; variance = Invariant; - TableTypeVar* superTable = getMutable(superTy); - TableTypeVar* subTable = getMutable(subTy); - - if (FFlag::LuauUseCommittingTxnLog) - { - superTable = log.getMutable(superTy); - subTable = log.getMutable(subTy); - } + TableTypeVar* superTable = log.getMutable(superTy); + TableTypeVar* subTable = log.getMutable(subTy); if (!superTable || !subTable) ice("passed non-table types to unifyTables"); @@ -2231,15 +1683,7 @@ void Unifier::DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isInt // avoid creating a cycle when the types are already pointing at each other if (follow(superTy) != follow(subTy)) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.bindTable(superTy, subTy); - } - else - { - DEPRECATED_log(superTable); - superTable->boundTo = subTy; - } + log.bindTable(superTy, subTy); } return; } @@ -2268,14 +1712,7 @@ void Unifier::DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isInt // e.g. table.insert(t, 1) where t is a non-sealed table and doesn't have an indexer. if (subTable->state == TableState::Unsealed) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.changeIndexer(subTy, superTable->indexer); - } - else - { - subTable->indexer = superTable->indexer; - } + log.changeIndexer(subTy, superTable->indexer); } else reportError(TypeError{location, CannotExtendTable{subTy, CannotExtendTable::Indexer}}); @@ -2295,14 +1732,8 @@ void Unifier::DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isInt void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy) { - TableTypeVar* freeTable = getMutable(superTy); - TableTypeVar* subTable = getMutable(subTy); - - if (FFlag::LuauUseCommittingTxnLog) - { - freeTable = log.getMutable(superTy); - subTable = log.getMutable(subTy); - } + TableTypeVar* freeTable = log.getMutable(superTy); + TableTypeVar* subTable = log.getMutable(subTy); if (!freeTable || !subTable) ice("passed non-table types to tryUnifyFreeTable"); @@ -2323,22 +1754,11 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy) * I believe this is guaranteed to terminate eventually because this will * only happen when a free table is bound to another table. */ - if (FFlag::LuauUseCommittingTxnLog) - { - if (!log.getMutable(superTy) || !log.getMutable(subTy)) - return tryUnify_(subTy, superTy); + if (!log.getMutable(superTy) || !log.getMutable(subTy)) + return tryUnify_(subTy, superTy); - if (TableTypeVar* pendingFreeTtv = log.getMutable(superTy); pendingFreeTtv && pendingFreeTtv->boundTo) - return tryUnify_(subTy, superTy); - } - else - { - if (!get(superTy) || !get(subTy)) - return tryUnify_(subTy, superTy); - - if (freeTable->boundTo) - return tryUnify_(subTy, superTy); - } + if (TableTypeVar* pendingFreeTtv = log.getMutable(superTy); pendingFreeTtv && pendingFreeTtv->boundTo) + return tryUnify_(subTy, superTy); } else { @@ -2346,17 +1766,10 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy) // properties than we previously thought. Else, it is an error. if (subTable->state == TableState::Free) { - if (FFlag::LuauUseCommittingTxnLog) - { - PendingType* pendingSub = log.queue(subTy); - TableTypeVar* pendingSubTtv = getMutable(pendingSub); - LUAU_ASSERT(pendingSubTtv); - pendingSubTtv->props.insert({freeName, freeProp}); - } - else - { - subTable->props.insert({freeName, freeProp}); - } + PendingType* pendingSub = log.queue(subTy); + TableTypeVar* pendingSubTtv = getMutable(pendingSub); + LUAU_ASSERT(pendingSubTtv); + pendingSubTtv->props.insert({freeName, freeProp}); } else reportError(TypeError{location, UnknownProperty{subTy, freeName}}); @@ -2370,47 +1783,23 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy) checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy); - if (FFlag::LuauUseCommittingTxnLog) - log.concat(std::move(innerState.log)); - else - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); + log.concat(std::move(innerState.log)); } else if (subTable->state == TableState::Free && freeTable->indexer) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.changeIndexer(superTy, subTable->indexer); - } - else - { - freeTable->indexer = subTable->indexer; - } + log.changeIndexer(superTy, subTable->indexer); } if (!freeTable->boundTo && subTable->state != TableState::Free) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.bindTable(superTy, subTy); - } - else - { - DEPRECATED_log(freeTable); - freeTable->boundTo = subTy; - } + log.bindTable(superTy, subTy); } } void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection) { - TableTypeVar* superTable = getMutable(superTy); - TableTypeVar* subTable = getMutable(subTy); - - if (FFlag::LuauUseCommittingTxnLog) - { - superTable = log.getMutable(superTy); - subTable = log.getMutable(subTy); - } + TableTypeVar* superTable = log.getMutable(superTy); + TableTypeVar* subTable = log.getMutable(subTy); if (!superTable || !subTable) ice("passed non-table types to unifySealedTables"); @@ -2476,77 +1865,39 @@ void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersec if (superTable->indexer || subTable->indexer) { - if (FFlag::LuauUseCommittingTxnLog) + if (superTable->indexer && subTable->indexer) + innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer); + else if (subTable->state == TableState::Unsealed) { - if (superTable->indexer && subTable->indexer) - innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer); - else if (subTable->state == TableState::Unsealed) + if (superTable->indexer && !subTable->indexer) { - if (superTable->indexer && !subTable->indexer) - { - log.changeIndexer(subTy, superTable->indexer); - } + log.changeIndexer(subTy, superTable->indexer); } - else if (superTable->state == TableState::Unsealed) + } + else if (superTable->state == TableState::Unsealed) + { + if (subTable->indexer && !superTable->indexer) { - if (subTable->indexer && !superTable->indexer) - { - log.changeIndexer(superTy, subTable->indexer); - } + log.changeIndexer(superTy, subTable->indexer); } - else if (superTable->indexer) + } + else if (superTable->indexer) + { + innerState.tryUnify_(getSingletonTypes().stringType, superTable->indexer->indexType); + for (const auto& [name, type] : subTable->props) { - innerState.tryUnify_(getSingletonTypes().stringType, superTable->indexer->indexType); - for (const auto& [name, type] : subTable->props) - { - const auto& it = superTable->props.find(name); - if (it == superTable->props.end()) - innerState.tryUnify_(type.type, superTable->indexer->indexResultType); - } + const auto& it = superTable->props.find(name); + if (it == superTable->props.end()) + innerState.tryUnify_(type.type, superTable->indexer->indexResultType); } - else - innerState.reportError(TypeError{location, TypeMismatch{superTy, subTy}}); } else - { - if (superTable->indexer && subTable->indexer) - innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer); - else if (subTable->state == TableState::Unsealed) - { - if (superTable->indexer && !subTable->indexer) - subTable->indexer = superTable->indexer; - } - else if (superTable->state == TableState::Unsealed) - { - if (subTable->indexer && !superTable->indexer) - superTable->indexer = subTable->indexer; - } - else if (superTable->indexer) - { - innerState.tryUnify_(getSingletonTypes().stringType, superTable->indexer->indexType); - // 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] : subTable->props) - { - const auto& it = superTable->props.find(name); - if (it == superTable->props.end()) - innerState.tryUnify_(type.type, superTable->indexer->indexResultType); - } - } - else - innerState.reportError(TypeError{location, TypeMismatch{superTy, subTy}}); - } + innerState.reportError(TypeError{location, TypeMismatch{superTy, subTy}}); } - if (FFlag::LuauUseCommittingTxnLog) - { - if (!errorReported) - log.concat(std::move(innerState.log)); - } + if (!errorReported) + log.concat(std::move(innerState.log)); else - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - - if (errorReported) return; if (!missingPropertiesInSuper.empty()) @@ -2594,8 +1945,7 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed) TypeError mismatchError = TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy}}; - if (const MetatableTypeVar* subMetatable = - FFlag::LuauUseCommittingTxnLog ? log.getMutable(subTy) : get(subTy)) + if (const MetatableTypeVar* subMetatable = log.getMutable(subTy)) { Unifier innerState = makeChildUnifier(); innerState.tryUnify_(subMetatable->table, superMetatable->table); @@ -2606,27 +1956,16 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed) else if (!innerState.errors.empty()) reportError(TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front()}}); - if (FFlag::LuauUseCommittingTxnLog) - log.concat(std::move(innerState.log)); - else - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); + log.concat(std::move(innerState.log)); } - else if (TableTypeVar* subTable = FFlag::LuauUseCommittingTxnLog ? log.getMutable(subTy) : getMutable(subTy)) + else if (TableTypeVar* subTable = log.getMutable(subTy)) { switch (subTable->state) { case TableState::Free: { tryUnify_(subTy, superMetatable->table); - - if (FFlag::LuauUseCommittingTxnLog) - { - log.bindTable(subTy, superTy); - } - else - { - subTable->boundTo = superTy; - } + log.bindTable(subTy, superTy); break; } @@ -2637,8 +1976,7 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed) reportError(mismatchError); } } - else if (FFlag::LuauUseCommittingTxnLog ? (log.getMutable(subTy) || log.getMutable(subTy)) - : (get(subTy) || get(subTy))) + else if (log.getMutable(subTy) || log.getMutable(subTy)) { } else @@ -2711,28 +2049,13 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) checkChildUnifierTypeMismatch(innerState.errors, propName, reversed ? subTy : superTy, reversed ? superTy : subTy); - if (FFlag::LuauUseCommittingTxnLog) + if (innerState.errors.empty()) { - if (innerState.errors.empty()) - { - log.concat(std::move(innerState.log)); - } - else - { - ok = false; - } + log.concat(std::move(innerState.log)); } else { - if (innerState.errors.empty()) - { - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); - } - else - { - ok = false; - innerState.DEPRECATED_log.rollback(); - } + ok = false; } } } @@ -2747,15 +2070,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) if (!ok) return; - if (FFlag::LuauUseCommittingTxnLog) - { - log.bindTable(subTy, superTy); - } - else - { - DEPRECATED_log(subTable); - subTable->boundTo = superTy; - } + log.bindTable(subTy, superTy); } else return fail(); @@ -2771,54 +2086,30 @@ static void queueTypePack(std::vector& queue, DenseHashSet& { while (true) { - a = FFlag::LuauFollowWithCommittingTxnLogInAnyUnification ? state.log.follow(a) : follow(a); + a = state.log.follow(a); if (seenTypePacks.find(a)) break; seenTypePacks.insert(a); - if (FFlag::LuauUseCommittingTxnLog) + if (state.log.getMutable(a)) { - if (state.log.getMutable(a)) - { - state.log.replace(a, Unifiable::Bound{anyTypePack}); - } - else if (auto tp = state.log.getMutable(a)) - { - queue.insert(queue.end(), tp->head.begin(), tp->head.end()); - if (tp->tail) - a = *tp->tail; - else - break; - } + state.log.replace(a, Unifiable::Bound{anyTypePack}); } - else + else if (auto tp = state.log.getMutable(a)) { - if (get(a)) - { - state.DEPRECATED_log(a); - *asMutable(a) = Unifiable::Bound{anyTypePack}; - } - else if (auto tp = get(a)) - { - queue.insert(queue.end(), tp->head.begin(), tp->head.end()); - if (tp->tail) - a = *tp->tail; - else - break; - } + queue.insert(queue.end(), tp->head.begin(), tp->head.end()); + if (tp->tail) + a = *tp->tail; + else + break; } } } void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool reversed, int subOffset) { - const VariadicTypePack* superVariadic = get(superTp); - - if (FFlag::LuauUseCommittingTxnLog) - { - superVariadic = log.getMutable(superTp); - } + const VariadicTypePack* superVariadic = log.getMutable(superTp); if (!superVariadic) ice("passed non-variadic pack to tryUnifyVariadics"); @@ -2843,15 +2134,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever TypePackId tail = follow(*maybeTail); if (get(tail)) { - if (FFlag::LuauUseCommittingTxnLog) - { - log.replace(tail, BoundTypePack(superTp)); - } - else - { - DEPRECATED_log(tail); - *asMutable(tail) = BoundTypePack{superTp}; - } + log.replace(tail, BoundTypePack(superTp)); } else if (const VariadicTypePack* vtp = get(tail)) { @@ -2882,103 +2165,54 @@ static void tryUnifyWithAny(std::vector& queue, Unifier& state, DenseHas { while (!queue.empty()) { - if (FFlag::LuauUseCommittingTxnLog) + TypeId ty = state.log.follow(queue.back()); + queue.pop_back(); + + // Types from other modules don't have free types + if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) + continue; + + if (seen.find(ty)) + continue; + + seen.insert(ty); + + if (state.log.getMutable(ty)) { - TypeId ty = state.log.follow(queue.back()); - queue.pop_back(); - - // Types from other modules don't have free types - if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) - continue; - - if (seen.find(ty)) - continue; - - seen.insert(ty); - - if (state.log.getMutable(ty)) - { - state.log.replace(ty, BoundTypeVar{anyType}); - } - else if (auto fun = state.log.getMutable(ty)) - { - queueTypePack(queue, seenTypePacks, state, fun->argTypes, anyTypePack); - queueTypePack(queue, seenTypePacks, state, fun->retType, anyTypePack); - } - else if (auto table = state.log.getMutable(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 = state.log.getMutable(ty)) - { - queue.push_back(mt->table); - queue.push_back(mt->metatable); - } - else if (state.log.getMutable(ty)) - { - // ClassTypeVars never contain free typevars. - } - else if (auto union_ = state.log.getMutable(ty)) - queue.insert(queue.end(), union_->options.begin(), union_->options.end()); - else if (auto intersection = state.log.getMutable(ty)) - queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end()); - else - { - } // Primitives, any, errors, and generics are left untouched. + state.log.replace(ty, BoundTypeVar{anyType}); } + else if (auto fun = state.log.getMutable(ty)) + { + queueTypePack(queue, seenTypePacks, state, fun->argTypes, anyTypePack); + queueTypePack(queue, seenTypePacks, state, fun->retType, anyTypePack); + } + else if (auto table = state.log.getMutable(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 = state.log.getMutable(ty)) + { + queue.push_back(mt->table); + queue.push_back(mt->metatable); + } + else if (state.log.getMutable(ty)) + { + // ClassTypeVars never contain free typevars. + } + else if (auto union_ = state.log.getMutable(ty)) + queue.insert(queue.end(), union_->options.begin(), union_->options.end()); + else if (auto intersection = state.log.getMutable(ty)) + queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end()); else { - TypeId ty = follow(queue.back()); - queue.pop_back(); - if (seen.find(ty)) - continue; - seen.insert(ty); - - if (get(ty)) - { - state.DEPRECATED_log(ty); - *asMutable(ty) = BoundTypeVar{anyType}; - } - else if (auto fun = get(ty)) - { - queueTypePack(queue, seenTypePacks, state, fun->argTypes, anyTypePack); - queueTypePack(queue, seenTypePacks, state, fun->retType, anyTypePack); - } - else if (auto table = get(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(ty)) - { - queue.push_back(mt->table); - queue.push_back(mt->metatable); - } - else if (get(ty)) - { - // ClassTypeVars never contain free typevars. - } - else if (auto union_ = get(ty)) - queue.insert(queue.end(), union_->options.begin(), union_->options.end()); - else if (auto intersection = get(ty)) - queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end()); - else - { - } // Primitives, any, errors, and generics are left untouched. - } + } // Primitives, any, errors, and generics are left untouched. } } @@ -3038,79 +2272,39 @@ void Unifier::occursCheck(DenseHashSet& seen, TypeId needle, TypeId hays occursCheck(seen, needle, tv); }; - if (FFlag::LuauUseCommittingTxnLog) + needle = log.follow(needle); + haystack = log.follow(haystack); + + if (seen.find(haystack)) + return; + + seen.insert(haystack); + + if (log.getMutable(needle)) + return; + + if (!log.getMutable(needle)) + ice("Expected needle to be free"); + + if (needle == haystack) { - needle = log.follow(needle); - haystack = log.follow(haystack); + reportError(TypeError{location, OccursCheckFailed{}}); + log.replace(needle, *getSingletonTypes().errorRecoveryType()); - if (seen.find(haystack)) - return; - - seen.insert(haystack); - - if (log.getMutable(needle)) - return; - - if (!log.getMutable(needle)) - ice("Expected needle to be free"); - - if (needle == haystack) - { - reportError(TypeError{location, OccursCheckFailed{}}); - log.replace(needle, *getSingletonTypes().errorRecoveryType()); - - return; - } - - if (log.getMutable(haystack)) - return; - else if (auto a = log.getMutable(haystack)) - { - for (TypeId ty : a->options) - check(ty); - } - else if (auto a = log.getMutable(haystack)) - { - for (TypeId ty : a->parts) - check(ty); - } + return; } - else + + if (log.getMutable(haystack)) + return; + else if (auto a = log.getMutable(haystack)) { - needle = follow(needle); - haystack = follow(haystack); - - if (seen.find(haystack)) - return; - - seen.insert(haystack); - - if (get(needle)) - return; - - if (!get(needle)) - ice("Expected needle to be free"); - - if (needle == haystack) - { - reportError(TypeError{location, OccursCheckFailed{}}); - DEPRECATED_log(needle); - *asMutable(needle) = *getSingletonTypes().errorRecoveryType(); - return; - } - - if (get(haystack)) - return; - else if (auto a = get(haystack)) - { - for (TypeId ty : a->options) - check(ty); - } - else if (auto a = get(haystack)) - { - for (TypeId ty : a->parts) - check(ty); - } + for (TypeId ty : a->options) + check(ty); + } + else if (auto a = log.getMutable(haystack)) + { + for (TypeId ty : a->parts) + check(ty); } } @@ -3123,87 +2317,45 @@ void Unifier::occursCheck(TypePackId needle, TypePackId haystack) void Unifier::occursCheck(DenseHashSet& seen, TypePackId needle, TypePackId haystack) { - if (FFlag::LuauUseCommittingTxnLog) + needle = log.follow(needle); + haystack = log.follow(haystack); + + if (seen.find(haystack)) + return; + + seen.insert(haystack); + + if (log.getMutable(needle)) + return; + + if (!log.getMutable(needle)) + ice("Expected needle pack to be free"); + + RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit); + + while (!log.getMutable(haystack)) { - needle = log.follow(needle); - haystack = log.follow(haystack); - - if (seen.find(haystack)) - return; - - seen.insert(haystack); - - if (log.getMutable(needle)) - return; - - if (!log.getMutable(needle)) - ice("Expected needle pack to be free"); - - RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit); - - while (!log.getMutable(haystack)) + if (needle == haystack) { - if (needle == haystack) - { - reportError(TypeError{location, OccursCheckFailed{}}); - log.replace(needle, *getSingletonTypes().errorRecoveryTypePack()); + reportError(TypeError{location, OccursCheckFailed{}}); + log.replace(needle, *getSingletonTypes().errorRecoveryTypePack()); - return; - } - - if (auto a = get(haystack); a && a->tail) - { - haystack = log.follow(*a->tail); - continue; - } - - break; + return; } - } - else - { - needle = follow(needle); - haystack = follow(haystack); - if (seen.find(haystack)) - return; - - seen.insert(haystack); - - if (get(needle)) - return; - - if (!get(needle)) - ice("Expected needle pack to be free"); - - RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit); - - while (!get(haystack)) + if (auto a = get(haystack); a && a->tail) { - if (needle == haystack) - { - reportError(TypeError{location, OccursCheckFailed{}}); - DEPRECATED_log(needle); - *asMutable(needle) = *getSingletonTypes().errorRecoveryTypePack(); - } - - if (auto a = get(haystack); a && a->tail) - { - haystack = follow(*a->tail); - continue; - } - - break; + haystack = log.follow(*a->tail); + continue; } + + break; } } Unifier Unifier::makeChildUnifier() { - if (FFlag::LuauUseCommittingTxnLog) - return Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log}; - else - return Unifier{types, mode, DEPRECATED_log.sharedSeen, location, variance, sharedState, &log}; + return Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log}; } bool Unifier::isNonstrictMode() const diff --git a/VM/include/lua.h b/VM/include/lua.h index 0a561f2..274c4ed 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -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, diff --git a/VM/include/luaconf.h b/VM/include/luaconf.h index c5bf1c1..b93cbf7 100644 --- a/VM/include/luaconf.h +++ b/VM/include/luaconf.h @@ -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 diff --git a/VM/include/lualib.h b/VM/include/lualib.h index baf27b4..bebd0a0 100644 --- a/VM/include/lualib.h +++ b/VM/include/lualib.h @@ -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))) diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 9a6f779..9fe2ebb 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -11,6 +11,8 @@ #include +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 diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index 988fd31..2307598 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -10,6 +10,8 @@ #include #include +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; } diff --git a/VM/src/lgc.h b/VM/src/lgc.h index cbeeebd..ad8ee78 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -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 */ diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index ce890ba..55b0618 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -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 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 diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index d584eb2..711c0aa 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -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(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"( diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index bf99077..8da655b 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -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(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index c482847..547fbab 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -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(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(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(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index da035ba..a5eba5d 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -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(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index f63579b..d7bbad2 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -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(); diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index f6ee3cc..d8de259 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -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(&tableOne)->props["foo"].type, *getMutable(&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(&tableOne)->props["foo"].type, *getMutable(&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(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{}}; diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index 6b96f44..fcc21c1 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -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") diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 0e0b6eb..ad4cecd 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -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(result.errors[0]); REQUIRE(acm); diff --git a/tools/LuauVisualize.py b/tools/LuauVisualize.py new file mode 100644 index 0000000..40f8d6b --- /dev/null +++ b/tools/LuauVisualize.py @@ -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*)\"\"") + 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*)\"\"") + else: + return self.valobj.CreateValueFromExpression(node, "(const char*)\"\"") + 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 diff --git a/tools/lldb-formatters.lldb b/tools/lldb-formatters.lldb new file mode 100644 index 0000000..3868ac2 --- /dev/null +++ b/tools/lldb-formatters.lldb @@ -0,0 +1,2 @@ +type synthetic add -x "^Luau::Variant<.+>$" -l LuauVisualize.LuauVariantSyntheticChildrenProvider +type summary add -x "^Luau::Variant<.+>$" -l LuauVisualize.luau_variant_summary