// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/TxnLog.h" #include "Luau/Scope.h" #include "Luau/ToString.h" #include "Luau/TypeArena.h" #include "Luau/TypePack.h" #include #include namespace Luau { const std::string nullPendingResult = ""; std::string toString(PendingType* pending) { if (pending == nullptr) return nullPendingResult; return toString(pending->pending); } std::string dump(PendingType* pending) { if (pending == nullptr) { printf("%s\n", nullPendingResult.c_str()); return nullPendingResult; } ToStringOptions opts; opts.exhaustive = true; opts.functionTypeArguments = true; std::string result = toString(pending->pending, opts); printf("%s\n", result.c_str()); return result; } std::string toString(PendingTypePack* pending) { if (pending == nullptr) return nullPendingResult; return toString(pending->pending); } std::string dump(PendingTypePack* pending) { if (pending == nullptr) { printf("%s\n", nullPendingResult.c_str()); return nullPendingResult; } ToStringOptions opts; opts.exhaustive = true; opts.functionTypeArguments = true; std::string result = toString(pending->pending, opts); printf("%s\n", result.c_str()); return result; } static const TxnLog emptyLog; const TxnLog* TxnLog::empty() { return &emptyLog; } void TxnLog::concat(TxnLog rhs) { for (auto& [ty, rep] : rhs.typeVarChanges) { if (rep->dead) continue; typeVarChanges[ty] = std::move(rep); } for (auto& [tp, rep] : rhs.typePackChanges) typePackChanges[tp] = std::move(rep); radioactive |= rhs.radioactive; } void TxnLog::concatAsIntersections(TxnLog rhs, NotNull arena) { for (auto& [ty, rightRep] : rhs.typeVarChanges) { if (rightRep->dead) continue; if (auto leftRep = typeVarChanges.find(ty); leftRep && !(*leftRep)->dead) { TypeId leftTy = arena->addType((*leftRep)->pending); TypeId rightTy = arena->addType(rightRep->pending); typeVarChanges[ty]->pending.ty = IntersectionType{{leftTy, rightTy}}; } else typeVarChanges[ty] = std::move(rightRep); } for (auto& [tp, rep] : rhs.typePackChanges) typePackChanges[tp] = std::move(rep); radioactive |= rhs.radioactive; } void TxnLog::concatAsUnion(TxnLog rhs, NotNull arena) { /* * Check for cycles. * * We must not combine a log entry that binds 'a to 'b with a log that * binds 'b to 'a. * * Of the two, identify the one with the 'bigger' scope and eliminate the * entry that rebinds it. */ for (const auto& [rightTy, rightRep] : rhs.typeVarChanges) { if (rightRep->dead) continue; // We explicitly use get_if here because we do not wish to do anything // if the uncommitted type is already bound to something else. const FreeType* rf = get_if(&rightTy->ty); if (!rf) continue; const BoundType* rb = Luau::get(&rightRep->pending); if (!rb) continue; const TypeId leftTy = rb->boundTo; const FreeType* lf = get_if(&leftTy->ty); if (!lf) continue; auto leftRep = typeVarChanges.find(leftTy); if (!leftRep) continue; if ((*leftRep)->dead) continue; const BoundType* lb = Luau::get(&(*leftRep)->pending); if (!lb) continue; if (lb->boundTo == rightTy) { // leftTy has been bound to rightTy, but rightTy has also been bound // to leftTy. We find the one that belongs to the more deeply nested // scope and remove it from the log. const bool discardLeft = useScopes ? subsumes(lf->scope, rf->scope) : lf->level.subsumes(rf->level); if (discardLeft) (*leftRep)->dead = true; else rightRep->dead = true; } } for (auto& [ty, rightRep] : rhs.typeVarChanges) { if (rightRep->dead) continue; if (auto leftRep = typeVarChanges.find(ty); leftRep && !(*leftRep)->dead) { TypeId leftTy = arena->addType((*leftRep)->pending); TypeId rightTy = arena->addType(rightRep->pending); if (follow(leftTy) == follow(rightTy)) typeVarChanges[ty] = std::move(rightRep); else typeVarChanges[ty]->pending.ty = UnionType{{leftTy, rightTy}}; } else typeVarChanges[ty] = std::move(rightRep); } for (auto& [tp, rep] : rhs.typePackChanges) typePackChanges[tp] = std::move(rep); radioactive |= rhs.radioactive; } void TxnLog::commit() { LUAU_ASSERT(!radioactive); for (auto& [ty, rep] : typeVarChanges) { if (!rep->dead) asMutable(ty)->reassign(rep.get()->pending); } for (auto& [tp, rep] : typePackChanges) asMutable(tp)->reassign(rep.get()->pending); clear(); } void TxnLog::clear() { typeVarChanges.clear(); typePackChanges.clear(); } TxnLog TxnLog::inverse() { TxnLog inversed(sharedSeen); for (auto& [ty, _rep] : typeVarChanges) { if (!_rep->dead) inversed.typeVarChanges[ty] = std::make_unique(*ty); } for (auto& [tp, _rep] : typePackChanges) inversed.typePackChanges[tp] = std::make_unique(*tp); inversed.radioactive = radioactive; return inversed; } bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const { return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } void TxnLog::pushSeen(TypeId lhs, TypeId rhs) { pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } void TxnLog::popSeen(TypeId lhs, TypeId rhs) { popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } bool TxnLog::haveSeen(TypePackId lhs, TypePackId rhs) const { return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } void TxnLog::pushSeen(TypePackId lhs, TypePackId rhs) { pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } void TxnLog::popSeen(TypePackId lhs, TypePackId rhs) { popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const { 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)) { return true; } return false; } void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs) { 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) { const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); LUAU_ASSERT(sortedPair == sharedSeen->back()); sharedSeen->pop_back(); } PendingType* TxnLog::queue(TypeId ty) { if (ty->persistent) radioactive = true; // Explicitly don't look in ancestors. If we have discovered something new // about this type, we don't want to mutate the parent's state. auto& pending = typeVarChanges[ty]; if (!pending || (*pending).dead) { pending = std::make_unique(*ty); pending->pending.owningArena = nullptr; } return pending.get(); } PendingTypePack* TxnLog::queue(TypePackId tp) { if (tp->persistent) radioactive = true; // Explicitly don't look in ancestors. If we have discovered something new // about this type, we don't want to mutate the parent's state. auto& pending = typePackChanges[tp]; if (!pending) { pending = std::make_unique(*tp); pending->pending.owningArena = nullptr; } return pending.get(); } PendingType* TxnLog::pending(TypeId ty) const { // This function will technically work if `this` is nullptr, but this // indicates a bug, so we explicitly assert. LUAU_ASSERT(static_cast(this) != nullptr); for (const TxnLog* current = this; current; current = current->parent) { if (auto it = current->typeVarChanges.find(ty); it && !(*it)->dead) return it->get(); } return nullptr; } PendingTypePack* TxnLog::pending(TypePackId tp) const { // This function will technically work if `this` is nullptr, but this // indicates a bug, so we explicitly assert. LUAU_ASSERT(static_cast(this) != nullptr); for (const TxnLog* current = this; current; current = current->parent) { if (auto it = current->typePackChanges.find(tp)) return it->get(); } return nullptr; } PendingType* TxnLog::replace(TypeId ty, Type replacement) { PendingType* newTy = queue(ty); newTy->pending.reassign(replacement); return newTy; } PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement) { PendingTypePack* newTp = queue(tp); newTp->pending.reassign(replacement); return newTp; } PendingType* TxnLog::bindTable(TypeId ty, std::optional newBoundTo) { LUAU_ASSERT(get(ty)); LUAU_ASSERT(ty != newBoundTo); PendingType* newTy = queue(ty); if (TableType* ttv = Luau::getMutable(newTy)) ttv->boundTo = newBoundTo; return newTy; } PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel) { LUAU_ASSERT(get(ty) || get(ty) || get(ty)); PendingType* newTy = queue(ty); if (FreeType* ftv = Luau::getMutable(newTy)) { ftv->level = newLevel; } else if (TableType* ttv = Luau::getMutable(newTy)) { LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic); ttv->level = newLevel; } else if (FunctionType* ftv = Luau::getMutable(newTy)) { ftv->level = newLevel; } return newTy; } PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel) { LUAU_ASSERT(get(tp)); PendingTypePack* newTp = queue(tp); if (FreeTypePack* ftp = Luau::getMutable(newTp)) { ftp->level = newLevel; } return newTp; } PendingType* TxnLog::changeScope(TypeId ty, NotNull newScope) { LUAU_ASSERT(get(ty) || get(ty) || get(ty)); PendingType* newTy = queue(ty); if (FreeType* ftv = Luau::getMutable(newTy)) { ftv->scope = newScope; } else if (TableType* ttv = Luau::getMutable(newTy)) { LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic); ttv->scope = newScope; } else if (FunctionType* ftv = Luau::getMutable(newTy)) { ftv->scope = newScope; } return newTy; } PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull newScope) { LUAU_ASSERT(get(tp)); PendingTypePack* newTp = queue(tp); if (FreeTypePack* ftp = Luau::getMutable(newTp)) { ftp->scope = newScope; } return newTp; } PendingType* TxnLog::changeIndexer(TypeId ty, std::optional indexer) { LUAU_ASSERT(get(ty)); PendingType* newTy = queue(ty); if (TableType* ttv = Luau::getMutable(newTy)) { ttv->indexer = indexer; } return newTy; } std::optional TxnLog::getLevel(TypeId ty) const { if (FreeType* ftv = getMutable(ty)) return ftv->level; else if (TableType* ttv = getMutable(ty); ttv && (ttv->state == TableState::Free || ttv->state == TableState::Generic)) return ttv->level; else if (FunctionType* ftv = getMutable(ty)) return ftv->level; return std::nullopt; } TypeId TxnLog::follow(TypeId ty) const { return Luau::follow(ty, this, [](const void* ctx, TypeId ty) -> TypeId { const TxnLog* self = static_cast(ctx); PendingType* state = self->pending(ty); if (state == nullptr) return ty; // Ugly: Fabricate a TypeId that doesn't adhere to most of the invariants // that normally apply. This is safe because follow will only call get<> // on the returned pointer. return const_cast(&state->pending); }); } TypePackId TxnLog::follow(TypePackId tp) const { return Luau::follow(tp, this, [](const void* ctx, TypePackId tp) -> TypePackId { const TxnLog* self = static_cast(ctx); PendingTypePack* state = self->pending(tp); if (state == nullptr) return tp; // Ugly: Fabricate a TypePackId that doesn't adhere to most of the // invariants that normally apply. This is safe because follow will // only call get<> on the returned pointer. return const_cast(&state->pending); }); } std::pair, std::vector> TxnLog::getChanges() const { std::pair, std::vector> result; for (const auto& [typeId, _newState] : typeVarChanges) result.first.push_back(typeId); for (const auto& [typePackId, _newState] : typePackChanges) result.second.push_back(typePackId); return result; } } // namespace Luau