Sync to upstream/release/509 (#303)
- Rework transaction log used for type checking which should result in more robust type checking internals with fewer bugs - Reduce the amount of memory consumed by type checker on large module graphs - Type checker now errors on attempts to change the type of imported module fields - The return type of newproxy is now any (fixes #296) - Implement new number printing algorithm (Schubfach) which makes tostring() produce precise (round-trippable) and short decimal output up to 10x faster - Fix lua_Debug::linedefined to point to the line with the function definition instead of the first statement (fixes #265) - Fix minor bugs in Tab completion in Repl - Repl now saves/restores command history in ~/.luau_history
This commit is contained in:
parent
d323237b6c
commit
d50b079325
|
@ -1,8 +1,10 @@
|
|||
^build/
|
||||
^coverage/
|
||||
^fuzz/luau.pb.*
|
||||
^crash-*
|
||||
^default.prof*
|
||||
^fuzz-*
|
||||
^luau$
|
||||
/.vs
|
||||
/build/
|
||||
/build[.-]*/
|
||||
/coverage/
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/fuzz/luau.pb.*
|
||||
/crash-*
|
||||
/default.prof*
|
||||
/fuzz-*
|
||||
/luau
|
||||
|
|
|
@ -68,7 +68,7 @@ struct FrontendOptions
|
|||
// is complete.
|
||||
bool retainFullTypeGraphs = false;
|
||||
|
||||
// When true, we run typechecking twice, one in the regular mode, ond once in strict mode
|
||||
// When true, we run typechecking twice, once in the regular mode, and once in strict mode
|
||||
// in order to get more precise type information (e.g. for autocomplete).
|
||||
bool typecheckTwice = false;
|
||||
};
|
||||
|
@ -109,18 +109,18 @@ struct Frontend
|
|||
|
||||
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
|
||||
|
||||
CheckResult check(const ModuleName& name); // new shininess
|
||||
LintResult lint(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
|
||||
LintResult lint(const ModuleName& name, std::optional<LintOptions> enabledLintWarnings = {});
|
||||
|
||||
/** Lint some code that has no associated DataModel object
|
||||
*
|
||||
* Since this source fragment has no name, we cannot cache its AST. Instead,
|
||||
* we return it to the caller to use as they wish.
|
||||
*/
|
||||
std::pair<SourceModule, LintResult> lintFragment(std::string_view source, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
std::pair<SourceModule, LintResult> lintFragment(std::string_view source, std::optional<LintOptions> enabledLintWarnings = {});
|
||||
|
||||
CheckResult check(const SourceModule& module); // OLD. TODO KILL
|
||||
LintResult lint(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
LintResult lint(const SourceModule& module, std::optional<LintOptions> enabledLintWarnings = {});
|
||||
|
||||
bool isDirty(const ModuleName& name) const;
|
||||
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauShareTxnSeen);
|
||||
|
||||
|
@ -9,27 +13,28 @@ namespace Luau
|
|||
{
|
||||
|
||||
// Log of where what TypeIds we are rebinding and what they used to be
|
||||
struct TxnLog
|
||||
// Remove with LuauUseCommitTxnLog
|
||||
struct DEPRECATED_TxnLog
|
||||
{
|
||||
TxnLog()
|
||||
DEPRECATED_TxnLog()
|
||||
: originalSeenSize(0)
|
||||
, ownedSeen()
|
||||
, sharedSeen(&ownedSeen)
|
||||
{
|
||||
}
|
||||
|
||||
explicit TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
|
||||
explicit DEPRECATED_TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
|
||||
: originalSeenSize(sharedSeen->size())
|
||||
, ownedSeen()
|
||||
, sharedSeen(sharedSeen)
|
||||
{
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
DEPRECATED_TxnLog(const DEPRECATED_TxnLog&) = delete;
|
||||
DEPRECATED_TxnLog& operator=(const DEPRECATED_TxnLog&) = delete;
|
||||
|
||||
TxnLog(TxnLog&&) = default;
|
||||
TxnLog& operator=(TxnLog&&) = default;
|
||||
DEPRECATED_TxnLog(DEPRECATED_TxnLog&&) = default;
|
||||
DEPRECATED_TxnLog& operator=(DEPRECATED_TxnLog&&) = default;
|
||||
|
||||
void operator()(TypeId a);
|
||||
void operator()(TypePackId a);
|
||||
|
@ -37,7 +42,7 @@ struct TxnLog
|
|||
|
||||
void rollback();
|
||||
|
||||
void concat(TxnLog rhs);
|
||||
void concat(DEPRECATED_TxnLog rhs);
|
||||
|
||||
bool haveSeen(TypeId lhs, TypeId rhs);
|
||||
void pushSeen(TypeId lhs, TypeId rhs);
|
||||
|
@ -54,4 +59,263 @@ public:
|
|||
std::vector<std::pair<TypeId, TypeId>>* sharedSeen; // shared with all the descendent logs
|
||||
};
|
||||
|
||||
// Pending state for a TypeVar. Generated by a TxnLog and committed via
|
||||
// TxnLog::commit.
|
||||
struct PendingType
|
||||
{
|
||||
// The pending TypeVar state.
|
||||
TypeVar pending;
|
||||
|
||||
explicit PendingType(TypeVar state)
|
||||
: pending(std::move(state))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Pending state for a TypePackVar. Generated by a TxnLog and committed via
|
||||
// TxnLog::commit.
|
||||
struct PendingTypePack
|
||||
{
|
||||
// The pending TypePackVar state.
|
||||
TypePackVar pending;
|
||||
|
||||
explicit PendingTypePack(TypePackVar state)
|
||||
: pending(std::move(state))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(PendingType* pending)
|
||||
{
|
||||
// We use getMutable here because this state is intended to be mutated freely.
|
||||
return getMutable<T>(&pending->pending);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(PendingTypePack* pending)
|
||||
{
|
||||
// We use getMutable here because this state is intended to be mutated freely.
|
||||
return getMutable<T>(&pending->pending);
|
||||
}
|
||||
|
||||
// Log of what TypeIds we are rebinding, to be committed later.
|
||||
struct TxnLog
|
||||
{
|
||||
TxnLog()
|
||||
: ownedSeen()
|
||||
, sharedSeen(&ownedSeen)
|
||||
{
|
||||
}
|
||||
|
||||
explicit TxnLog(TxnLog* parent)
|
||||
: parent(parent)
|
||||
{
|
||||
if (parent)
|
||||
{
|
||||
sharedSeen = parent->sharedSeen;
|
||||
}
|
||||
else
|
||||
{
|
||||
sharedSeen = &ownedSeen;
|
||||
}
|
||||
}
|
||||
|
||||
explicit TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
|
||||
: sharedSeen(sharedSeen)
|
||||
{
|
||||
}
|
||||
|
||||
TxnLog(TxnLog* parent, std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
|
||||
: parent(parent)
|
||||
, sharedSeen(sharedSeen)
|
||||
{
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
|
||||
TxnLog(TxnLog&&) = default;
|
||||
TxnLog& operator=(TxnLog&&) = default;
|
||||
|
||||
// Gets an empty TxnLog pointer. This is useful for constructs that
|
||||
// take a TxnLog, like TypePackIterator - use the empty log if you
|
||||
// don't have a TxnLog to give it.
|
||||
static const TxnLog* empty();
|
||||
|
||||
// Joins another TxnLog onto this one. You should use std::move to avoid
|
||||
// copying the rhs TxnLog.
|
||||
//
|
||||
// If both logs talk about the same type, pack, or table, the rhs takes
|
||||
// priority.
|
||||
void concat(TxnLog rhs);
|
||||
|
||||
// Commits the TxnLog, rebinding all type pointers to their pending states.
|
||||
// Clears the TxnLog afterwards.
|
||||
void commit();
|
||||
|
||||
// Clears the TxnLog without committing any pending changes.
|
||||
void clear();
|
||||
|
||||
// Computes an inverse of this TxnLog at the current time.
|
||||
// This method should be called before commit is called in order to give an
|
||||
// accurate result. Committing the inverse of a TxnLog will undo the changes
|
||||
// made by commit, assuming the inverse log is accurate.
|
||||
TxnLog inverse();
|
||||
|
||||
bool haveSeen(TypeId lhs, TypeId rhs) const;
|
||||
void pushSeen(TypeId lhs, TypeId rhs);
|
||||
void popSeen(TypeId lhs, TypeId rhs);
|
||||
|
||||
// Queues a type for modification. The original type will not change until commit
|
||||
// is called. Use pending to get the pending state.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* queue(TypeId ty);
|
||||
|
||||
// Queues a type pack for modification. The original type pack will not change
|
||||
// until commit is called. Use pending to get the pending state.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* queue(TypePackId tp);
|
||||
|
||||
// Returns the pending state of a type, or nullptr if there isn't any. It is important
|
||||
// to note that this pending state is not transitive: the pending state may reference
|
||||
// non-pending types freely, so you may need to call pending multiple times to view the
|
||||
// entire pending state of a type graph.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* pending(TypeId ty) const;
|
||||
|
||||
// Returns the pending state of a type pack, or nullptr if there isn't any. It is
|
||||
// important to note that this pending state is not transitive: the pending state may
|
||||
// reference non-pending types freely, so you may need to call pending multiple times
|
||||
// to view the entire pending state of a type graph.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* pending(TypePackId tp) const;
|
||||
|
||||
// Queues a replacement of a type with another type.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* replace(TypeId ty, TypeVar replacement);
|
||||
|
||||
// Queues a replacement of a type pack with another type pack.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* replace(TypePackId tp, TypePackVar replacement);
|
||||
|
||||
// Queues a replacement of a table type with another table type that is bound
|
||||
// to a specific value.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* bindTable(TypeId ty, std::optional<TypeId> newBoundTo);
|
||||
|
||||
// Queues a replacement of a type with a level with a duplicate of that type
|
||||
// with a new type level.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* changeLevel(TypeId ty, TypeLevel newLevel);
|
||||
|
||||
// Queues a replacement of a type pack with a level with a duplicate of that
|
||||
// type pack with a new type level.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
|
||||
|
||||
// Queues a replacement of a table type with another table type with a new
|
||||
// indexer.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* changeIndexer(TypeId ty, std::optional<TableIndexer> indexer);
|
||||
|
||||
// Returns the type level of the pending state of the type, or the level of that
|
||||
// type, if no pending state exists. If the type doesn't have a notion of a level,
|
||||
// returns nullopt. If the pending state doesn't have a notion of a level, but the
|
||||
// original state does, returns nullopt.
|
||||
std::optional<TypeLevel> getLevel(TypeId ty) const;
|
||||
|
||||
// Follows a type, accounting for pending type states. The returned type may have
|
||||
// pending state; you should use `pending` or `get` to find out.
|
||||
TypeId follow(TypeId ty);
|
||||
|
||||
// Follows a type pack, accounting for pending type states. The returned type pack
|
||||
// may have pending state; you should use `pending` or `get` to find out.
|
||||
TypePackId follow(TypePackId tp) const;
|
||||
|
||||
// Replaces a given type's state with a new variant. Returns the new pending state
|
||||
// of that type.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
template<typename T>
|
||||
PendingType* replace(TypeId ty, T replacement)
|
||||
{
|
||||
return replace(ty, TypeVar(replacement));
|
||||
}
|
||||
|
||||
// Replaces a given type pack's state with a new variant. Returns the new
|
||||
// pending state of that type pack.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
template<typename T>
|
||||
PendingTypePack* replace(TypePackId tp, T replacement)
|
||||
{
|
||||
return replace(tp, TypePackVar(replacement));
|
||||
}
|
||||
|
||||
// Returns T if a given type or type pack is this variant, respecting the
|
||||
// log's pending state.
|
||||
//
|
||||
// Do not retain this pointer; it has the potential to be invalidated when
|
||||
// commit or clear is called.
|
||||
template<typename T, typename TID>
|
||||
T* getMutable(TID ty) const
|
||||
{
|
||||
auto* pendingTy = pending(ty);
|
||||
if (pendingTy)
|
||||
return Luau::getMutable<T>(pendingTy);
|
||||
|
||||
return Luau::getMutable<T>(ty);
|
||||
}
|
||||
|
||||
// Returns whether a given type or type pack is a given state, respecting the
|
||||
// log's pending state.
|
||||
//
|
||||
// This method will not assert if called on a BoundTypeVar or BoundTypePack.
|
||||
template<typename T, typename TID>
|
||||
bool is(TID ty) const
|
||||
{
|
||||
// We do not use getMutable here because this method can be called on
|
||||
// BoundTypeVars, which triggers an assertion.
|
||||
auto* pendingTy = pending(ty);
|
||||
if (pendingTy)
|
||||
return Luau::get_if<T>(&pendingTy->pending.ty) != nullptr;
|
||||
|
||||
return Luau::get_if<T>(&ty->ty) != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
// unique_ptr is used to give us stable pointers across insertions into the
|
||||
// map. Otherwise, it would be really easy to accidentally invalidate the
|
||||
// pointers returned from queue/pending.
|
||||
//
|
||||
// We can't use a DenseHashMap here because we need a non-const iterator
|
||||
// over the map when we concatenate.
|
||||
std::unordered_map<TypeId, std::unique_ptr<PendingType>> typeVarChanges;
|
||||
std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges;
|
||||
|
||||
TxnLog* parent = nullptr;
|
||||
|
||||
// Owned version of sharedSeen. This should not be accessed directly in
|
||||
// TxnLogs; use sharedSeen instead. This field exists because in the tree
|
||||
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
|
||||
// this is an empty vector.
|
||||
std::vector<std::pair<TypeId, TypeId>> ownedSeen;
|
||||
|
||||
public:
|
||||
// Used to avoid infinite recursion when types are cyclic.
|
||||
// Shared with all the descendent TxnLogs.
|
||||
std::vector<std::pair<TypeId, TypeId>>* sharedSeen;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -198,32 +198,32 @@ struct TypeChecker
|
|||
*/
|
||||
TypeId anyIfNonstrict(TypeId ty) const;
|
||||
|
||||
/** Attempt to unify the types left and right. Treat any failures as type errors
|
||||
* in the final typecheck report.
|
||||
/** Attempt to unify the types.
|
||||
* Treat any failures as type errors in the final typecheck report.
|
||||
*/
|
||||
bool unify(TypeId left, TypeId right, const Location& location);
|
||||
bool unify(TypePackId left, TypePackId right, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
||||
bool unify(TypeId subTy, TypeId superTy, const Location& location);
|
||||
bool unify(TypePackId subTy, TypePackId superTy, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
||||
|
||||
/** Attempt to unify the types left and right.
|
||||
* If this fails, and the right type can be instantiated, do so and try unification again.
|
||||
/** Attempt to unify the types.
|
||||
* If this fails, and the subTy type can be instantiated, do so and try unification again.
|
||||
*/
|
||||
bool unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, const Location& location);
|
||||
void unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, Unifier& state);
|
||||
bool unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId subTy, TypeId superTy, const Location& location);
|
||||
void unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId subTy, TypeId superTy, Unifier& state);
|
||||
|
||||
/** Attempt to unify left with right.
|
||||
/** Attempt to unify.
|
||||
* If there are errors, undo everything and return the errors.
|
||||
* If there are no errors, commit and return an empty error vector.
|
||||
*/
|
||||
ErrorVec tryUnify(TypeId left, TypeId right, const Location& location);
|
||||
ErrorVec tryUnify(TypePackId left, TypePackId right, const Location& location);
|
||||
template<typename Id>
|
||||
ErrorVec tryUnify_(Id subTy, Id superTy, const Location& location);
|
||||
ErrorVec tryUnify(TypeId subTy, TypeId superTy, const Location& location);
|
||||
ErrorVec tryUnify(TypePackId subTy, TypePackId superTy, const Location& location);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy, const Location& location);
|
||||
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, const Location& location);
|
||||
|
||||
// Variant that takes a preexisting 'seen' set. We need this in certain cases to avoid infinitely recursing
|
||||
// into cyclic types.
|
||||
ErrorVec canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId left, TypeId right, const Location& location);
|
||||
template<typename Id>
|
||||
ErrorVec canUnify_(Id subTy, Id superTy, const Location& location);
|
||||
ErrorVec canUnify(TypeId subTy, TypeId superTy, const Location& location);
|
||||
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, const Location& location);
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
|
||||
|
@ -237,12 +237,6 @@ struct TypeChecker
|
|||
std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
|
||||
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec tryUnify_(Id left, Id right, const Location& location);
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec canUnify_(Id left, Id right, const Location& location);
|
||||
|
||||
public:
|
||||
/*
|
||||
* Convert monotype into a a polytype, by replacing any metavariables in descendant scopes
|
||||
|
|
|
@ -18,6 +18,8 @@ struct VariadicTypePack;
|
|||
|
||||
struct TypePackVar;
|
||||
|
||||
struct TxnLog;
|
||||
|
||||
using TypePackId = const TypePackVar*;
|
||||
using FreeTypePack = Unifiable::Free;
|
||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||
|
@ -84,6 +86,7 @@ struct TypePackIterator
|
|||
|
||||
TypePackIterator() = default;
|
||||
explicit TypePackIterator(TypePackId tp);
|
||||
TypePackIterator(TypePackId tp, const TxnLog* log);
|
||||
|
||||
TypePackIterator& operator++();
|
||||
TypePackIterator operator++(int);
|
||||
|
@ -104,9 +107,13 @@ private:
|
|||
TypePackId currentTypePack = nullptr;
|
||||
const TypePack* tp = nullptr;
|
||||
size_t currentIndex = 0;
|
||||
|
||||
// Only used if LuauUseCommittingTxnLog is true.
|
||||
const TxnLog* log;
|
||||
};
|
||||
|
||||
TypePackIterator begin(TypePackId tp);
|
||||
TypePackIterator begin(TypePackId tp, TxnLog* log);
|
||||
TypePackIterator end(TypePackId tp);
|
||||
|
||||
using SeenSet = std::set<std::pair<void*, void*>>;
|
||||
|
@ -114,6 +121,7 @@ using SeenSet = std::set<std::pair<void*, void*>>;
|
|||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
|
||||
|
||||
TypePackId follow(TypePackId tp);
|
||||
TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper);
|
||||
|
||||
size_t size(TypePackId tp);
|
||||
bool finite(TypePackId tp);
|
||||
|
|
|
@ -453,6 +453,7 @@ bool areEqual(SeenSet& seen, const TypeVar& lhs, const TypeVar& rhs);
|
|||
|
||||
// Follow BoundTypeVars until we get to something real
|
||||
TypeId follow(TypeId t);
|
||||
TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper);
|
||||
|
||||
std::vector<TypeId> flattenIntersection(TypeId ty);
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypedAllocatorZeroStart)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -20,7 +22,10 @@ class TypedAllocator
|
|||
public:
|
||||
TypedAllocator()
|
||||
{
|
||||
appendBlock();
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
currentBlockSize = kBlockSize;
|
||||
else
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
~TypedAllocator()
|
||||
|
@ -59,12 +64,18 @@ public:
|
|||
|
||||
bool empty() const
|
||||
{
|
||||
return stuff.size() == 1 && currentBlockSize == 0;
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
return stuff.empty();
|
||||
else
|
||||
return stuff.size() == 1 && currentBlockSize == 0;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
return stuff.empty() ? 0 : kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
else
|
||||
return kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
}
|
||||
|
||||
void clear()
|
||||
|
@ -72,7 +83,11 @@ public:
|
|||
if (frozen)
|
||||
unfreeze();
|
||||
free();
|
||||
appendBlock();
|
||||
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
currentBlockSize = kBlockSize;
|
||||
else
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
void freeze()
|
||||
|
|
|
@ -25,6 +25,7 @@ struct Unifier
|
|||
Mode mode;
|
||||
ScopePtr globalScope; // sigh. Needed solely to get at string's metatable.
|
||||
|
||||
DEPRECATED_TxnLog DEPRECATED_log;
|
||||
TxnLog log;
|
||||
ErrorVec errors;
|
||||
Location location;
|
||||
|
@ -33,44 +34,45 @@ struct Unifier
|
|||
|
||||
UnifierSharedState& sharedState;
|
||||
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
||||
TxnLog* parentLog = nullptr);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState);
|
||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy);
|
||||
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||
|
||||
/** Attempt to unify left with right.
|
||||
/** Attempt to unify.
|
||||
* Populate the vector errors with any type errors that may arise.
|
||||
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
|
||||
*/
|
||||
void tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnifyPrimitives(TypeId superTy, TypeId subTy);
|
||||
void tryUnifySingletons(TypeId superTy, TypeId subTy);
|
||||
void tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCall = false);
|
||||
void tryUnifyTables(TypeId left, TypeId right, bool isIntersection = false);
|
||||
void DEPRECATED_tryUnifyTables(TypeId left, TypeId right, bool isIntersection = false);
|
||||
void tryUnifyFreeTable(TypeId free, TypeId other);
|
||||
void tryUnifySealedTables(TypeId left, TypeId right, bool isIntersection);
|
||||
void tryUnifyWithMetatable(TypeId metatable, TypeId other, bool reversed);
|
||||
void tryUnifyWithClass(TypeId superTy, TypeId subTy, bool reversed);
|
||||
void tryUnify(const TableIndexer& superIndexer, const TableIndexer& subIndexer);
|
||||
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnifyPrimitives(TypeId subTy, TypeId superTy);
|
||||
void tryUnifySingletons(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
|
||||
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
|
||||
void DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
|
||||
void tryUnifyFreeTable(TypeId subTy, TypeId superTy);
|
||||
void tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection);
|
||||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
||||
TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {});
|
||||
void cacheResult(TypeId superTy, TypeId subTy);
|
||||
void cacheResult(TypeId subTy, TypeId superTy);
|
||||
|
||||
public:
|
||||
void tryUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
void tryUnifyVariadics(TypePackId superTy, TypePackId subTy, bool reversed, int subOffset = 0);
|
||||
void tryUnify_(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||
void tryUnifyVariadics(TypePackId subTy, TypePackId superTy, bool reversed, int subOffset = 0);
|
||||
|
||||
void tryUnifyWithAny(TypeId any, TypeId ty);
|
||||
void tryUnifyWithAny(TypePackId any, TypePackId ty);
|
||||
void tryUnifyWithAny(TypeId subTy, TypeId anyTy);
|
||||
void tryUnifyWithAny(TypePackId subTy, TypePackId anyTp);
|
||||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
|
||||
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteFirstArg, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteBrokenStringParams, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
|
@ -236,28 +238,31 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
|||
{
|
||||
ty = follow(ty);
|
||||
|
||||
auto canUnify = [&typeArena, &module](TypeId expectedType, TypeId actualType) {
|
||||
auto canUnify = [&typeArena, &module](TypeId subTy, TypeId superTy) {
|
||||
InternalErrorReporter iceReporter;
|
||||
UnifierSharedState unifierState(&iceReporter);
|
||||
Unifier unifier(typeArena, Mode::Strict, module.getModuleScope(), Location(), Variance::Covariant, unifierState);
|
||||
|
||||
if (FFlag::LuauAutocompleteAvoidMutation)
|
||||
if (FFlag::LuauAutocompleteAvoidMutation && !FFlag::LuauUseCommittingTxnLog)
|
||||
{
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
superTy = clone(superTy, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
subTy = clone(subTy, *typeArena, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
auto errors = unifier.canUnify(expectedType, actualType);
|
||||
auto errors = unifier.canUnify(subTy, superTy);
|
||||
return errors.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
unifier.tryUnify(expectedType, actualType);
|
||||
unifier.tryUnify(subTy, superTy);
|
||||
|
||||
bool ok = unifier.errors.empty();
|
||||
unifier.log.rollback();
|
||||
|
||||
if (!FFlag::LuauUseCommittingTxnLog)
|
||||
unifier.DEPRECATED_log.rollback();
|
||||
|
||||
return ok;
|
||||
}
|
||||
};
|
||||
|
@ -293,22 +298,22 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
|||
{
|
||||
auto [retHead, retTail] = flatten(ftv->retType);
|
||||
|
||||
if (!retHead.empty() && canUnify(expectedType, retHead.front()))
|
||||
if (!retHead.empty() && canUnify(retHead.front(), expectedType))
|
||||
return TypeCorrectKind::CorrectFunctionResult;
|
||||
|
||||
// We might only have a variadic tail pack, check if the element is compatible
|
||||
if (retTail)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(expectedType, vtp->ty))
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(vtp->ty, expectedType))
|
||||
return TypeCorrectKind::CorrectFunctionResult;
|
||||
}
|
||||
}
|
||||
|
||||
return canUnify(expectedType, ty) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||
return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (canUnify(expectedType, ty))
|
||||
if (canUnify(ty, expectedType))
|
||||
return TypeCorrectKind::Correct;
|
||||
|
||||
// We also want to suggest functions that return compatible result
|
||||
|
@ -320,13 +325,13 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
|||
auto [retHead, retTail] = flatten(ftv->retType);
|
||||
|
||||
if (!retHead.empty())
|
||||
return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
return canUnify(retHead.front(), expectedType) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
|
||||
// We might only have a variadic tail pack, check if the element is compatible
|
||||
if (retTail)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)))
|
||||
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
return canUnify(vtp->ty, expectedType) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
|
||||
}
|
||||
|
||||
return TypeCorrectKind::None;
|
||||
|
@ -1319,7 +1324,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!nodes.back()->is<AstExprConstantString>())
|
||||
if (!nodes.back()->is<AstExprConstantString>() && (!FFlag::LuauCompleteBrokenStringParams || !nodes.back()->is<AstExprError>()))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
|
@ -138,12 +138,7 @@ declare function gcinfo(): number
|
|||
-- (nil, string).
|
||||
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
|
||||
|
||||
-- a userdata object is "roughly" the same as a sealed empty table
|
||||
-- except `type(newproxy(false))` evaluates to "userdata" so we may need another special type here too.
|
||||
-- another important thing to note: the value passed in conditionally creates an empty metatable, and you have to use getmetatable, NOT
|
||||
-- setmetatable.
|
||||
-- FIXME: change this to something Luau can understand how to reject `setmetatable(newproxy(false or true), {})`.
|
||||
declare function newproxy(mt: boolean?): {}
|
||||
declare function newproxy(mt: boolean?): any
|
||||
|
||||
declare coroutine: {
|
||||
create: <A..., R...>((A...) -> R...) -> thread,
|
||||
|
|
|
@ -351,7 +351,7 @@ FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
|
|||
{
|
||||
}
|
||||
|
||||
CheckResult Frontend::check(const ModuleName& name)
|
||||
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend");
|
||||
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
||||
|
@ -372,6 +372,8 @@ CheckResult Frontend::check(const ModuleName& name)
|
|||
std::vector<ModuleName> buildQueue;
|
||||
bool cycleDetected = parseGraph(buildQueue, checkResult, name);
|
||||
|
||||
FrontendOptions frontendOptions = optionOverride.value_or(options);
|
||||
|
||||
// Keep track of which AST nodes we've reported cycles in
|
||||
std::unordered_set<AstNode*> reportedCycles;
|
||||
|
||||
|
@ -411,31 +413,11 @@ CheckResult Frontend::check(const ModuleName& name)
|
|||
// If we're typechecking twice, we do so.
|
||||
// The second typecheck is always in strict mode with DM awareness
|
||||
// to provide better typen information for IDE features.
|
||||
if (options.typecheckTwice)
|
||||
if (frontendOptions.typecheckTwice)
|
||||
{
|
||||
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict);
|
||||
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
||||
}
|
||||
else if (options.retainFullTypeGraphs && options.typecheckTwice && mode != Mode::Strict)
|
||||
{
|
||||
ModulePtr strictModule = typeChecker.check(sourceModule, Mode::Strict, environmentScope);
|
||||
module->astTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astTypes)
|
||||
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes)
|
||||
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astExpectedTypes)
|
||||
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
|
||||
}
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
|
@ -444,7 +426,7 @@ CheckResult Frontend::check(const ModuleName& name)
|
|||
if (module == nullptr)
|
||||
throw std::runtime_error("Frontend::check produced a nullptr module for " + moduleName);
|
||||
|
||||
if (!options.retainFullTypeGraphs)
|
||||
if (!frontendOptions.retainFullTypeGraphs)
|
||||
{
|
||||
// copyErrors needs to allocate into interfaceTypes as it copies
|
||||
// types out of internalTypes, so we unfreeze it here.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyVisitOnce, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -81,16 +79,8 @@ struct Quantifier
|
|||
void quantify(ModulePtr module, TypeId ty, TypeLevel level)
|
||||
{
|
||||
Quantifier q{std::move(module), level};
|
||||
|
||||
if (FFlag::LuauQuantifyVisitOnce)
|
||||
{
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(ty, q, seen);
|
||||
}
|
||||
else
|
||||
{
|
||||
visitTypeVar(ty, q);
|
||||
}
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(ty, q, seen);
|
||||
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false)
|
||||
|
||||
/*
|
||||
* Prefix generic typenames with gen-
|
||||
|
@ -766,24 +765,12 @@ struct TypePackStringifier
|
|||
else
|
||||
state.emit(", ");
|
||||
|
||||
if (FFlag::LuauFunctionArgumentNameSize)
|
||||
if (elemIndex < elemNames.size() && elemNames[elemIndex])
|
||||
{
|
||||
if (elemIndex < elemNames.size() && elemNames[elemIndex])
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
}
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
|
||||
|
||||
if (!elemNames.empty() && elemNames[elemIndex])
|
||||
{
|
||||
state.emit(elemNames[elemIndex]->name);
|
||||
state.emit(": ");
|
||||
}
|
||||
}
|
||||
elemIndex++;
|
||||
|
||||
stringify(typeId);
|
||||
|
@ -1151,38 +1138,19 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
|
|||
s += ", ";
|
||||
first = false;
|
||||
|
||||
if (FFlag::LuauFunctionArgumentNameSize)
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (argNameIter != ftv.argNames.end())
|
||||
{
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (argNameIter != ftv.argNames.end())
|
||||
{
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
++argNameIter;
|
||||
}
|
||||
else
|
||||
{
|
||||
s += "_: ";
|
||||
}
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
++argNameIter;
|
||||
}
|
||||
else
|
||||
{
|
||||
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (!ftv.argNames.empty())
|
||||
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
|
||||
s += "_: ";
|
||||
}
|
||||
|
||||
s += toString_(*argPackIter);
|
||||
++argPackIter;
|
||||
|
||||
if (!FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
if (!ftv.argNames.empty())
|
||||
{
|
||||
LUAU_ASSERT(argNameIter != ftv.argNames.end());
|
||||
++argNameIter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (argPackIter.tail())
|
||||
|
|
|
@ -4,27 +4,34 @@
|
|||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauUseCommittingTxnLog, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void TxnLog::operator()(TypeId a)
|
||||
void DEPRECATED_TxnLog::operator()(TypeId a)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
typeVarChanges.emplace_back(a, *a);
|
||||
}
|
||||
|
||||
void TxnLog::operator()(TypePackId a)
|
||||
void DEPRECATED_TxnLog::operator()(TypePackId a)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
typePackChanges.emplace_back(a, *a);
|
||||
}
|
||||
|
||||
void TxnLog::operator()(TableTypeVar* a)
|
||||
void DEPRECATED_TxnLog::operator()(TableTypeVar* a)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
tableChanges.emplace_back(a, a->boundTo);
|
||||
}
|
||||
|
||||
void TxnLog::rollback()
|
||||
void DEPRECATED_TxnLog::rollback()
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
for (auto it = typeVarChanges.rbegin(); it != typeVarChanges.rend(); ++it)
|
||||
std::swap(*asMutable(it->first), it->second);
|
||||
|
||||
|
@ -38,8 +45,9 @@ void TxnLog::rollback()
|
|||
sharedSeen->resize(originalSeenSize);
|
||||
}
|
||||
|
||||
void TxnLog::concat(TxnLog rhs)
|
||||
void DEPRECATED_TxnLog::concat(DEPRECATED_TxnLog rhs)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
typeVarChanges.insert(typeVarChanges.end(), rhs.typeVarChanges.begin(), rhs.typeVarChanges.end());
|
||||
rhs.typeVarChanges.clear();
|
||||
|
||||
|
@ -50,23 +58,298 @@ void TxnLog::concat(TxnLog rhs)
|
|||
rhs.tableChanges.clear();
|
||||
}
|
||||
|
||||
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs)
|
||||
bool DEPRECATED_TxnLog::haveSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
const std::pair<TypeId, TypeId> 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(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
sharedSeen->push_back(sortedPair);
|
||||
}
|
||||
|
||||
void DEPRECATED_TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
||||
sharedSeen->pop_back();
|
||||
}
|
||||
|
||||
static const TxnLog emptyLog;
|
||||
|
||||
const TxnLog* TxnLog::empty()
|
||||
{
|
||||
return &emptyLog;
|
||||
}
|
||||
|
||||
void TxnLog::concat(TxnLog rhs)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
for (auto& [ty, rep] : rhs.typeVarChanges)
|
||||
typeVarChanges[ty] = std::move(rep);
|
||||
|
||||
for (auto& [tp, rep] : rhs.typePackChanges)
|
||||
typePackChanges[tp] = std::move(rep);
|
||||
}
|
||||
|
||||
void TxnLog::commit()
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
for (auto& [ty, rep] : typeVarChanges)
|
||||
*asMutable(ty) = rep.get()->pending;
|
||||
|
||||
for (auto& [tp, rep] : typePackChanges)
|
||||
*asMutable(tp) = rep.get()->pending;
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
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)
|
||||
inversed.typeVarChanges[ty] = std::make_unique<PendingType>(*ty);
|
||||
|
||||
for (auto& [tp, _rep] : typePackChanges)
|
||||
inversed.typePackChanges[tp] = std::make_unique<PendingTypePack>(*tp);
|
||||
|
||||
return inversed;
|
||||
}
|
||||
|
||||
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
const std::pair<TypeId, TypeId> 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;
|
||||
}
|
||||
|
||||
if (parent)
|
||||
{
|
||||
return parent->haveSeen(lhs, rhs);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
sharedSeen->push_back(sortedPair);
|
||||
}
|
||||
|
||||
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
const std::pair<TypeId, TypeId> 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)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
LUAU_ASSERT(!ty->persistent);
|
||||
|
||||
// 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 = std::make_unique<PendingType>(*ty);
|
||||
|
||||
return pending.get();
|
||||
}
|
||||
|
||||
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
|
||||
// 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);
|
||||
|
||||
return pending.get();
|
||||
}
|
||||
|
||||
PendingType* TxnLog::pending(TypeId ty) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
for (const TxnLog* current = this; current; current = current->parent)
|
||||
{
|
||||
if (auto it = current->typeVarChanges.find(ty); it != current->typeVarChanges.end())
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PendingTypePack* TxnLog::pending(TypePackId tp) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
for (const TxnLog* current = this; current; current = current->parent)
|
||||
{
|
||||
if (auto it = current->typePackChanges.find(tp); it != current->typePackChanges.end())
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::replace(TypeId ty, TypeVar replacement)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
newTy->pending = replacement;
|
||||
return newTy;
|
||||
}
|
||||
|
||||
PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
PendingTypePack* newTp = queue(tp);
|
||||
newTp->pending = replacement;
|
||||
return newTp;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
LUAU_ASSERT(get<TableTypeVar>(ty));
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy))
|
||||
ttv->boundTo = newBoundTo;
|
||||
|
||||
return newTy;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
LUAU_ASSERT(get<FreeTypeVar>(ty) || get<TableTypeVar>(ty) || get<FunctionTypeVar>(ty));
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
if (FreeTypeVar* ftv = Luau::getMutable<FreeTypeVar>(newTy))
|
||||
{
|
||||
ftv->level = newLevel;
|
||||
}
|
||||
else if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy))
|
||||
{
|
||||
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
|
||||
ttv->level = newLevel;
|
||||
}
|
||||
else if (FunctionTypeVar* ftv = Luau::getMutable<FunctionTypeVar>(newTy))
|
||||
{
|
||||
ftv->level = newLevel;
|
||||
}
|
||||
|
||||
return newTy;
|
||||
}
|
||||
|
||||
PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
LUAU_ASSERT(get<FreeTypePack>(tp));
|
||||
|
||||
PendingTypePack* newTp = queue(tp);
|
||||
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
|
||||
{
|
||||
ftp->level = newLevel;
|
||||
}
|
||||
|
||||
return newTp;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
LUAU_ASSERT(get<TableTypeVar>(ty));
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy))
|
||||
{
|
||||
ttv->indexer = indexer;
|
||||
}
|
||||
|
||||
return newTy;
|
||||
}
|
||||
|
||||
std::optional<TypeLevel> TxnLog::getLevel(TypeId ty) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
if (FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty))
|
||||
return ftv->level;
|
||||
else if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty); ttv && (ttv->state == TableState::Free || ttv->state == TableState::Generic))
|
||||
return ttv->level;
|
||||
else if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
|
||||
return ftv->level;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeId TxnLog::follow(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
return Luau::follow(ty, [this](TypeId ty) {
|
||||
PendingType* state = this->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 TypeVar*>(&state->pending);
|
||||
});
|
||||
}
|
||||
|
||||
TypePackId TxnLog::follow(TypePackId tp) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||
|
||||
return Luau::follow(tp, [this](TypePackId tp) {
|
||||
PendingTypePack* state = this->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);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,12 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include "Luau/TxnLog.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -35,14 +39,28 @@ TypePackVar& TypePackVar::operator=(TypePackVariant&& tp)
|
|||
}
|
||||
|
||||
TypePackIterator::TypePackIterator(TypePackId typePack)
|
||||
: TypePackIterator(typePack, TxnLog::empty())
|
||||
{
|
||||
}
|
||||
|
||||
TypePackIterator::TypePackIterator(TypePackId typePack, const TxnLog* log)
|
||||
: currentTypePack(follow(typePack))
|
||||
, tp(get<TypePack>(currentTypePack))
|
||||
, currentIndex(0)
|
||||
, log(log)
|
||||
{
|
||||
while (tp && tp->head.empty())
|
||||
{
|
||||
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
|
||||