mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
76f67e0733
Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
515 lines
14 KiB
C++
515 lines
14 KiB
C++
// 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 <algorithm>
|
|
#include <stdexcept>
|
|
|
|
namespace Luau
|
|
{
|
|
|
|
const std::string nullPendingResult = "<nullptr>";
|
|
|
|
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<TypeArena> 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<TypeArena> 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<FreeType>(&rightTy->ty);
|
|
if (!rf)
|
|
continue;
|
|
|
|
const BoundType* rb = Luau::get<BoundType>(&rightRep->pending);
|
|
if (!rb)
|
|
continue;
|
|
|
|
const TypeId leftTy = rb->boundTo;
|
|
const FreeType* lf = get_if<FreeType>(&leftTy->ty);
|
|
if (!lf)
|
|
continue;
|
|
|
|
auto leftRep = typeVarChanges.find(leftTy);
|
|
if (!leftRep)
|
|
continue;
|
|
|
|
if ((*leftRep)->dead)
|
|
continue;
|
|
|
|
const BoundType* lb = Luau::get<BoundType>(&(*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<PendingType>(*ty);
|
|
}
|
|
|
|
for (auto& [tp, _rep] : typePackChanges)
|
|
inversed.typePackChanges[tp] = std::make_unique<PendingTypePack>(*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<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
|
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
|
{
|
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
|
sharedSeen->push_back(sortedPair);
|
|
}
|
|
|
|
void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
|
{
|
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
|
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
|
sharedSeen->pop_back();
|
|
}
|
|
|
|
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<PendingType>(*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<PendingTypePack>(*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<const void*>(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<const void*>(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<TypeId> newBoundTo)
|
|
{
|
|
LUAU_ASSERT(get<TableType>(ty));
|
|
LUAU_ASSERT(ty != newBoundTo);
|
|
|
|
PendingType* newTy = queue(ty);
|
|
if (TableType* ttv = Luau::getMutable<TableType>(newTy))
|
|
ttv->boundTo = newBoundTo;
|
|
|
|
return newTy;
|
|
}
|
|
|
|
PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
|
|
{
|
|
LUAU_ASSERT(get<FreeType>(ty) || get<TableType>(ty) || get<FunctionType>(ty));
|
|
|
|
PendingType* newTy = queue(ty);
|
|
if (FreeType* ftv = Luau::getMutable<FreeType>(newTy))
|
|
{
|
|
ftv->level = newLevel;
|
|
}
|
|
else if (TableType* ttv = Luau::getMutable<TableType>(newTy))
|
|
{
|
|
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
|
|
ttv->level = newLevel;
|
|
}
|
|
else if (FunctionType* ftv = Luau::getMutable<FunctionType>(newTy))
|
|
{
|
|
ftv->level = newLevel;
|
|
}
|
|
|
|
return newTy;
|
|
}
|
|
|
|
PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
|
|
{
|
|
LUAU_ASSERT(get<FreeTypePack>(tp));
|
|
|
|
PendingTypePack* newTp = queue(tp);
|
|
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
|
|
{
|
|
ftp->level = newLevel;
|
|
}
|
|
|
|
return newTp;
|
|
}
|
|
|
|
PendingType* TxnLog::changeScope(TypeId ty, NotNull<Scope> newScope)
|
|
{
|
|
LUAU_ASSERT(get<FreeType>(ty) || get<TableType>(ty) || get<FunctionType>(ty));
|
|
|
|
PendingType* newTy = queue(ty);
|
|
if (FreeType* ftv = Luau::getMutable<FreeType>(newTy))
|
|
{
|
|
ftv->scope = newScope;
|
|
}
|
|
else if (TableType* ttv = Luau::getMutable<TableType>(newTy))
|
|
{
|
|
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
|
|
ttv->scope = newScope;
|
|
}
|
|
else if (FunctionType* ftv = Luau::getMutable<FunctionType>(newTy))
|
|
{
|
|
ftv->scope = newScope;
|
|
}
|
|
|
|
return newTy;
|
|
}
|
|
|
|
PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull<Scope> newScope)
|
|
{
|
|
LUAU_ASSERT(get<FreeTypePack>(tp));
|
|
|
|
PendingTypePack* newTp = queue(tp);
|
|
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
|
|
{
|
|
ftp->scope = newScope;
|
|
}
|
|
|
|
return newTp;
|
|
}
|
|
|
|
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
|
|
{
|
|
LUAU_ASSERT(get<TableType>(ty));
|
|
|
|
PendingType* newTy = queue(ty);
|
|
if (TableType* ttv = Luau::getMutable<TableType>(newTy))
|
|
{
|
|
ttv->indexer = indexer;
|
|
}
|
|
|
|
return newTy;
|
|
}
|
|
|
|
std::optional<TypeLevel> TxnLog::getLevel(TypeId ty) const
|
|
{
|
|
if (FreeType* ftv = getMutable<FreeType>(ty))
|
|
return ftv->level;
|
|
else if (TableType* ttv = getMutable<TableType>(ty); ttv && (ttv->state == TableState::Free || ttv->state == TableState::Generic))
|
|
return ttv->level;
|
|
else if (FunctionType* ftv = getMutable<FunctionType>(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<const TxnLog*>(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<const Type*>(&state->pending);
|
|
});
|
|
}
|
|
|
|
TypePackId TxnLog::follow(TypePackId tp) const
|
|
{
|
|
return Luau::follow(tp, this, [](const void* ctx, TypePackId tp) -> TypePackId {
|
|
const TxnLog* self = static_cast<const TxnLog*>(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<const TypePackVar*>(&state->pending);
|
|
});
|
|
}
|
|
|
|
std::pair<std::vector<TypeId>, std::vector<TypePackId>> TxnLog::getChanges() const
|
|
{
|
|
std::pair<std::vector<TypeId>, std::vector<TypePackId>> 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
|