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:
Arseny Kapoulkine 2022-01-06 17:46:53 -08:00 committed by GitHub
parent d323237b6c
commit d50b079325
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 3901 additions and 1375 deletions

18
.gitignore vendored
View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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()

View File

@ -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);

View File

@ -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;
}

View File

@ -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,

View File

@ -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.

View File

@ -13,7 +13,7 @@
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
namespace Luau
{

View File

@ -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);

View File

@ -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())

View File

@ -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

View File

@ -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;