mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/517 (#408)
This commit is contained in:
parent
6c923b8802
commit
dbdf91f3ca
@ -12,6 +12,8 @@ LUAU_FASTFLAG(LuauShareTxnSeen);
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
using TypeOrPackId = const void*;
|
||||||
|
|
||||||
// Log of where what TypeIds we are rebinding and what they used to be
|
// Log of where what TypeIds we are rebinding and what they used to be
|
||||||
// Remove with LuauUseCommitTxnLog
|
// Remove with LuauUseCommitTxnLog
|
||||||
struct DEPRECATED_TxnLog
|
struct DEPRECATED_TxnLog
|
||||||
@ -23,7 +25,7 @@ struct DEPRECATED_TxnLog
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit DEPRECATED_TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
|
explicit DEPRECATED_TxnLog(std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
|
||||||
: originalSeenSize(sharedSeen->size())
|
: originalSeenSize(sharedSeen->size())
|
||||||
, ownedSeen()
|
, ownedSeen()
|
||||||
, sharedSeen(sharedSeen)
|
, sharedSeen(sharedSeen)
|
||||||
@ -48,15 +50,23 @@ struct DEPRECATED_TxnLog
|
|||||||
void pushSeen(TypeId lhs, TypeId rhs);
|
void pushSeen(TypeId lhs, TypeId rhs);
|
||||||
void popSeen(TypeId lhs, TypeId rhs);
|
void popSeen(TypeId lhs, TypeId rhs);
|
||||||
|
|
||||||
|
bool haveSeen(TypePackId lhs, TypePackId rhs);
|
||||||
|
void pushSeen(TypePackId lhs, TypePackId rhs);
|
||||||
|
void popSeen(TypePackId lhs, TypePackId rhs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::pair<TypeId, TypeVar>> typeVarChanges;
|
std::vector<std::pair<TypeId, TypeVar>> typeVarChanges;
|
||||||
std::vector<std::pair<TypePackId, TypePackVar>> typePackChanges;
|
std::vector<std::pair<TypePackId, TypePackVar>> typePackChanges;
|
||||||
std::vector<std::pair<TableTypeVar*, std::optional<TypeId>>> tableChanges;
|
std::vector<std::pair<TableTypeVar*, std::optional<TypeId>>> tableChanges;
|
||||||
size_t originalSeenSize;
|
size_t originalSeenSize;
|
||||||
|
|
||||||
|
bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||||
|
void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||||
|
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::vector<std::pair<TypeId, TypeId>> ownedSeen; // used to avoid infinite recursion when types are cyclic
|
std::vector<std::pair<TypeOrPackId, TypeOrPackId>> ownedSeen; // used to avoid infinite recursion when types are cyclic
|
||||||
std::vector<std::pair<TypeId, TypeId>>* sharedSeen; // shared with all the descendent logs
|
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen; // shared with all the descendent logs
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pending state for a TypeVar. Generated by a TxnLog and committed via
|
// Pending state for a TypeVar. Generated by a TxnLog and committed via
|
||||||
@ -127,12 +137,12 @@ struct TxnLog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
|
explicit TxnLog(std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
|
||||||
: sharedSeen(sharedSeen)
|
: sharedSeen(sharedSeen)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TxnLog(TxnLog* parent, std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
|
TxnLog(TxnLog* parent, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
|
||||||
: parent(parent)
|
: parent(parent)
|
||||||
, sharedSeen(sharedSeen)
|
, sharedSeen(sharedSeen)
|
||||||
{
|
{
|
||||||
@ -173,6 +183,10 @@ struct TxnLog
|
|||||||
void pushSeen(TypeId lhs, TypeId rhs);
|
void pushSeen(TypeId lhs, TypeId rhs);
|
||||||
void popSeen(TypeId lhs, TypeId rhs);
|
void popSeen(TypeId lhs, TypeId rhs);
|
||||||
|
|
||||||
|
bool haveSeen(TypePackId lhs, TypePackId rhs) const;
|
||||||
|
void pushSeen(TypePackId lhs, TypePackId rhs);
|
||||||
|
void popSeen(TypePackId lhs, TypePackId rhs);
|
||||||
|
|
||||||
// Queues a type for modification. The original type will not change until commit
|
// Queues a type for modification. The original type will not change until commit
|
||||||
// is called. Use pending to get the pending state.
|
// is called. Use pending to get the pending state.
|
||||||
//
|
//
|
||||||
@ -316,12 +330,16 @@ private:
|
|||||||
// TxnLogs; use sharedSeen instead. This field exists because in the tree
|
// TxnLogs; use sharedSeen instead. This field exists because in the tree
|
||||||
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
|
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
|
||||||
// this is an empty vector.
|
// this is an empty vector.
|
||||||
std::vector<std::pair<TypeId, TypeId>> ownedSeen;
|
std::vector<std::pair<TypeOrPackId, TypeOrPackId>> ownedSeen;
|
||||||
|
|
||||||
|
bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const;
|
||||||
|
void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||||
|
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Used to avoid infinite recursion when types are cyclic.
|
// Used to avoid infinite recursion when types are cyclic.
|
||||||
// Shared with all the descendent TxnLogs.
|
// Shared with all the descendent TxnLogs.
|
||||||
std::vector<std::pair<TypeId, TypeId>>* sharedSeen;
|
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -73,24 +73,6 @@ struct Instantiation : Substitution
|
|||||||
TypePackId clean(TypePackId tp) override;
|
TypePackId clean(TypePackId tp) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A substitution which replaces free types by generic types.
|
|
||||||
struct Quantification : Substitution
|
|
||||||
{
|
|
||||||
Quantification(TypeArena* arena, TypeLevel level)
|
|
||||||
: Substitution(TxnLog::empty(), arena)
|
|
||||||
, level(level)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeLevel level;
|
|
||||||
std::vector<TypeId> generics;
|
|
||||||
std::vector<TypePackId> genericPacks;
|
|
||||||
bool isDirty(TypeId ty) override;
|
|
||||||
bool isDirty(TypePackId tp) override;
|
|
||||||
TypeId clean(TypeId ty) override;
|
|
||||||
TypePackId clean(TypePackId tp) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A substitution which replaces free types by any
|
// A substitution which replaces free types by any
|
||||||
struct Anyification : Substitution
|
struct Anyification : Substitution
|
||||||
{
|
{
|
||||||
|
@ -298,7 +298,7 @@ struct TableTypeVar
|
|||||||
|
|
||||||
TableTypeVar() = default;
|
TableTypeVar() = default;
|
||||||
explicit TableTypeVar(TableState state, TypeLevel level);
|
explicit TableTypeVar(TableState state, TypeLevel level);
|
||||||
TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, TableState state = TableState::Unsealed);
|
TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, TableState state);
|
||||||
|
|
||||||
Props props;
|
Props props;
|
||||||
std::optional<TableIndexer> indexer;
|
std::optional<TableIndexer> indexer;
|
||||||
@ -477,6 +477,9 @@ bool isOptional(TypeId ty);
|
|||||||
bool isTableIntersection(TypeId ty);
|
bool isTableIntersection(TypeId ty);
|
||||||
bool isOverloadedFunction(TypeId ty);
|
bool isOverloadedFunction(TypeId ty);
|
||||||
|
|
||||||
|
// True when string is a subtype of ty
|
||||||
|
bool maybeString(TypeId ty);
|
||||||
|
|
||||||
std::optional<TypeId> getMetatable(TypeId type);
|
std::optional<TypeId> getMetatable(TypeId type);
|
||||||
TableTypeVar* getMutableTableType(TypeId type);
|
TableTypeVar* getMutableTableType(TypeId type);
|
||||||
const TableTypeVar* getTableType(TypeId type);
|
const TableTypeVar* getTableType(TypeId type);
|
||||||
|
@ -56,7 +56,7 @@ struct Unifier
|
|||||||
|
|
||||||
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
||||||
TxnLog* parentLog = nullptr);
|
TxnLog* parentLog = nullptr);
|
||||||
Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, const Location& location,
|
||||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
||||||
|
|
||||||
// Test whether the two type vars unify. Never commits the result.
|
// Test whether the two type vars unify. Never commits the result.
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false);
|
LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false);
|
||||||
LUAU_FASTFLAGVARIABLE(PreferToCallFunctionsForIntersects, false);
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
|
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
|
||||||
|
|
||||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||||
@ -272,55 +271,34 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
|||||||
|
|
||||||
TypeId expectedType = follow(*typeAtPosition);
|
TypeId expectedType = follow(*typeAtPosition);
|
||||||
|
|
||||||
if (FFlag::PreferToCallFunctionsForIntersects)
|
auto checkFunctionType = [&canUnify, &expectedType](const FunctionTypeVar* ftv) {
|
||||||
{
|
auto [retHead, retTail] = flatten(ftv->retType);
|
||||||
auto checkFunctionType = [&canUnify, &expectedType](const FunctionTypeVar* ftv) {
|
|
||||||
auto [retHead, retTail] = flatten(ftv->retType);
|
|
||||||
|
|
||||||
if (!retHead.empty() && canUnify(retHead.front(), expectedType))
|
if (!retHead.empty() && canUnify(retHead.front(), expectedType))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 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(vtp->ty, expectedType))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// 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(vtp->ty, expectedType))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// We also want to suggest functions that return compatible result
|
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty); ftv && checkFunctionType(ftv))
|
|
||||||
{
|
|
||||||
return TypeCorrectKind::CorrectFunctionResult;
|
|
||||||
}
|
}
|
||||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
|
||||||
{
|
return false;
|
||||||
for (TypeId id : itv->parts)
|
};
|
||||||
{
|
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(id); ftv && checkFunctionType(ftv))
|
// We also want to suggest functions that return compatible result
|
||||||
{
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty); ftv && checkFunctionType(ftv))
|
||||||
return TypeCorrectKind::CorrectFunctionResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// We also want to suggest functions that return compatible result
|
return TypeCorrectKind::CorrectFunctionResult;
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
}
|
||||||
|
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||||
|
{
|
||||||
|
for (TypeId id : itv->parts)
|
||||||
{
|
{
|
||||||
auto [retHead, retTail] = flatten(ftv->retType);
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(id); ftv && checkFunctionType(ftv))
|
||||||
|
|
||||||
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(vtp->ty, expectedType))
|
return TypeCorrectKind::CorrectFunctionResult;
|
||||||
return TypeCorrectKind::CorrectFunctionResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
|
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauTableCloneType, false)
|
||||||
|
|
||||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||||
*
|
*
|
||||||
@ -283,8 +284,16 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
|||||||
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
|
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
|
||||||
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
|
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
|
||||||
|
|
||||||
auto tableLib = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table"));
|
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table")))
|
||||||
attachMagicFunction(tableLib->props["pack"].type, magicFunctionPack);
|
{
|
||||||
|
// tabTy is a generic table type which we can't express via declaration syntax yet
|
||||||
|
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
|
||||||
|
|
||||||
|
if (FFlag::LuauTableCloneType)
|
||||||
|
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone");
|
||||||
|
|
||||||
|
attachMagicFunction(ttv->props["pack"].type, magicFunctionPack);
|
||||||
|
}
|
||||||
|
|
||||||
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,6 @@ declare function gcinfo(): number
|
|||||||
move: <V>({V}, number, number, number, {V}?) -> {V},
|
move: <V>({V}, number, number, number, {V}?) -> {V},
|
||||||
clear: <K, V>({[K]: V}) -> (),
|
clear: <K, V>({[K]: V}) -> (),
|
||||||
|
|
||||||
freeze: <K, V>({[K]: V}) -> {[K]: V},
|
|
||||||
isfrozen: <K, V>({[K]: V}) -> boolean,
|
isfrozen: <K, V>({[K]: V}) -> boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauTypeAliasDefaults)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -369,38 +367,24 @@ struct AstJsonEncoder : public AstVisitor
|
|||||||
|
|
||||||
void write(const AstGenericType& genericType)
|
void write(const AstGenericType& genericType)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writeRaw("{");
|
||||||
{
|
bool c = pushComma();
|
||||||
writeRaw("{");
|
write("name", genericType.name);
|
||||||
bool c = pushComma();
|
if (genericType.defaultValue)
|
||||||
write("name", genericType.name);
|
write("type", genericType.defaultValue);
|
||||||
if (genericType.defaultValue)
|
popComma(c);
|
||||||
write("type", genericType.defaultValue);
|
writeRaw("}");
|
||||||
popComma(c);
|
|
||||||
writeRaw("}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write(genericType.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const AstGenericTypePack& genericTypePack)
|
void write(const AstGenericTypePack& genericTypePack)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writeRaw("{");
|
||||||
{
|
bool c = pushComma();
|
||||||
writeRaw("{");
|
write("name", genericTypePack.name);
|
||||||
bool c = pushComma();
|
if (genericTypePack.defaultValue)
|
||||||
write("name", genericTypePack.name);
|
write("type", genericTypePack.defaultValue);
|
||||||
if (genericTypePack.defaultValue)
|
popComma(c);
|
||||||
write("type", genericTypePack.defaultValue);
|
writeRaw("}");
|
||||||
popComma(c);
|
|
||||||
writeRaw("}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write(genericTypePack.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(AstExprTable::Item::Kind kind)
|
void write(AstExprTable::Item::Kind kind)
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -233,6 +234,20 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct FunctionInfo
|
||||||
|
{
|
||||||
|
explicit FunctionInfo(AstExprFunction* ast)
|
||||||
|
: ast(ast)
|
||||||
|
, dominatedGlobals({})
|
||||||
|
, conditionalExecution(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprFunction* ast;
|
||||||
|
DenseHashSet<AstName> dominatedGlobals;
|
||||||
|
bool conditionalExecution;
|
||||||
|
};
|
||||||
|
|
||||||
struct Global
|
struct Global
|
||||||
{
|
{
|
||||||
AstExprGlobal* firstRef = nullptr;
|
AstExprGlobal* firstRef = nullptr;
|
||||||
@ -241,6 +256,9 @@ private:
|
|||||||
|
|
||||||
bool assigned = false;
|
bool assigned = false;
|
||||||
bool builtin = false;
|
bool builtin = false;
|
||||||
|
bool definedInModuleScope = false;
|
||||||
|
bool definedAsFunction = false;
|
||||||
|
bool readBeforeWritten = false;
|
||||||
std::optional<const char*> deprecated;
|
std::optional<const char*> deprecated;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -248,7 +266,8 @@ private:
|
|||||||
|
|
||||||
DenseHashMap<AstName, Global> globals;
|
DenseHashMap<AstName, Global> globals;
|
||||||
std::vector<AstExprGlobal*> globalRefs;
|
std::vector<AstExprGlobal*> globalRefs;
|
||||||
std::vector<AstExprFunction*> functionStack;
|
std::vector<FunctionInfo> functionStack;
|
||||||
|
|
||||||
|
|
||||||
LintGlobalLocal()
|
LintGlobalLocal()
|
||||||
: globals(AstName())
|
: globals(AstName())
|
||||||
@ -291,12 +310,18 @@ private:
|
|||||||
"Global '%s' is only used in the enclosing function defined at line %d; consider changing it to local",
|
"Global '%s' is only used in the enclosing function defined at line %d; consider changing it to local",
|
||||||
g.firstRef->name.value, top->location.begin.line + 1);
|
g.firstRef->name.value, top->location.begin.line + 1);
|
||||||
}
|
}
|
||||||
|
else if (FFlag::LuauLintGlobalNeverReadBeforeWritten && g.assigned && !g.readBeforeWritten && !g.definedInModuleScope &&
|
||||||
|
g.firstRef->name != context->placeholder)
|
||||||
|
{
|
||||||
|
emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location,
|
||||||
|
"Global '%s' is never read before being written. Consider changing it to local", g.firstRef->name.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(AstExprFunction* node) override
|
bool visit(AstExprFunction* node) override
|
||||||
{
|
{
|
||||||
functionStack.push_back(node);
|
functionStack.emplace_back(node);
|
||||||
|
|
||||||
node->body->visit(this);
|
node->body->visit(this);
|
||||||
|
|
||||||
@ -307,6 +332,11 @@ private:
|
|||||||
|
|
||||||
bool visit(AstExprGlobal* node) override
|
bool visit(AstExprGlobal* node) override
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauLintGlobalNeverReadBeforeWritten && !functionStack.empty() && !functionStack.back().dominatedGlobals.contains(node->name))
|
||||||
|
{
|
||||||
|
Global& g = globals[node->name];
|
||||||
|
g.readBeforeWritten = true;
|
||||||
|
}
|
||||||
trackGlobalRef(node);
|
trackGlobalRef(node);
|
||||||
|
|
||||||
if (node->name == context->placeholder)
|
if (node->name == context->placeholder)
|
||||||
@ -335,6 +365,21 @@ private:
|
|||||||
{
|
{
|
||||||
Global& g = globals[gv->name];
|
Global& g = globals[gv->name];
|
||||||
|
|
||||||
|
if (FFlag::LuauLintGlobalNeverReadBeforeWritten)
|
||||||
|
{
|
||||||
|
if (functionStack.empty())
|
||||||
|
{
|
||||||
|
g.definedInModuleScope = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!functionStack.back().conditionalExecution)
|
||||||
|
{
|
||||||
|
functionStack.back().dominatedGlobals.insert(gv->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (g.builtin)
|
if (g.builtin)
|
||||||
emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location,
|
emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location,
|
||||||
"Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value);
|
"Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value);
|
||||||
@ -369,7 +414,14 @@ private:
|
|||||||
emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location,
|
emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location,
|
||||||
"Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value);
|
"Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value);
|
||||||
else
|
else
|
||||||
|
{
|
||||||
g.assigned = true;
|
g.assigned = true;
|
||||||
|
if (FFlag::LuauLintGlobalNeverReadBeforeWritten)
|
||||||
|
{
|
||||||
|
g.definedAsFunction = true;
|
||||||
|
g.definedInModuleScope = functionStack.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trackGlobalRef(gv);
|
trackGlobalRef(gv);
|
||||||
}
|
}
|
||||||
@ -377,6 +429,98 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HoldConditionalExecution
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HoldConditionalExecution(LintGlobalLocal& p)
|
||||||
|
: p(p)
|
||||||
|
{
|
||||||
|
if (!p.functionStack.empty() && !p.functionStack.back().conditionalExecution)
|
||||||
|
{
|
||||||
|
resetToFalse = true;
|
||||||
|
p.functionStack.back().conditionalExecution = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~HoldConditionalExecution()
|
||||||
|
{
|
||||||
|
if (resetToFalse)
|
||||||
|
p.functionStack.back().conditionalExecution = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool resetToFalse = false;
|
||||||
|
LintGlobalLocal& p;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool visit(AstStatIf* node) override
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
HoldConditionalExecution ce(*this);
|
||||||
|
node->condition->visit(this);
|
||||||
|
node->thenbody->visit(this);
|
||||||
|
if (node->elsebody)
|
||||||
|
node->elsebody->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatWhile* node) override
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
HoldConditionalExecution ce(*this);
|
||||||
|
node->condition->visit(this);
|
||||||
|
node->body->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatRepeat* node) override
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
HoldConditionalExecution ce(*this);
|
||||||
|
node->condition->visit(this);
|
||||||
|
node->body->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatFor* node) override
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
HoldConditionalExecution ce(*this);
|
||||||
|
node->from->visit(this);
|
||||||
|
node->to->visit(this);
|
||||||
|
|
||||||
|
if (node->step)
|
||||||
|
node->step->visit(this);
|
||||||
|
|
||||||
|
node->body->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatForIn* node) override
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
HoldConditionalExecution ce(*this);
|
||||||
|
for (AstExpr* expr : node->values)
|
||||||
|
expr->visit(this);
|
||||||
|
|
||||||
|
node->body->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void trackGlobalRef(AstExprGlobal* node)
|
void trackGlobalRef(AstExprGlobal* node)
|
||||||
{
|
{
|
||||||
Global& g = globals[node->name];
|
Global& g = globals[node->name];
|
||||||
@ -390,7 +534,12 @@ private:
|
|||||||
// to reduce the cost of tracking we only track this for user globals
|
// to reduce the cost of tracking we only track this for user globals
|
||||||
if (!g.builtin)
|
if (!g.builtin)
|
||||||
{
|
{
|
||||||
g.functionRef = functionStack;
|
g.functionRef.clear();
|
||||||
|
g.functionRef.reserve(functionStack.size());
|
||||||
|
for (const FunctionInfo& entry : functionStack)
|
||||||
|
{
|
||||||
|
g.functionRef.push_back(entry.ast);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -401,7 +550,7 @@ private:
|
|||||||
// we need to find a common prefix between all uses of a global
|
// we need to find a common prefix between all uses of a global
|
||||||
size_t prefix = 0;
|
size_t prefix = 0;
|
||||||
|
|
||||||
while (prefix < g.functionRef.size() && prefix < functionStack.size() && g.functionRef[prefix] == functionStack[prefix])
|
while (prefix < g.functionRef.size() && prefix < functionStack.size() && g.functionRef[prefix] == functionStack[prefix].ast)
|
||||||
prefix++;
|
prefix++;
|
||||||
|
|
||||||
g.functionRef.resize(prefix);
|
g.functionRef.resize(prefix);
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
|
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||||
LUAU_FASTFLAG(LuauTypeAliasDefaults)
|
|
||||||
LUAU_FASTFLAG(LuauImmutableTypes)
|
LUAU_FASTFLAG(LuauImmutableTypes)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -463,7 +462,7 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, See
|
|||||||
TypeId ty = clone(param.ty, dest, seenTypes, seenTypePacks, cloneState);
|
TypeId ty = clone(param.ty, dest, seenTypes, seenTypePacks, cloneState);
|
||||||
std::optional<TypeId> defaultValue;
|
std::optional<TypeId> defaultValue;
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults && param.defaultValue)
|
if (param.defaultValue)
|
||||||
defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState);
|
defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState);
|
||||||
|
|
||||||
result.typeParams.push_back({ty, defaultValue});
|
result.typeParams.push_back({ty, defaultValue});
|
||||||
@ -474,7 +473,7 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, See
|
|||||||
TypePackId tp = clone(param.tp, dest, seenTypes, seenTypePacks, cloneState);
|
TypePackId tp = clone(param.tp, dest, seenTypes, seenTypePacks, cloneState);
|
||||||
std::optional<TypePackId> defaultValue;
|
std::optional<TypePackId> defaultValue;
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults && param.defaultValue)
|
if (param.defaultValue)
|
||||||
defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState);
|
defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState);
|
||||||
|
|
||||||
result.typePackParams.push_back({tp, defaultValue});
|
result.typePackParams.push_back({tp, defaultValue});
|
||||||
|
@ -10,8 +10,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauTypeAliasDefaults)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prefix generic typenames with gen-
|
* Prefix generic typenames with gen-
|
||||||
* Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4
|
* Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4
|
||||||
@ -288,28 +286,15 @@ struct TypeVarStringifier
|
|||||||
else
|
else
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
bool wrap = !singleTp && get<TypePack>(follow(tp));
|
||||||
{
|
|
||||||
bool wrap = !singleTp && get<TypePack>(follow(tp));
|
|
||||||
|
|
||||||
if (wrap)
|
if (wrap)
|
||||||
state.emit("(");
|
state.emit("(");
|
||||||
|
|
||||||
stringify(tp);
|
stringify(tp);
|
||||||
|
|
||||||
if (wrap)
|
if (wrap)
|
||||||
state.emit(")");
|
state.emit(")");
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!singleTp)
|
|
||||||
state.emit("(");
|
|
||||||
|
|
||||||
stringify(tp);
|
|
||||||
|
|
||||||
if (!singleTp)
|
|
||||||
state.emit(")");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types.size() || typePacks.size())
|
if (types.size() || typePacks.size())
|
||||||
@ -1105,100 +1090,8 @@ std::string toString(const TypePackVar& tp, const ToStringOptions& opts)
|
|||||||
return toString(const_cast<TypePackId>(&tp), std::move(opts));
|
return toString(const_cast<TypePackId>(&tp), std::move(opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string toStringNamedFunction_DEPRECATED(const std::string& prefix, const FunctionTypeVar& ftv, ToStringOptions opts)
|
|
||||||
{
|
|
||||||
std::string s = prefix;
|
|
||||||
|
|
||||||
auto toString_ = [&opts](TypeId ty) -> std::string {
|
|
||||||
ToStringResult res = toStringDetailed(ty, opts);
|
|
||||||
opts.nameMap = std::move(res.nameMap);
|
|
||||||
return res.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto toStringPack_ = [&opts](TypePackId ty) -> std::string {
|
|
||||||
ToStringResult res = toStringDetailed(ty, opts);
|
|
||||||
opts.nameMap = std::move(res.nameMap);
|
|
||||||
return res.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!opts.hideNamedFunctionTypeParameters && (!ftv.generics.empty() || !ftv.genericPacks.empty()))
|
|
||||||
{
|
|
||||||
s += "<";
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
for (TypeId g : ftv.generics)
|
|
||||||
{
|
|
||||||
if (!first)
|
|
||||||
s += ", ";
|
|
||||||
first = false;
|
|
||||||
s += toString_(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TypePackId gp : ftv.genericPacks)
|
|
||||||
{
|
|
||||||
if (!first)
|
|
||||||
s += ", ";
|
|
||||||
first = false;
|
|
||||||
s += toStringPack_(gp);
|
|
||||||
}
|
|
||||||
|
|
||||||
s += ">";
|
|
||||||
}
|
|
||||||
|
|
||||||
s += "(";
|
|
||||||
|
|
||||||
auto argPackIter = begin(ftv.argTypes);
|
|
||||||
auto argNameIter = ftv.argNames.begin();
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
while (argPackIter != end(ftv.argTypes))
|
|
||||||
{
|
|
||||||
if (!first)
|
|
||||||
s += ", ";
|
|
||||||
first = false;
|
|
||||||
|
|
||||||
// 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 += toString_(*argPackIter);
|
|
||||||
++argPackIter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argPackIter.tail())
|
|
||||||
{
|
|
||||||
if (auto vtp = get<VariadicTypePack>(*argPackIter.tail()))
|
|
||||||
s += ", ...: " + toString_(vtp->ty);
|
|
||||||
else
|
|
||||||
s += ", ...: " + toStringPack_(*argPackIter.tail());
|
|
||||||
}
|
|
||||||
|
|
||||||
s += "): ";
|
|
||||||
|
|
||||||
size_t retSize = size(ftv.retType);
|
|
||||||
bool hasTail = !finite(ftv.retType);
|
|
||||||
if (retSize == 0 && !hasTail)
|
|
||||||
s += "()";
|
|
||||||
else if ((retSize == 0 && hasTail) || (retSize == 1 && !hasTail))
|
|
||||||
s += toStringPack_(ftv.retType);
|
|
||||||
else
|
|
||||||
s += "(" + toStringPack_(ftv.retType) + ")";
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeVar& ftv, ToStringOptions opts)
|
std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeVar& ftv, ToStringOptions opts)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauTypeAliasDefaults)
|
|
||||||
return toStringNamedFunction_DEPRECATED(prefix, ftv, opts);
|
|
||||||
|
|
||||||
ToStringResult result;
|
ToStringResult result;
|
||||||
StringifierState state(opts, result, opts.nameMap);
|
StringifierState state(opts, result, opts.nameMap);
|
||||||
TypeVarStringifier tvs{state};
|
TypeVarStringifier tvs{state};
|
||||||
|
@ -10,8 +10,6 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauTypeAliasDefaults)
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
bool isIdentifierStartChar(char c)
|
bool isIdentifierStartChar(char c)
|
||||||
@ -796,21 +794,14 @@ struct Printer
|
|||||||
{
|
{
|
||||||
comma();
|
comma();
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writer.advance(o.location.begin);
|
||||||
{
|
writer.identifier(o.name.value);
|
||||||
writer.advance(o.location.begin);
|
|
||||||
writer.identifier(o.name.value);
|
|
||||||
|
|
||||||
if (o.defaultValue)
|
if (o.defaultValue)
|
||||||
{
|
|
||||||
writer.maybeSpace(o.defaultValue->location.begin, 2);
|
|
||||||
writer.symbol("=");
|
|
||||||
visualizeTypeAnnotation(*o.defaultValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
writer.identifier(o.name.value);
|
writer.maybeSpace(o.defaultValue->location.begin, 2);
|
||||||
|
writer.symbol("=");
|
||||||
|
visualizeTypeAnnotation(*o.defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,23 +809,15 @@ struct Printer
|
|||||||
{
|
{
|
||||||
comma();
|
comma();
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writer.advance(o.location.begin);
|
||||||
{
|
writer.identifier(o.name.value);
|
||||||
writer.advance(o.location.begin);
|
writer.symbol("...");
|
||||||
writer.identifier(o.name.value);
|
|
||||||
writer.symbol("...");
|
|
||||||
|
|
||||||
if (o.defaultValue)
|
if (o.defaultValue)
|
||||||
{
|
|
||||||
writer.maybeSpace(o.defaultValue->location.begin, 2);
|
|
||||||
writer.symbol("=");
|
|
||||||
visualizeTypePackAnnotation(*o.defaultValue, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
writer.identifier(o.name.value);
|
writer.maybeSpace(o.defaultValue->location.begin, 2);
|
||||||
writer.symbol("...");
|
writer.symbol("=");
|
||||||
|
visualizeTypePackAnnotation(*o.defaultValue, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -882,18 +865,14 @@ struct Printer
|
|||||||
{
|
{
|
||||||
comma();
|
comma();
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writer.advance(o.location.begin);
|
||||||
writer.advance(o.location.begin);
|
|
||||||
|
|
||||||
writer.identifier(o.name.value);
|
writer.identifier(o.name.value);
|
||||||
}
|
}
|
||||||
for (const auto& o : func.genericPacks)
|
for (const auto& o : func.genericPacks)
|
||||||
{
|
{
|
||||||
comma();
|
comma();
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writer.advance(o.location.begin);
|
||||||
writer.advance(o.location.begin);
|
|
||||||
|
|
||||||
writer.identifier(o.name.value);
|
writer.identifier(o.name.value);
|
||||||
writer.symbol("...");
|
writer.symbol("...");
|
||||||
}
|
}
|
||||||
@ -1023,18 +1002,14 @@ struct Printer
|
|||||||
{
|
{
|
||||||
comma();
|
comma();
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writer.advance(o.location.begin);
|
||||||
writer.advance(o.location.begin);
|
|
||||||
|
|
||||||
writer.identifier(o.name.value);
|
writer.identifier(o.name.value);
|
||||||
}
|
}
|
||||||
for (const auto& o : a->genericPacks)
|
for (const auto& o : a->genericPacks)
|
||||||
{
|
{
|
||||||
comma();
|
comma();
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
writer.advance(o.location.begin);
|
||||||
writer.advance(o.location.begin);
|
|
||||||
|
|
||||||
writer.identifier(o.name.value);
|
writer.identifier(o.name.value);
|
||||||
writer.symbol("...");
|
writer.symbol("...");
|
||||||
}
|
}
|
||||||
|
@ -61,22 +61,52 @@ void DEPRECATED_TxnLog::concat(DEPRECATED_TxnLog rhs)
|
|||||||
|
|
||||||
bool DEPRECATED_TxnLog::haveSeen(TypeId lhs, TypeId rhs)
|
bool DEPRECATED_TxnLog::haveSeen(TypeId lhs, TypeId rhs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
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)
|
void DEPRECATED_TxnLog::pushSeen(TypeId lhs, TypeId rhs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
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)
|
void DEPRECATED_TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
||||||
|
{
|
||||||
|
popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DEPRECATED_TxnLog::haveSeen(TypePackId lhs, TypePackId rhs)
|
||||||
|
{
|
||||||
|
return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DEPRECATED_TxnLog::pushSeen(TypePackId lhs, TypePackId rhs)
|
||||||
|
{
|
||||||
|
pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DEPRECATED_TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
|
||||||
|
{
|
||||||
|
popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DEPRECATED_TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||||
|
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DEPRECATED_TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||||
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||||
|
sharedSeen->push_back(sortedPair);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DEPRECATED_TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
|
||||||
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||||
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
||||||
sharedSeen->pop_back();
|
sharedSeen->pop_back();
|
||||||
}
|
}
|
||||||
@ -186,10 +216,40 @@ TxnLog TxnLog::inverse()
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const
|
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const
|
||||||
|
{
|
||||||
|
return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
|
||||||
|
{
|
||||||
|
pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
||||||
|
{
|
||||||
|
popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TxnLog::haveSeen(TypePackId lhs, TypePackId rhs) const
|
||||||
|
{
|
||||||
|
return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxnLog::pushSeen(TypePackId lhs, TypePackId rhs)
|
||||||
|
{
|
||||||
|
pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
|
||||||
|
{
|
||||||
|
popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||||
|
|
||||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||||
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
|
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@ -203,19 +263,19 @@ bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
|
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||||
|
|
||||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||||
sharedSeen->push_back(sortedPair);
|
sharedSeen->push_back(sortedPair);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
|
||||||
|
|
||||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||||
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
LUAU_ASSERT(sortedPair == sharedSeen->back());
|
||||||
sharedSeen->pop_back();
|
sharedSeen->pop_back();
|
||||||
}
|
}
|
||||||
|
@ -29,22 +29,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
|
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
|
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
|
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
|
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
|
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
|
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false)
|
|
||||||
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree)
|
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false)
|
LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -445,7 +443,7 @@ void TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block)
|
|||||||
// function f<a>(x:a):a local x: number = g(37) return x end
|
// function f<a>(x:a):a local x: number = g(37) return x end
|
||||||
// function g(x:number):number return f(x) end
|
// function g(x:number):number return f(x) end
|
||||||
// ```
|
// ```
|
||||||
if (FFlag::LuauQuantifyInPlace2 ? containsFunctionCallOrReturn(**protoIter) : containsFunctionCall(**protoIter))
|
if (containsFunctionCallOrReturn(**protoIter))
|
||||||
{
|
{
|
||||||
while (checkIter != protoIter)
|
while (checkIter != protoIter)
|
||||||
{
|
{
|
||||||
@ -1676,7 +1674,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||||||
}
|
}
|
||||||
else if (tableType->state == TableState::Free)
|
else if (tableType->state == TableState::Free)
|
||||||
{
|
{
|
||||||
TypeId result = FFlag::LuauAscribeCorrectLevelToInferredProperitesOfFreeTables ? freshType(tableType->level) : freshType(scope);
|
TypeId result = freshType(tableType->level);
|
||||||
tableType->props[name] = {result};
|
tableType->props[name] = {result};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -1776,31 +1774,62 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||||||
|
|
||||||
std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||||
{
|
{
|
||||||
std::set<TypeId> s;
|
if (FFlag::LuauDoNotAccidentallyDependOnPointerOrdering)
|
||||||
|
|
||||||
for (TypeId t : types)
|
|
||||||
{
|
{
|
||||||
if (const UnionTypeVar* utv = get<UnionTypeVar>(follow(t)))
|
std::vector<TypeId> result;
|
||||||
|
for (TypeId t : types)
|
||||||
{
|
{
|
||||||
std::vector<TypeId> r = reduceUnion(utv->options);
|
t = follow(t);
|
||||||
for (TypeId ty : r)
|
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
|
||||||
s.insert(ty);
|
return {t};
|
||||||
|
|
||||||
|
if (const UnionTypeVar* utv = get<UnionTypeVar>(t))
|
||||||
|
{
|
||||||
|
std::vector<TypeId> r = reduceUnion(utv->options);
|
||||||
|
for (TypeId ty : r)
|
||||||
|
{
|
||||||
|
ty = follow(ty);
|
||||||
|
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
||||||
|
return {ty};
|
||||||
|
|
||||||
|
if (std::find(result.begin(), result.end(), ty) == result.end())
|
||||||
|
result.push_back(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (std::find(result.begin(), result.end(), t) == result.end())
|
||||||
|
result.push_back(t);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
s.insert(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any of them are ErrorTypeVars/AnyTypeVars, decay into them.
|
return result;
|
||||||
for (TypeId t : s)
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
t = follow(t);
|
std::set<TypeId> s;
|
||||||
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
|
|
||||||
return {t};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<TypeId> r(s.begin(), s.end());
|
for (TypeId t : types)
|
||||||
std::sort(r.begin(), r.end());
|
{
|
||||||
return r;
|
if (const UnionTypeVar* utv = get<UnionTypeVar>(follow(t)))
|
||||||
|
{
|
||||||
|
std::vector<TypeId> r = reduceUnion(utv->options);
|
||||||
|
for (TypeId ty : r)
|
||||||
|
s.insert(ty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s.insert(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of them are ErrorTypeVars/AnyTypeVars, decay into them.
|
||||||
|
for (TypeId t : s)
|
||||||
|
{
|
||||||
|
t = follow(t);
|
||||||
|
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
|
||||||
|
return {t};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TypeId> r(s.begin(), s.end());
|
||||||
|
std::sort(r.begin(), r.end());
|
||||||
|
return r;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
|
std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
|
||||||
@ -2811,7 +2840,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
|||||||
}
|
}
|
||||||
else if (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free)
|
else if (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free)
|
||||||
{
|
{
|
||||||
TypeId resultType = freshType(FFlag::LuauAnotherTypeLevelFix ? exprTable->level : scope->level);
|
TypeId resultType = freshType(exprTable->level);
|
||||||
exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)};
|
exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)};
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
@ -4453,51 +4482,6 @@ TypePackId ReplaceGenerics::clean(TypePackId tp)
|
|||||||
return addTypePack(TypePackVar(FreeTypePack{level}));
|
return addTypePack(TypePackVar(FreeTypePack{level}));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Quantification::isDirty(TypeId ty)
|
|
||||||
{
|
|
||||||
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
|
||||||
return level.subsumes(ttv->level) && ((ttv->state == TableState::Free) || (ttv->state == TableState::Unsealed));
|
|
||||||
else if (const FreeTypeVar* ftv = log->getMutable<FreeTypeVar>(ty))
|
|
||||||
return level.subsumes(ftv->level);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Quantification::isDirty(TypePackId tp)
|
|
||||||
{
|
|
||||||
if (const FreeTypePack* ftv = log->getMutable<FreeTypePack>(tp))
|
|
||||||
return level.subsumes(ftv->level);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeId Quantification::clean(TypeId ty)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(isDirty(ty));
|
|
||||||
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
|
||||||
{
|
|
||||||
TableState state = (ttv->state == TableState::Unsealed ? TableState::Sealed : TableState::Generic);
|
|
||||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, state};
|
|
||||||
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
|
|
||||||
clone.definitionModuleName = ttv->definitionModuleName;
|
|
||||||
return addType(std::move(clone));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TypeId generic = addType(GenericTypeVar{level});
|
|
||||||
generics.push_back(generic);
|
|
||||||
return generic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypePackId Quantification::clean(TypePackId tp)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(isDirty(tp));
|
|
||||||
TypePackId genericPack = addTypePack(TypePackVar(GenericTypePack{level}));
|
|
||||||
genericPacks.push_back(genericPack);
|
|
||||||
return genericPack;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Anyification::isDirty(TypeId ty)
|
bool Anyification::isDirty(TypeId ty)
|
||||||
{
|
{
|
||||||
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
||||||
@ -4550,29 +4534,8 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
|
|||||||
if (!ftv || !ftv->generics.empty() || !ftv->genericPacks.empty())
|
if (!ftv || !ftv->generics.empty() || !ftv->genericPacks.empty())
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
if (FFlag::LuauQuantifyInPlace2)
|
Luau::quantify(ty, scope->level);
|
||||||
{
|
return ty;
|
||||||
Luau::quantify(ty, scope->level);
|
|
||||||
return ty;
|
|
||||||
}
|
|
||||||
|
|
||||||
Quantification quantification{¤tModule->internalTypes, scope->level};
|
|
||||||
std::optional<TypeId> qty = quantification.substitute(ty);
|
|
||||||
|
|
||||||
if (!qty.has_value())
|
|
||||||
{
|
|
||||||
reportError(location, UnificationTooComplex{});
|
|
||||||
return errorRecoveryType(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ty == *qty)
|
|
||||||
return ty;
|
|
||||||
|
|
||||||
FunctionTypeVar* qftv = getMutable<FunctionTypeVar>(*qty);
|
|
||||||
LUAU_ASSERT(qftv);
|
|
||||||
qftv->generics = std::move(quantification.generics);
|
|
||||||
qftv->genericPacks = std::move(quantification.genericPacks);
|
|
||||||
return *qty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log)
|
TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log)
|
||||||
@ -4915,35 +4878,20 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
|
|||||||
if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty())
|
if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty())
|
||||||
return tf->type;
|
return tf->type;
|
||||||
|
|
||||||
bool hasDefaultTypes = false;
|
|
||||||
bool hasDefaultPacks = false;
|
|
||||||
bool parameterCountErrorReported = false;
|
bool parameterCountErrorReported = false;
|
||||||
|
bool hasDefaultTypes = std::any_of(tf->typeParams.begin(), tf->typeParams.end(), [](auto&& el) {
|
||||||
|
return el.defaultValue.has_value();
|
||||||
|
});
|
||||||
|
bool hasDefaultPacks = std::any_of(tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& el) {
|
||||||
|
return el.defaultValue.has_value();
|
||||||
|
});
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
if (!lit->hasParameterList)
|
||||||
{
|
{
|
||||||
hasDefaultTypes = std::any_of(tf->typeParams.begin(), tf->typeParams.end(), [](auto&& el) {
|
if ((!tf->typeParams.empty() && !hasDefaultTypes) || (!tf->typePackParams.empty() && !hasDefaultPacks))
|
||||||
return el.defaultValue.has_value();
|
|
||||||
});
|
|
||||||
hasDefaultPacks = std::any_of(tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& el) {
|
|
||||||
return el.defaultValue.has_value();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!lit->hasParameterList)
|
|
||||||
{
|
|
||||||
if ((!tf->typeParams.empty() && !hasDefaultTypes) || (!tf->typePackParams.empty() && !hasDefaultPacks))
|
|
||||||
{
|
|
||||||
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
|
|
||||||
parameterCountErrorReported = true;
|
|
||||||
if (!FFlag::LuauErrorRecoveryType)
|
|
||||||
return errorRecoveryType(scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!lit->hasParameterList && !tf->typePackParams.empty())
|
|
||||||
{
|
{
|
||||||
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
|
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
|
||||||
|
parameterCountErrorReported = true;
|
||||||
if (!FFlag::LuauErrorRecoveryType)
|
if (!FFlag::LuauErrorRecoveryType)
|
||||||
return errorRecoveryType(scope);
|
return errorRecoveryType(scope);
|
||||||
}
|
}
|
||||||
@ -4986,72 +4934,69 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
|
|||||||
if (typePackParams.empty() && !extraTypes.empty())
|
if (typePackParams.empty() && !extraTypes.empty())
|
||||||
typePackParams.push_back(addTypePack(extraTypes));
|
typePackParams.push_back(addTypePack(extraTypes));
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults)
|
size_t typesProvided = typeParams.size();
|
||||||
|
size_t typesRequired = tf->typeParams.size();
|
||||||
|
|
||||||
|
size_t packsProvided = typePackParams.size();
|
||||||
|
size_t packsRequired = tf->typePackParams.size();
|
||||||
|
|
||||||
|
bool notEnoughParameters =
|
||||||
|
(typesProvided < typesRequired && packsProvided == 0) || (typesProvided == typesRequired && packsProvided < packsRequired);
|
||||||
|
bool hasDefaultParameters = hasDefaultTypes || hasDefaultPacks;
|
||||||
|
|
||||||
|
// Add default type and type pack parameters if that's required and it's possible
|
||||||
|
if (notEnoughParameters && hasDefaultParameters)
|
||||||
{
|
{
|
||||||
size_t typesProvided = typeParams.size();
|
// 'applyTypeFunction' is used to substitute default types that reference previous generic types
|
||||||
size_t typesRequired = tf->typeParams.size();
|
ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level};
|
||||||
|
|
||||||
size_t packsProvided = typePackParams.size();
|
for (size_t i = 0; i < typesProvided; ++i)
|
||||||
size_t packsRequired = tf->typePackParams.size();
|
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i];
|
||||||
|
|
||||||
bool notEnoughParameters =
|
if (typesProvided < typesRequired)
|
||||||
(typesProvided < typesRequired && packsProvided == 0) || (typesProvided == typesRequired && packsProvided < packsRequired);
|
|
||||||
bool hasDefaultParameters = hasDefaultTypes || hasDefaultPacks;
|
|
||||||
|
|
||||||
// Add default type and type pack parameters if that's required and it's possible
|
|
||||||
if (notEnoughParameters && hasDefaultParameters)
|
|
||||||
{
|
{
|
||||||
// 'applyTypeFunction' is used to substitute default types that reference previous generic types
|
for (size_t i = typesProvided; i < typesRequired; ++i)
|
||||||
ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level};
|
|
||||||
|
|
||||||
for (size_t i = 0; i < typesProvided; ++i)
|
|
||||||
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i];
|
|
||||||
|
|
||||||
if (typesProvided < typesRequired)
|
|
||||||
{
|
{
|
||||||
for (size_t i = typesProvided; i < typesRequired; ++i)
|
TypeId defaultTy = tf->typeParams[i].defaultValue.value_or(nullptr);
|
||||||
|
|
||||||
|
if (!defaultTy)
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(defaultTy);
|
||||||
|
|
||||||
|
if (!maybeInstantiated.has_value())
|
||||||
{
|
{
|
||||||
TypeId defaultTy = tf->typeParams[i].defaultValue.value_or(nullptr);
|
reportError(annotation.location, UnificationTooComplex{});
|
||||||
|
maybeInstantiated = errorRecoveryType(scope);
|
||||||
if (!defaultTy)
|
|
||||||
break;
|
|
||||||
|
|
||||||
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(defaultTy);
|
|
||||||
|
|
||||||
if (!maybeInstantiated.has_value())
|
|
||||||
{
|
|
||||||
reportError(annotation.location, UnificationTooComplex{});
|
|
||||||
maybeInstantiated = errorRecoveryType(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = *maybeInstantiated;
|
|
||||||
typeParams.push_back(*maybeInstantiated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = *maybeInstantiated;
|
||||||
|
typeParams.push_back(*maybeInstantiated);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < packsProvided; ++i)
|
for (size_t i = 0; i < packsProvided; ++i)
|
||||||
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = typePackParams[i];
|
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = typePackParams[i];
|
||||||
|
|
||||||
if (packsProvided < packsRequired)
|
if (packsProvided < packsRequired)
|
||||||
|
{
|
||||||
|
for (size_t i = packsProvided; i < packsRequired; ++i)
|
||||||
{
|
{
|
||||||
for (size_t i = packsProvided; i < packsRequired; ++i)
|
TypePackId defaultTp = tf->typePackParams[i].defaultValue.value_or(nullptr);
|
||||||
|
|
||||||
|
if (!defaultTp)
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::optional<TypePackId> maybeInstantiated = applyTypeFunction.substitute(defaultTp);
|
||||||
|
|
||||||
|
if (!maybeInstantiated.has_value())
|
||||||
{
|
{
|
||||||
TypePackId defaultTp = tf->typePackParams[i].defaultValue.value_or(nullptr);
|
reportError(annotation.location, UnificationTooComplex{});
|
||||||
|
maybeInstantiated = errorRecoveryTypePack(scope);
|
||||||
if (!defaultTp)
|
|
||||||
break;
|
|
||||||
|
|
||||||
std::optional<TypePackId> maybeInstantiated = applyTypeFunction.substitute(defaultTp);
|
|
||||||
|
|
||||||
if (!maybeInstantiated.has_value())
|
|
||||||
{
|
|
||||||
reportError(annotation.location, UnificationTooComplex{});
|
|
||||||
maybeInstantiated = errorRecoveryTypePack(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = *maybeInstantiated;
|
|
||||||
typePackParams.push_back(*maybeInstantiated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = *maybeInstantiated;
|
||||||
|
typePackParams.push_back(*maybeInstantiated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5343,12 +5288,12 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
|||||||
|
|
||||||
TypeId instantiated = *maybeInstantiated;
|
TypeId instantiated = *maybeInstantiated;
|
||||||
|
|
||||||
// TODO: CLI-46926 it's not a good idea to rename the type here
|
|
||||||
TypeId target = follow(instantiated);
|
TypeId target = follow(instantiated);
|
||||||
bool needsClone = follow(tf.type) == target;
|
bool needsClone = follow(tf.type) == target;
|
||||||
|
bool shouldMutate = (!FFlag::LuauOnlyMutateInstantiatedTables || getTableType(tf.type));
|
||||||
TableTypeVar* ttv = getMutableTableType(target);
|
TableTypeVar* ttv = getMutableTableType(target);
|
||||||
|
|
||||||
if (ttv && needsClone)
|
if (shouldMutate && ttv && needsClone)
|
||||||
{
|
{
|
||||||
// Substitution::clone is a shallow clone. If this is a metatable type, we
|
// Substitution::clone is a shallow clone. If this is a metatable type, we
|
||||||
// want to mutate its table, so we need to explicitly clone that table as
|
// want to mutate its table, so we need to explicitly clone that table as
|
||||||
@ -5368,7 +5313,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ttv)
|
if (shouldMutate && ttv)
|
||||||
{
|
{
|
||||||
ttv->instantiatedTypeParams = typeParams;
|
ttv->instantiatedTypeParams = typeParams;
|
||||||
ttv->instantiatedTypePackParams = typePackParams;
|
ttv->instantiatedTypePackParams = typePackParams;
|
||||||
@ -5382,7 +5327,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
|
|||||||
{
|
{
|
||||||
LUAU_ASSERT(scope->parent);
|
LUAU_ASSERT(scope->parent);
|
||||||
|
|
||||||
const TypeLevel level = (FFlag::LuauQuantifyInPlace2 && levelOpt) ? *levelOpt : scope->level;
|
const TypeLevel level = levelOpt.value_or(scope->level);
|
||||||
|
|
||||||
std::vector<GenericTypeDefinition> generics;
|
std::vector<GenericTypeDefinition> generics;
|
||||||
|
|
||||||
@ -5390,7 +5335,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
|
|||||||
{
|
{
|
||||||
std::optional<TypeId> defaultValue;
|
std::optional<TypeId> defaultValue;
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults && generic.defaultValue)
|
if (generic.defaultValue)
|
||||||
defaultValue = resolveType(scope, *generic.defaultValue);
|
defaultValue = resolveType(scope, *generic.defaultValue);
|
||||||
|
|
||||||
Name n = generic.name.value;
|
Name n = generic.name.value;
|
||||||
@ -5426,7 +5371,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
|
|||||||
{
|
{
|
||||||
std::optional<TypePackId> defaultValue;
|
std::optional<TypePackId> defaultValue;
|
||||||
|
|
||||||
if (FFlag::LuauTypeAliasDefaults && genericPack.defaultValue)
|
if (genericPack.defaultValue)
|
||||||
defaultValue = resolveTypePack(scope, *genericPack.defaultValue);
|
defaultValue = resolveTypePack(scope, *genericPack.defaultValue);
|
||||||
|
|
||||||
Name n = genericPack.name.value;
|
Name n = genericPack.name.value;
|
||||||
|
@ -24,6 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
|||||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||||
|
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
||||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -157,6 +158,7 @@ bool isNumber(TypeId ty)
|
|||||||
return isPrim(ty, PrimitiveTypeVar::Number);
|
return isPrim(ty, PrimitiveTypeVar::Number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true when ty is a subtype of string
|
||||||
bool isString(TypeId ty)
|
bool isString(TypeId ty)
|
||||||
{
|
{
|
||||||
if (isPrim(ty, PrimitiveTypeVar::String) || get<StringSingleton>(get<SingletonTypeVar>(follow(ty))))
|
if (isPrim(ty, PrimitiveTypeVar::String) || get<StringSingleton>(get<SingletonTypeVar>(follow(ty))))
|
||||||
@ -168,6 +170,27 @@ bool isString(TypeId ty)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true when ty is a supertype of string
|
||||||
|
bool maybeString(TypeId ty)
|
||||||
|
{
|
||||||
|
if (FFlag::LuauSubtypingAddOptPropsToUnsealedTables)
|
||||||
|
{
|
||||||
|
ty = follow(ty);
|
||||||
|
|
||||||
|
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (auto utv = get<UnionTypeVar>(ty))
|
||||||
|
return std::any_of(begin(utv), end(utv), maybeString);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return isString(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isThread(TypeId ty)
|
bool isThread(TypeId ty)
|
||||||
{
|
{
|
||||||
return isPrim(ty, PrimitiveTypeVar::Thread);
|
return isPrim(ty, PrimitiveTypeVar::Thread);
|
||||||
@ -684,7 +707,7 @@ TypeId SingletonTypes::makeStringMetatable()
|
|||||||
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}},
|
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}},
|
||||||
{"upper", {stringToStringType}},
|
{"upper", {stringToStringType}},
|
||||||
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
|
{"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {},
|
||||||
{arena->addType(TableTypeVar{{}, TableIndexer{numberType, stringType}, TypeLevel{}})})}},
|
{arena->addType(TableTypeVar{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})})}},
|
||||||
{"pack", {arena->addType(FunctionTypeVar{
|
{"pack", {arena->addType(FunctionTypeVar{
|
||||||
arena->addTypePack(TypePack{{stringType}, anyTypePack}),
|
arena->addTypePack(TypePack{{stringType}, anyTypePack}),
|
||||||
oneStringPack,
|
oneStringPack,
|
||||||
@ -761,6 +784,8 @@ void persist(TypeId ty)
|
|||||||
}
|
}
|
||||||
else if (auto ttv = get<TableTypeVar>(t))
|
else if (auto ttv = get<TableTypeVar>(t))
|
||||||
{
|
{
|
||||||
|
LUAU_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed);
|
||||||
|
|
||||||
for (const auto& [_name, prop] : ttv->props)
|
for (const auto& [_name, prop] : ttv->props)
|
||||||
queue.push_back(prop.type);
|
queue.push_back(prop.type);
|
||||||
|
|
||||||
|
@ -18,11 +18,13 @@ LUAU_FASTFLAG(LuauImmutableTypes)
|
|||||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
|
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
|
|
||||||
LUAU_FASTFLAG(LuauSingletonTypes)
|
LUAU_FASTFLAG(LuauSingletonTypes)
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
|
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree, false)
|
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, true)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -329,7 +331,7 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance
|
|||||||
LUAU_ASSERT(sharedState.iceHandler);
|
LUAU_ASSERT(sharedState.iceHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
Unifier::Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
Unifier::Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, const Location& location,
|
||||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
||||||
: types(types)
|
: types(types)
|
||||||
, mode(mode)
|
, mode(mode)
|
||||||
@ -656,26 +658,85 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
|
|||||||
failed = true;
|
failed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauUseCommittingTxnLog)
|
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter)
|
||||||
{
|
{
|
||||||
if (i == count - 1)
|
if (!FFlag::LuauUseCommittingTxnLog)
|
||||||
{
|
innerState.DEPRECATED_log.rollback();
|
||||||
log.concat(std::move(innerState.log));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (i != count - 1)
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
{
|
{
|
||||||
innerState.DEPRECATED_log.rollback();
|
if (i == count - 1)
|
||||||
|
{
|
||||||
|
log.concat(std::move(innerState.log));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
if (i != count - 1)
|
||||||
|
{
|
||||||
|
innerState.DEPRECATED_log.rollback();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
++i;
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option.
|
||||||
|
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter)
|
||||||
|
{
|
||||||
|
auto tryBind = [this, subTy](TypeId superOption) {
|
||||||
|
superOption = FFlag::LuauUseCommittingTxnLog ? log.follow(superOption) : follow(superOption);
|
||||||
|
|
||||||
|
// just skip if the superOption is not free-ish.
|
||||||
|
auto ttv = log.getMutable<TableTypeVar>(superOption);
|
||||||
|
if (!log.is<FreeTypeVar>(superOption) && (!ttv || ttv->state != TableState::Free))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Since we have already checked if S <: T, checking it again will not queue up the type for replacement.
|
||||||
|
// So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set.
|
||||||
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
|
{
|
||||||
|
if (log.haveSeen(subTy, superOption))
|
||||||
|
{
|
||||||
|
// TODO: would it be nice for TxnLog::replace to do this?
|
||||||
|
if (log.is<TableTypeVar>(superOption))
|
||||||
|
log.bindTable(superOption, subTy);
|
||||||
|
else
|
||||||
|
log.replace(superOption, *subTy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (DEPRECATED_log.haveSeen(subTy, superOption))
|
||||||
|
{
|
||||||
|
if (auto ttv = getMutable<TableTypeVar>(superOption))
|
||||||
|
{
|
||||||
|
DEPRECATED_log(ttv);
|
||||||
|
ttv->boundTo = subTy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DEPRECATED_log(superOption);
|
||||||
|
*asMutable(superOption) = BoundTypeVar(subTy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (auto utv = (FFlag::LuauUseCommittingTxnLog ? log.getMutable<UnionTypeVar>(superTy) : get<UnionTypeVar>(superTy)))
|
||||||
|
{
|
||||||
|
for (TypeId ty : utv)
|
||||||
|
tryBind(ty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
tryBind(superTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unificationTooComplex)
|
if (unificationTooComplex)
|
||||||
@ -1163,6 +1224,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||||||
if (superTp == subTp)
|
if (superTp == subTp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (FFlag::LuauTxnLogSeesTypePacks2 && log.haveSeen(superTp, subTp))
|
||||||
|
return;
|
||||||
|
|
||||||
if (log.getMutable<Unifiable::Free>(superTp))
|
if (log.getMutable<Unifiable::Free>(superTp))
|
||||||
{
|
{
|
||||||
occursCheck(superTp, subTp);
|
occursCheck(superTp, subTp);
|
||||||
@ -1365,6 +1429,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||||||
if (superTp == subTp)
|
if (superTp == subTp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (FFlag::LuauTxnLogSeesTypePacks2 && DEPRECATED_log.haveSeen(superTp, subTp))
|
||||||
|
return;
|
||||||
|
|
||||||
if (get<Unifiable::Free>(superTp))
|
if (get<Unifiable::Free>(superTp))
|
||||||
{
|
{
|
||||||
occursCheck(superTp, subTp);
|
occursCheck(superTp, subTp);
|
||||||
@ -1619,6 +1686,17 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||||||
DEPRECATED_log.pushSeen(superFunction->generics[i], subFunction->generics[i]);
|
DEPRECATED_log.pushSeen(superFunction->generics[i], subFunction->generics[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauTxnLogSeesTypePacks2)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < numGenericPacks; i++)
|
||||||
|
{
|
||||||
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
|
log.pushSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]);
|
||||||
|
else
|
||||||
|
DEPRECATED_log.pushSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CountMismatch::Context context = ctx;
|
CountMismatch::Context context = ctx;
|
||||||
|
|
||||||
if (!isFunctionCall)
|
if (!isFunctionCall)
|
||||||
@ -1708,6 +1786,17 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||||||
|
|
||||||
ctx = context;
|
ctx = context;
|
||||||
|
|
||||||
|
if (FFlag::LuauTxnLogSeesTypePacks2)
|
||||||
|
{
|
||||||
|
for (int i = int(numGenericPacks) - 1; 0 <= i; i--)
|
||||||
|
{
|
||||||
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
|
log.popSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]);
|
||||||
|
else
|
||||||
|
DEPRECATED_log.popSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = int(numGenerics) - 1; 0 <= i; i--)
|
for (int i = int(numGenerics) - 1; 0 <= i; i--)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauUseCommittingTxnLog)
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
@ -1760,7 +1849,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
std::vector<std::string> extraProperties;
|
std::vector<std::string> extraProperties;
|
||||||
|
|
||||||
// Optimization: First test that the property sets are compatible without doing any recursive unification
|
// Optimization: First test that the property sets are compatible without doing any recursive unification
|
||||||
if (FFlag::LuauTableUnificationEarlyTest && !subTable->indexer && subTable->state != TableState::Free)
|
if (!subTable->indexer && subTable->state != TableState::Free)
|
||||||
{
|
{
|
||||||
for (const auto& [propName, superProp] : superTable->props)
|
for (const auto& [propName, superProp] : superTable->props)
|
||||||
{
|
{
|
||||||
@ -1769,7 +1858,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
bool isAny =
|
bool isAny =
|
||||||
FFlag::LuauUseCommittingTxnLog ? log.getMutable<AnyTypeVar>(log.follow(superProp.type)) : get<AnyTypeVar>(follow(superProp.type));
|
FFlag::LuauUseCommittingTxnLog ? log.getMutable<AnyTypeVar>(log.follow(superProp.type)) : get<AnyTypeVar>(follow(superProp.type));
|
||||||
|
|
||||||
if (subIter == subTable->props.end() && !isOptional(superProp.type) && !isAny)
|
if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) && !isAny)
|
||||||
missingProperties.push_back(propName);
|
missingProperties.push_back(propName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1781,7 +1870,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// And vice versa if we're invariant
|
// And vice versa if we're invariant
|
||||||
if (FFlag::LuauTableUnificationEarlyTest && variance == Invariant && !superTable->indexer && superTable->state != TableState::Unsealed &&
|
if (variance == Invariant && !superTable->indexer && superTable->state != TableState::Unsealed &&
|
||||||
superTable->state != TableState::Free)
|
superTable->state != TableState::Free)
|
||||||
{
|
{
|
||||||
for (const auto& [propName, subProp] : subTable->props)
|
for (const auto& [propName, subProp] : subTable->props)
|
||||||
@ -1790,7 +1879,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
|
|
||||||
bool isAny =
|
bool isAny =
|
||||||
FFlag::LuauUseCommittingTxnLog ? log.getMutable<AnyTypeVar>(log.follow(subProp.type)) : get<AnyTypeVar>(follow(subProp.type));
|
FFlag::LuauUseCommittingTxnLog ? log.getMutable<AnyTypeVar>(log.follow(subProp.type)) : get<AnyTypeVar>(follow(subProp.type));
|
||||||
if (superIter == superTable->props.end() && !isOptional(subProp.type) && !isAny)
|
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny)))
|
||||||
extraProperties.push_back(propName);
|
extraProperties.push_back(propName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1830,7 +1919,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
innerState.DEPRECATED_log.rollback();
|
innerState.DEPRECATED_log.rollback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (subTable->indexer && isString(subTable->indexer->indexType))
|
else if (subTable->indexer && maybeString(subTable->indexer->indexType))
|
||||||
{
|
{
|
||||||
// TODO: read-only indexers don't need invariance
|
// TODO: read-only indexers don't need invariance
|
||||||
// TODO: really we should only allow this if prop.type is optional.
|
// TODO: really we should only allow this if prop.type is optional.
|
||||||
@ -1855,9 +1944,11 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
innerState.DEPRECATED_log.rollback();
|
innerState.DEPRECATED_log.rollback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type)))
|
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
|
||||||
// TODO: this case is unsound, but without it our test suite fails. CLI-46031
|
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
|
||||||
|
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
|
||||||
// TODO: should isOptional(anyType) be true?
|
// TODO: should isOptional(anyType) be true?
|
||||||
|
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else if (subTable->state == TableState::Free)
|
else if (subTable->state == TableState::Free)
|
||||||
@ -1887,7 +1978,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
// If both lt and rt contain the property, then
|
// If both lt and rt contain the property, then
|
||||||
// we're done since we already unified them above
|
// we're done since we already unified them above
|
||||||
}
|
}
|
||||||
else if (superTable->indexer && isString(superTable->indexer->indexType))
|
else if (superTable->indexer && maybeString(superTable->indexer->indexType))
|
||||||
{
|
{
|
||||||
// TODO: read-only indexers don't need invariance
|
// TODO: read-only indexers don't need invariance
|
||||||
// TODO: really we should only allow this if prop.type is optional.
|
// TODO: really we should only allow this if prop.type is optional.
|
||||||
@ -1936,9 +2027,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
else if (variance == Covariant)
|
else if (variance == Covariant)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else if (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type)))
|
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
|
||||||
// TODO: this case is unsound, but without it our test suite fails. CLI-46031
|
|
||||||
// TODO: should isOptional(anyType) be true?
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else if (superTable->state == TableState::Free)
|
else if (superTable->state == TableState::Free)
|
||||||
@ -2333,7 +2422,7 @@ void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersec
|
|||||||
bool errorReported = false;
|
bool errorReported = false;
|
||||||
|
|
||||||
// Optimization: First test that the property sets are compatible without doing any recursive unification
|
// Optimization: First test that the property sets are compatible without doing any recursive unification
|
||||||
if (FFlag::LuauTableUnificationEarlyTest && !subTable->indexer)
|
if (!subTable->indexer)
|
||||||
{
|
{
|
||||||
for (const auto& [propName, superProp] : superTable->props)
|
for (const auto& [propName, superProp] : superTable->props)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false)
|
LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
|
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
|
||||||
|
|
||||||
@ -779,7 +778,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
|
|||||||
if (!name)
|
if (!name)
|
||||||
name = Name(nameError, lexer.current().location);
|
name = Name(nameError, lexer.current().location);
|
||||||
|
|
||||||
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ FFlag::LuauParseTypeAliasDefaults);
|
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ true);
|
||||||
|
|
||||||
expectAndConsume('=', "type alias");
|
expectAndConsume('=', "type alias");
|
||||||
|
|
||||||
|
35
CLI/Repl.cpp
35
CLI/Repl.cpp
@ -413,29 +413,22 @@ static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer)
|
|||||||
ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar);
|
ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LinenoiseScopedHistory
|
static void loadHistory(const char* name)
|
||||||
{
|
{
|
||||||
LinenoiseScopedHistory()
|
std::string path;
|
||||||
|
|
||||||
|
if (const char* home = getenv("HOME"))
|
||||||
{
|
{
|
||||||
const std::string name(".luau_history");
|
path = joinPaths(home, name);
|
||||||
|
}
|
||||||
if (const char* home = getenv("HOME"))
|
else if (const char* userProfile = getenv("USERPROFILE"))
|
||||||
{
|
{
|
||||||
historyFilepath = joinPaths(home, name);
|
path = joinPaths(userProfile, name);
|
||||||
}
|
|
||||||
else if (const char* userProfile = getenv("USERPROFILE"))
|
|
||||||
{
|
|
||||||
historyFilepath = joinPaths(userProfile, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!historyFilepath.empty())
|
|
||||||
ic_set_history(historyFilepath.c_str(), -1 /* default entries (= 200) */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~LinenoiseScopedHistory() {}
|
if (!path.empty())
|
||||||
|
ic_set_history(path.c_str(), -1 /* default entries (= 200) */);
|
||||||
std::string historyFilepath;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
static void runReplImpl(lua_State* L)
|
static void runReplImpl(lua_State* L)
|
||||||
{
|
{
|
||||||
@ -447,8 +440,10 @@ static void runReplImpl(lua_State* L)
|
|||||||
// Prevent auto insertion of braces
|
// Prevent auto insertion of braces
|
||||||
ic_enable_brace_insertion(false);
|
ic_enable_brace_insertion(false);
|
||||||
|
|
||||||
|
// Loads history from the given file; isocline automatically saves the history on process exit
|
||||||
|
loadHistory(".luau_history");
|
||||||
|
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
LinenoiseScopedHistory scopedHistory;
|
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
@ -265,6 +265,8 @@ LUA_API double lua_clock();
|
|||||||
|
|
||||||
LUA_API void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*));
|
LUA_API void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*));
|
||||||
|
|
||||||
|
LUA_API void lua_clonefunction(lua_State* L, int idx);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** reference system, can be used to pin objects
|
** reference system, can be used to pin objects
|
||||||
*/
|
*/
|
||||||
@ -324,6 +326,7 @@ typedef struct lua_Debug lua_Debug; /* activation record */
|
|||||||
/* Functions to be called by the debugger in specific events */
|
/* Functions to be called by the debugger in specific events */
|
||||||
typedef void (*lua_Hook)(lua_State* L, lua_Debug* ar);
|
typedef void (*lua_Hook)(lua_State* L, lua_Debug* ar);
|
||||||
|
|
||||||
|
LUA_API int lua_stackdepth(lua_State* L);
|
||||||
LUA_API int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar);
|
LUA_API int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar);
|
||||||
LUA_API int lua_getargument(lua_State* L, int level, int n);
|
LUA_API int lua_getargument(lua_State* L, int level, int n);
|
||||||
LUA_API const char* lua_getlocal(lua_State* L, int level, int n);
|
LUA_API const char* lua_getlocal(lua_State* L, int level, int n);
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauGcForwardMetatableBarrier, false)
|
LUAU_FASTFLAG(LuauGcAdditionalStats)
|
||||||
|
|
||||||
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
||||||
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
|
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
|
||||||
@ -876,16 +876,7 @@ int lua_setmetatable(lua_State* L, int objindex)
|
|||||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||||
hvalue(obj)->metatable = mt;
|
hvalue(obj)->metatable = mt;
|
||||||
if (mt)
|
if (mt)
|
||||||
{
|
luaC_objbarrier(L, hvalue(obj), mt);
|
||||||
if (FFlag::LuauGcForwardMetatableBarrier)
|
|
||||||
{
|
|
||||||
luaC_objbarrier(L, hvalue(obj), mt);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
luaC_objbarriert(L, hvalue(obj), mt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_TUSERDATA:
|
case LUA_TUSERDATA:
|
||||||
@ -1069,6 +1060,8 @@ int lua_gc(lua_State* L, int what, int data)
|
|||||||
g->GCthreshold = 0;
|
g->GCthreshold = 0;
|
||||||
|
|
||||||
bool waspaused = g->gcstate == GCSpause;
|
bool waspaused = g->gcstate == GCSpause;
|
||||||
|
double startmarktime = g->gcstats.currcycle.marktime;
|
||||||
|
double startsweeptime = g->gcstats.currcycle.sweeptime;
|
||||||
|
|
||||||
// track how much work the loop will actually perform
|
// track how much work the loop will actually perform
|
||||||
size_t actualwork = 0;
|
size_t actualwork = 0;
|
||||||
@ -1086,6 +1079,31 @@ int lua_gc(lua_State* L, int what, int data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
{
|
||||||
|
// record explicit step statistics
|
||||||
|
GCCycleStats* cyclestats = g->gcstate == GCSpause ? &g->gcstats.lastcycle : &g->gcstats.currcycle;
|
||||||
|
|
||||||
|
double totalmarktime = cyclestats->marktime - startmarktime;
|
||||||
|
double totalsweeptime = cyclestats->sweeptime - startsweeptime;
|
||||||
|
|
||||||
|
if (totalmarktime > 0.0)
|
||||||
|
{
|
||||||
|
cyclestats->markexplicitsteps++;
|
||||||
|
|
||||||
|
if (totalmarktime > cyclestats->markmaxexplicittime)
|
||||||
|
cyclestats->markmaxexplicittime = totalmarktime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalsweeptime > 0.0)
|
||||||
|
{
|
||||||
|
cyclestats->sweepexplicitsteps++;
|
||||||
|
|
||||||
|
if (totalsweeptime > cyclestats->sweepmaxexplicittime)
|
||||||
|
cyclestats->sweepmaxexplicittime = totalsweeptime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if cycle hasn't finished, advance threshold forward for the amount of extra work performed
|
// if cycle hasn't finished, advance threshold forward for the amount of extra work performed
|
||||||
if (g->gcstate != GCSpause)
|
if (g->gcstate != GCSpause)
|
||||||
{
|
{
|
||||||
@ -1299,6 +1317,18 @@ void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*))
|
|||||||
L->global->udatagc[tag] = dtor;
|
L->global->udatagc[tag] = dtor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LUA_API void lua_clonefunction(lua_State* L, int idx)
|
||||||
|
{
|
||||||
|
StkId p = index2addr(L, idx);
|
||||||
|
api_check(L, isLfunction(p));
|
||||||
|
|
||||||
|
luaC_checkthreadsleep(L);
|
||||||
|
|
||||||
|
Closure* cl = clvalue(p);
|
||||||
|
Closure* newcl = luaF_newLclosure(L, 0, L->gt, cl->l.p);
|
||||||
|
setclvalue(L, L->top - 1, newcl);
|
||||||
|
}
|
||||||
|
|
||||||
lua_Callbacks* lua_callbacks(lua_State* L)
|
lua_Callbacks* lua_callbacks(lua_State* L)
|
||||||
{
|
{
|
||||||
return &L->global->cb;
|
return &L->global->cb;
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSchubfach)
|
|
||||||
|
|
||||||
/* convert a stack index to positive */
|
/* convert a stack index to positive */
|
||||||
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
|
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
|
||||||
|
|
||||||
@ -480,18 +478,13 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
|||||||
switch (lua_type(L, idx))
|
switch (lua_type(L, idx))
|
||||||
{
|
{
|
||||||
case LUA_TNUMBER:
|
case LUA_TNUMBER:
|
||||||
if (FFlag::LuauSchubfach)
|
{
|
||||||
{
|
double n = lua_tonumber(L, idx);
|
||||||
double n = lua_tonumber(L, idx);
|
char s[LUAI_MAXNUM2STR];
|
||||||
char s[LUAI_MAXNUM2STR];
|
char* e = luai_num2str(s, n);
|
||||||
char* e = luai_num2str(s, n);
|
lua_pushlstring(L, s, e - s);
|
||||||
lua_pushlstring(L, s, e - s);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
lua_pushstring(L, lua_tostring(L, idx));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case LUA_TSTRING:
|
case LUA_TSTRING:
|
||||||
lua_pushvalue(L, idx);
|
lua_pushvalue(L, idx);
|
||||||
break;
|
break;
|
||||||
@ -505,29 +498,18 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
|||||||
{
|
{
|
||||||
const float* v = lua_tovector(L, idx);
|
const float* v = lua_tovector(L, idx);
|
||||||
|
|
||||||
if (FFlag::LuauSchubfach)
|
char s[LUAI_MAXNUM2STR * LUA_VECTOR_SIZE];
|
||||||
|
char* e = s;
|
||||||
|
for (int i = 0; i < LUA_VECTOR_SIZE; ++i)
|
||||||
{
|
{
|
||||||
char s[LUAI_MAXNUM2STR * LUA_VECTOR_SIZE];
|
if (i != 0)
|
||||||
char* e = s;
|
|
||||||
for (int i = 0; i < LUA_VECTOR_SIZE; ++i)
|
|
||||||
{
|
{
|
||||||
if (i != 0)
|
*e++ = ',';
|
||||||
{
|
*e++ = ' ';
|
||||||
*e++ = ',';
|
|
||||||
*e++ = ' ';
|
|
||||||
}
|
|
||||||
e = luai_num2str(e, v[i]);
|
|
||||||
}
|
}
|
||||||
lua_pushlstring(L, s, e - s);
|
e = luai_num2str(e, v[i]);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if LUA_VECTOR_SIZE == 4
|
|
||||||
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]);
|
|
||||||
#else
|
|
||||||
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
lua_pushlstring(L, s, e - s);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -168,6 +168,11 @@ static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f,
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lua_stackdepth(lua_State* L)
|
||||||
|
{
|
||||||
|
return int(L->ci - L->base_ci);
|
||||||
|
}
|
||||||
|
|
||||||
int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
|
int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
|
||||||
{
|
{
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
#include "lmem.h"
|
#include "lmem.h"
|
||||||
#include "ludata.h"
|
#include "ludata.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauGcAdditionalStats, false)
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define GC_SWEEPMAX 40
|
#define GC_SWEEPMAX 40
|
||||||
@ -53,17 +55,28 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
|
|||||||
case GCSpause:
|
case GCSpause:
|
||||||
// record root mark time if we have switched to next state
|
// record root mark time if we have switched to next state
|
||||||
if (g->gcstate == GCSpropagate)
|
if (g->gcstate == GCSpropagate)
|
||||||
|
{
|
||||||
g->gcstats.currcycle.marktime += seconds;
|
g->gcstats.currcycle.marktime += seconds;
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats && assist)
|
||||||
|
g->gcstats.currcycle.markassisttime += seconds;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case GCSpropagate:
|
case GCSpropagate:
|
||||||
case GCSpropagateagain:
|
case GCSpropagateagain:
|
||||||
g->gcstats.currcycle.marktime += seconds;
|
g->gcstats.currcycle.marktime += seconds;
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats && assist)
|
||||||
|
g->gcstats.currcycle.markassisttime += seconds;
|
||||||
break;
|
break;
|
||||||
case GCSatomic:
|
case GCSatomic:
|
||||||
g->gcstats.currcycle.atomictime += seconds;
|
g->gcstats.currcycle.atomictime += seconds;
|
||||||
break;
|
break;
|
||||||
case GCSsweep:
|
case GCSsweep:
|
||||||
g->gcstats.currcycle.sweeptime += seconds;
|
g->gcstats.currcycle.sweeptime += seconds;
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats && assist)
|
||||||
|
g->gcstats.currcycle.sweepassisttime += seconds;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LUAU_ASSERT(!"Unexpected GC state");
|
LUAU_ASSERT(!"Unexpected GC state");
|
||||||
@ -78,7 +91,7 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
|
|||||||
static void startGcCycleStats(global_State* g)
|
static void startGcCycleStats(global_State* g)
|
||||||
{
|
{
|
||||||
g->gcstats.currcycle.starttimestamp = lua_clock();
|
g->gcstats.currcycle.starttimestamp = lua_clock();
|
||||||
g->gcstats.currcycle.waittime = g->gcstats.currcycle.starttimestamp - g->gcstats.lastcycle.endtimestamp;
|
g->gcstats.currcycle.pausetime = g->gcstats.currcycle.starttimestamp - g->gcstats.lastcycle.endtimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void finishGcCycleStats(global_State* g)
|
static void finishGcCycleStats(global_State* g)
|
||||||
@ -585,10 +598,21 @@ static size_t atomic(lua_State* L)
|
|||||||
LUAU_ASSERT(g->gcstate == GCSatomic);
|
LUAU_ASSERT(g->gcstate == GCSatomic);
|
||||||
|
|
||||||
size_t work = 0;
|
size_t work = 0;
|
||||||
|
double currts = lua_clock();
|
||||||
|
double prevts = currts;
|
||||||
|
|
||||||
/* remark occasional upvalues of (maybe) dead threads */
|
/* remark occasional upvalues of (maybe) dead threads */
|
||||||
work += remarkupvals(g);
|
work += remarkupvals(g);
|
||||||
/* traverse objects caught by write barrier and by 'remarkupvals' */
|
/* traverse objects caught by write barrier and by 'remarkupvals' */
|
||||||
work += propagateall(g);
|
work += propagateall(g);
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
{
|
||||||
|
currts = lua_clock();
|
||||||
|
g->gcstats.currcycle.atomictimeupval += currts - prevts;
|
||||||
|
prevts = currts;
|
||||||
|
}
|
||||||
|
|
||||||
/* remark weak tables */
|
/* remark weak tables */
|
||||||
g->gray = g->weak;
|
g->gray = g->weak;
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
@ -596,16 +620,41 @@ static size_t atomic(lua_State* L)
|
|||||||
markobject(g, L); /* mark running thread */
|
markobject(g, L); /* mark running thread */
|
||||||
markmt(g); /* mark basic metatables (again) */
|
markmt(g); /* mark basic metatables (again) */
|
||||||
work += propagateall(g);
|
work += propagateall(g);
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
{
|
||||||
|
currts = lua_clock();
|
||||||
|
g->gcstats.currcycle.atomictimeweak += currts - prevts;
|
||||||
|
prevts = currts;
|
||||||
|
}
|
||||||
|
|
||||||
/* remark gray again */
|
/* remark gray again */
|
||||||
g->gray = g->grayagain;
|
g->gray = g->grayagain;
|
||||||
g->grayagain = NULL;
|
g->grayagain = NULL;
|
||||||
work += propagateall(g);
|
work += propagateall(g);
|
||||||
work += cleartable(L, g->weak); /* remove collected objects from weak tables */
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
{
|
||||||
|
currts = lua_clock();
|
||||||
|
g->gcstats.currcycle.atomictimegray += currts - prevts;
|
||||||
|
prevts = currts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove collected objects from weak tables */
|
||||||
|
work += cleartable(L, g->weak);
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
{
|
||||||
|
currts = lua_clock();
|
||||||
|
g->gcstats.currcycle.atomictimeclear += currts - prevts;
|
||||||
|
}
|
||||||
|
|
||||||
/* flip current white */
|
/* flip current white */
|
||||||
g->currentwhite = cast_byte(otherwhite(g));
|
g->currentwhite = cast_byte(otherwhite(g));
|
||||||
g->sweepgcopage = g->allgcopages;
|
g->sweepgcopage = g->allgcopages;
|
||||||
g->gcstate = GCSsweep;
|
g->gcstate = GCSsweep;
|
||||||
|
|
||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,6 +742,9 @@ static size_t gcstep(lua_State* L, size_t limit)
|
|||||||
|
|
||||||
if (!g->gray)
|
if (!g->gray)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
g->gcstats.currcycle.propagatework = g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork;
|
||||||
|
|
||||||
// perform one iteration over 'gray again' list
|
// perform one iteration over 'gray again' list
|
||||||
g->gray = g->grayagain;
|
g->gray = g->grayagain;
|
||||||
g->grayagain = NULL;
|
g->grayagain = NULL;
|
||||||
@ -710,6 +762,10 @@ static size_t gcstep(lua_State* L, size_t limit)
|
|||||||
|
|
||||||
if (!g->gray) /* no more `gray' objects */
|
if (!g->gray) /* no more `gray' objects */
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
g->gcstats.currcycle.propagateagainwork =
|
||||||
|
g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork - g->gcstats.currcycle.propagatework;
|
||||||
|
|
||||||
g->gcstate = GCSatomic;
|
g->gcstate = GCSatomic;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -811,6 +867,12 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal)
|
|||||||
void luaC_step(lua_State* L, bool assist)
|
void luaC_step(lua_State* L, bool assist)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
if (assist)
|
||||||
|
g->gcstats.currcycle.assistrequests += g->gcstepsize;
|
||||||
|
else
|
||||||
|
g->gcstats.currcycle.explicitrequests += g->gcstepsize;
|
||||||
|
|
||||||
int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
|
int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
|
||||||
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
|
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
|
||||||
size_t debt = g->totalbytes - g->GCthreshold;
|
size_t debt = g->totalbytes - g->GCthreshold;
|
||||||
@ -833,6 +895,11 @@ void luaC_step(lua_State* L, bool assist)
|
|||||||
|
|
||||||
recordGcStateTime(g, lastgcstate, lua_clock() - lasttimestamp, assist);
|
recordGcStateTime(g, lastgcstate, lua_clock() - lasttimestamp, assist);
|
||||||
|
|
||||||
|
if (lastgcstate == GCSpropagate)
|
||||||
|
g->gcstats.currcycle.markrequests += g->gcstepsize;
|
||||||
|
else if (lastgcstate == GCSsweep)
|
||||||
|
g->gcstats.currcycle.sweeprequests += g->gcstepsize;
|
||||||
|
|
||||||
// at the end of the last cycle
|
// at the end of the last cycle
|
||||||
if (g->gcstate == GCSpause)
|
if (g->gcstate == GCSpause)
|
||||||
{
|
{
|
||||||
@ -844,6 +911,9 @@ void luaC_step(lua_State* L, bool assist)
|
|||||||
|
|
||||||
finishGcCycleStats(g);
|
finishGcCycleStats(g);
|
||||||
|
|
||||||
|
if (FFlag::LuauGcAdditionalStats)
|
||||||
|
g->gcstats.currcycle.starttotalsizebytes = g->totalbytes;
|
||||||
|
|
||||||
g->gcstats.currcycle.heapgoalsizebytes = heapgoal;
|
g->gcstats.currcycle.heapgoalsizebytes = heapgoal;
|
||||||
g->gcstats.currcycle.heaptriggersizebytes = heaptrigger;
|
g->gcstats.currcycle.heaptriggersizebytes = heaptrigger;
|
||||||
}
|
}
|
||||||
|
@ -111,13 +111,6 @@
|
|||||||
luaC_barrierf(L, obj2gco(p), obj2gco(o)); \
|
luaC_barrierf(L, obj2gco(p), obj2gco(o)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove with FFlagLuauGcForwardMetatableBarrier
|
|
||||||
#define luaC_objbarriert(L, t, o) \
|
|
||||||
{ \
|
|
||||||
if (isblack(obj2gco(t)) && iswhite(obj2gco(o))) \
|
|
||||||
luaC_barriertable(L, t, obj2gco(o)); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define luaC_upvalbarrier(L, uv, tv) \
|
#define luaC_upvalbarrier(L, uv, tv) \
|
||||||
{ \
|
{ \
|
||||||
if (iscollectable(tv) && iswhite(gcvalue(tv)) && (!(uv) || ((UpVal*)uv)->v != &((UpVal*)uv)->u.value)) \
|
if (iscollectable(tv) && iswhite(gcvalue(tv)) && (!(uv) || ((UpVal*)uv)->v != &((UpVal*)uv)->u.value)) \
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
#include "lcommon.h"
|
#include "lcommon.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h> // TODO: Remove with LuauSchubfach
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
@ -18,8 +17,6 @@
|
|||||||
|
|
||||||
// The code uses the notation from the paper for local variables where appropriate, and refers to paper sections/figures/results.
|
// The code uses the notation from the paper for local variables where appropriate, and refers to paper sections/figures/results.
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSchubfach, false)
|
|
||||||
|
|
||||||
// 9.8.2. Precomputed table for 128-bit overestimates of powers of 10 (see figure 3 for table bounds)
|
// 9.8.2. Precomputed table for 128-bit overestimates of powers of 10 (see figure 3 for table bounds)
|
||||||
// To avoid storing 616 128-bit numbers directly we use a technique inspired by Dragonbox implementation and store 16 consecutive
|
// To avoid storing 616 128-bit numbers directly we use a technique inspired by Dragonbox implementation and store 16 consecutive
|
||||||
// powers using a 128-bit baseline and a bitvector with 1-bit scale and 3-bit offset for the delta between each entry and base*5^k
|
// powers using a 128-bit baseline and a bitvector with 1-bit scale and 3-bit offset for the delta between each entry and base*5^k
|
||||||
@ -275,12 +272,6 @@ inline char* trimzero(char* end)
|
|||||||
|
|
||||||
char* luai_num2str(char* buf, double n)
|
char* luai_num2str(char* buf, double n)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauSchubfach)
|
|
||||||
{
|
|
||||||
snprintf(buf, LUAI_MAXNUM2STR, LUA_NUMBER_FMT, n);
|
|
||||||
return buf + strlen(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// IEEE-754
|
// IEEE-754
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
|
@ -55,7 +55,6 @@ LUAU_FASTMATH_END
|
|||||||
#define luai_num2unsigned(i, n) ((i) = (unsigned)(long long)(n))
|
#define luai_num2unsigned(i, n) ((i) = (unsigned)(long long)(n))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define LUA_NUMBER_FMT "%.14g" /* TODO: Remove with LuauSchubfach */
|
|
||||||
#define LUAI_MAXNUM2STR 48
|
#define LUAI_MAXNUM2STR 48
|
||||||
|
|
||||||
LUAI_FUNC char* luai_num2str(char* buf, double n);
|
LUAI_FUNC char* luai_num2str(char* buf, double n);
|
||||||
|
@ -77,25 +77,46 @@ typedef struct CallInfo
|
|||||||
|
|
||||||
struct GCCycleStats
|
struct GCCycleStats
|
||||||
{
|
{
|
||||||
|
size_t starttotalsizebytes = 0;
|
||||||
size_t heapgoalsizebytes = 0;
|
size_t heapgoalsizebytes = 0;
|
||||||
size_t heaptriggersizebytes = 0;
|
size_t heaptriggersizebytes = 0;
|
||||||
|
|
||||||
double waittime = 0.0; // time from end of the last cycle to the start of a new one
|
double pausetime = 0.0; // time from end of the last cycle to the start of a new one
|
||||||
|
|
||||||
double starttimestamp = 0.0;
|
double starttimestamp = 0.0;
|
||||||
double endtimestamp = 0.0;
|
double endtimestamp = 0.0;
|
||||||
|
|
||||||
double marktime = 0.0;
|
double marktime = 0.0;
|
||||||
|
double markassisttime = 0.0;
|
||||||
|
double markmaxexplicittime = 0.0;
|
||||||
|
size_t markexplicitsteps = 0;
|
||||||
|
size_t markrequests = 0;
|
||||||
|
|
||||||
double atomicstarttimestamp = 0.0;
|
double atomicstarttimestamp = 0.0;
|
||||||
size_t atomicstarttotalsizebytes = 0;
|
size_t atomicstarttotalsizebytes = 0;
|
||||||
double atomictime = 0.0;
|
double atomictime = 0.0;
|
||||||
|
|
||||||
|
// specific atomic stage parts
|
||||||
|
double atomictimeupval = 0.0;
|
||||||
|
double atomictimeweak = 0.0;
|
||||||
|
double atomictimegray = 0.0;
|
||||||
|
double atomictimeclear = 0.0;
|
||||||
|
|
||||||
double sweeptime = 0.0;
|
double sweeptime = 0.0;
|
||||||
|
double sweepassisttime = 0.0;
|
||||||
|
double sweepmaxexplicittime = 0.0;
|
||||||
|
size_t sweepexplicitsteps = 0;
|
||||||
|
size_t sweeprequests = 0;
|
||||||
|
|
||||||
|
size_t assistrequests = 0;
|
||||||
|
size_t explicitrequests = 0;
|
||||||
|
|
||||||
size_t assistwork = 0;
|
size_t assistwork = 0;
|
||||||
size_t explicitwork = 0;
|
size_t explicitwork = 0;
|
||||||
|
|
||||||
|
size_t propagatework = 0;
|
||||||
|
size_t propagateagainwork = 0;
|
||||||
|
|
||||||
size_t endtotalsizebytes = 0;
|
size_t endtotalsizebytes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -447,7 +447,8 @@ void luaH_free(lua_State* L, Table* t, lua_Page* page)
|
|||||||
{
|
{
|
||||||
if (t->node != dummynode)
|
if (t->node != dummynode)
|
||||||
luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat);
|
luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat);
|
||||||
luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat);
|
if (t->array)
|
||||||
|
luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat);
|
||||||
luaM_freegco(L, t, sizeof(Table), t->memcat, page);
|
luaM_freegco(L, t, sizeof(Table), t->memcat, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
#include "lualib.h"
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lapi.h"
|
||||||
#include "lstate.h"
|
#include "lstate.h"
|
||||||
#include "ltable.h"
|
#include "ltable.h"
|
||||||
#include "lstring.h"
|
#include "lstring.h"
|
||||||
@ -9,6 +10,8 @@
|
|||||||
#include "ldebug.h"
|
#include "ldebug.h"
|
||||||
#include "lvm.h"
|
#include "lvm.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauTableClone, false)
|
||||||
|
|
||||||
static int foreachi(lua_State* L)
|
static int foreachi(lua_State* L)
|
||||||
{
|
{
|
||||||
luaL_checktype(L, 1, LUA_TTABLE);
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
@ -507,6 +510,23 @@ static int tisfrozen(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int tclone(lua_State* L)
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauTableClone)
|
||||||
|
luaG_runerror(L, "table.clone is not available");
|
||||||
|
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_argcheck(L, !luaL_getmetafield(L, 1, "__metatable"), 1, "table has a protected metatable");
|
||||||
|
|
||||||
|
Table* tt = luaH_clone(L, hvalue(L->base));
|
||||||
|
|
||||||
|
TValue v;
|
||||||
|
sethvalue(L, &v, tt);
|
||||||
|
luaA_pushobject(L, &v);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static const luaL_Reg tab_funcs[] = {
|
static const luaL_Reg tab_funcs[] = {
|
||||||
{"concat", tconcat},
|
{"concat", tconcat},
|
||||||
{"foreach", foreach},
|
{"foreach", foreach},
|
||||||
@ -524,6 +544,7 @@ static const luaL_Reg tab_funcs[] = {
|
|||||||
{"clear", tclear},
|
{"clear", tclear},
|
||||||
{"freeze", tfreeze},
|
{"freeze", tfreeze},
|
||||||
{"isfrozen", tisfrozen},
|
{"isfrozen", tisfrozen},
|
||||||
|
{"clone", tclone},
|
||||||
{NULL, NULL},
|
{NULL, NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,4 +74,7 @@ function test()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
bench.runs = 6
|
||||||
|
bench.extraRuns = 2
|
||||||
|
|
||||||
bench.runCode(test, "GC: Boehm tree")
|
bench.runCode(test, "GC: Boehm tree")
|
||||||
|
@ -40,7 +40,7 @@ function test()
|
|||||||
|
|
||||||
local tree = { id = 0 }
|
local tree = { id = 0 }
|
||||||
|
|
||||||
for i = 1,1000 do
|
for i = 1,100 do
|
||||||
fill_tree(tree, 10)
|
fill_tree(tree, 10)
|
||||||
|
|
||||||
prune_tree(tree, 0)
|
prune_tree(tree, 0)
|
||||||
|
@ -42,7 +42,7 @@ function test()
|
|||||||
local tree = { id = 0 }
|
local tree = { id = 0 }
|
||||||
fill_tree(tree, 16)
|
fill_tree(tree, 16)
|
||||||
|
|
||||||
for i = 1,1000 do
|
for i = 1,100 do
|
||||||
local small_tree = { id = 0 }
|
local small_tree = { id = 0 }
|
||||||
|
|
||||||
fill_tree(small_tree, 8)
|
fill_tree(small_tree, 8)
|
||||||
|
@ -46,7 +46,7 @@ function test()
|
|||||||
|
|
||||||
local tree = { id = 0 }
|
local tree = { id = 0 }
|
||||||
|
|
||||||
for i = 1,1000 do
|
for i = 1,100 do
|
||||||
fill_tree(tree, 10)
|
fill_tree(tree, 10)
|
||||||
|
|
||||||
prune_tree(tree, 0)
|
prune_tree(tree, 0)
|
||||||
|
2
extern/isocline/include/isocline.h
vendored
2
extern/isocline/include/isocline.h
vendored
@ -259,7 +259,7 @@ void ic_complete_qword( ic_completion_env_t* cenv, const char* prefix, ic_comple
|
|||||||
/// The `escape_char` is the escaping character, usually `\` but use 0 to not have escape characters.
|
/// The `escape_char` is the escaping character, usually `\` but use 0 to not have escape characters.
|
||||||
/// The `quote_chars` define the quotes, use NULL for the default `"\'\""` quotes.
|
/// The `quote_chars` define the quotes, use NULL for the default `"\'\""` quotes.
|
||||||
/// @see ic_complete_word() which uses the default values for `non_word_chars`, `quote_chars` and `\` for escape characters.
|
/// @see ic_complete_word() which uses the default values for `non_word_chars`, `quote_chars` and `\` for escape characters.
|
||||||
void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t fun,
|
void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun,
|
||||||
ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars );
|
ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars );
|
||||||
|
|
||||||
/// \}
|
/// \}
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSchubfach);
|
|
||||||
|
|
||||||
#define LUAI_MAXNUM2STR 48
|
#define LUAI_MAXNUM2STR 48
|
||||||
|
|
||||||
char* luai_num2str(char* buf, double n);
|
char* luai_num2str(char* buf, double n);
|
||||||
@ -17,8 +15,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
|||||||
if (Size < 8)
|
if (Size < 8)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
FFlag::LuauSchubfach.value = true;
|
|
||||||
|
|
||||||
double num;
|
double num;
|
||||||
memcpy(&num, Data, 8);
|
memcpy(&num, Data, 8);
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ void interrupt(lua_State* L, int gc)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void* allocate(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsize)
|
void* allocate(void* ud, void* ptr, size_t osize, size_t nsize)
|
||||||
{
|
{
|
||||||
if (nsize == 0)
|
if (nsize == 0)
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||||
|
LUAU_FASTFLAG(LuauTableCloneType)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
@ -262,7 +263,7 @@ TEST_CASE_FIXTURE(ACFixture, "get_member_completions")
|
|||||||
|
|
||||||
auto ac = autocomplete('1');
|
auto ac = autocomplete('1');
|
||||||
|
|
||||||
CHECK_EQ(16, ac.entryMap.size());
|
CHECK_EQ(FFlag::LuauTableCloneType ? 17 : 16, ac.entryMap.size());
|
||||||
CHECK(ac.entryMap.count("find"));
|
CHECK(ac.entryMap.count("find"));
|
||||||
CHECK(ac.entryMap.count("pack"));
|
CHECK(ac.entryMap.count("pack"));
|
||||||
CHECK(!ac.entryMap.count("math"));
|
CHECK(!ac.entryMap.count("math"));
|
||||||
@ -2235,7 +2236,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource")
|
|||||||
|
|
||||||
auto ac = autocompleteSource(frontend, source, Position{1, 24}, nullCallback).result;
|
auto ac = autocompleteSource(frontend, source, Position{1, 24}, nullCallback).result;
|
||||||
|
|
||||||
CHECK_EQ(16, ac.entryMap.size());
|
CHECK_EQ(FFlag::LuauTableCloneType ? 17 : 16, ac.entryMap.size());
|
||||||
CHECK(ac.entryMap.count("find"));
|
CHECK(ac.entryMap.count("find"));
|
||||||
CHECK(ac.entryMap.count("pack"));
|
CHECK(ac.entryMap.count("pack"));
|
||||||
CHECK(!ac.entryMap.count("math"));
|
CHECK(!ac.entryMap.count("math"));
|
||||||
@ -2695,8 +2696,6 @@ local r4 = t:bar1(@4)
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_parameters")
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_parameters")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
type A<T = @1> = () -> T
|
type A<T = @1> = () -> T
|
||||||
)");
|
)");
|
||||||
@ -2709,8 +2708,6 @@ type A<T = @1> = () -> T
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_pack_parameters")
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_pack_parameters")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
type A<T... = ...@1> = () -> T
|
type A<T... = ...@1> = () -> T
|
||||||
)");
|
)");
|
||||||
@ -2768,7 +2765,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_on_string_singletons")
|
|||||||
TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses_2")
|
TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses_2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
|
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
|
||||||
ScopedFastFlag preferToCallFunctionsForIntersects("PreferToCallFunctionsForIntersects", true);
|
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
local bar: ((number) -> number) & (number, number) -> number)
|
local bar: ((number) -> number) & (number, number) -> number)
|
||||||
|
@ -241,6 +241,8 @@ TEST_CASE("Math")
|
|||||||
|
|
||||||
TEST_CASE("Table")
|
TEST_CASE("Table")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff("LuauTableClone", true);
|
||||||
|
|
||||||
runConformance("nextvar.lua");
|
runConformance("nextvar.lua");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,6 +467,8 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
|
|||||||
|
|
||||||
TEST_CASE("Types")
|
TEST_CASE("Types")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff("LuauTableCloneType", true);
|
||||||
|
|
||||||
runConformance("types.lua", [](lua_State* L) {
|
runConformance("types.lua", [](lua_State* L) {
|
||||||
Luau::NullModuleResolver moduleResolver;
|
Luau::NullModuleResolver moduleResolver;
|
||||||
Luau::InternalErrorReporter iceHandler;
|
Luau::InternalErrorReporter iceHandler;
|
||||||
@ -959,8 +963,6 @@ TEST_CASE("Coverage")
|
|||||||
|
|
||||||
TEST_CASE("StringConversion")
|
TEST_CASE("StringConversion")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauSchubfach", true};
|
|
||||||
|
|
||||||
runConformance("strconv.lua");
|
runConformance("strconv.lua");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +157,113 @@ return bar()
|
|||||||
CHECK_EQ(result.warnings[0].text, "Global 'foo' is only used in the enclosing function 'bar'; consider changing it to local");
|
CHECK_EQ(result.warnings[0].text, "Global 'foo' is only used in the enclosing function 'bar'; consider changing it to local");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFx")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
|
||||||
|
LintResult result = lint(R"(
|
||||||
|
function bar()
|
||||||
|
foo = 6
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
function baz()
|
||||||
|
foo = 6
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
return bar() + baz()
|
||||||
|
)");
|
||||||
|
|
||||||
|
REQUIRE_EQ(result.warnings.size(), 1);
|
||||||
|
CHECK_EQ(result.warnings[0].text, "Global 'foo' is never read before being written. Consider changing it to local");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFxWithRead")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
|
||||||
|
LintResult result = lint(R"(
|
||||||
|
function bar()
|
||||||
|
foo = 6
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
function baz()
|
||||||
|
foo = 6
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
function read()
|
||||||
|
print(foo)
|
||||||
|
end
|
||||||
|
|
||||||
|
return bar() + baz() + read()
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK_EQ(result.warnings.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
|
||||||
|
LintResult result = lint(R"(
|
||||||
|
function bar()
|
||||||
|
if true then foo = 6 end
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
function baz()
|
||||||
|
foo = 6
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
return bar() + baz()
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK_EQ(result.warnings.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
|
||||||
|
LintResult result = lint(R"(
|
||||||
|
function bar()
|
||||||
|
foo = 6
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
function baz()
|
||||||
|
foo = 6
|
||||||
|
return foo
|
||||||
|
end
|
||||||
|
|
||||||
|
function read()
|
||||||
|
if false then print(foo) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return bar() + baz() + read()
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK_EQ(result.warnings.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
|
||||||
|
LintResult result = lint(R"(
|
||||||
|
function foo()
|
||||||
|
local f = function() return bar end
|
||||||
|
f()
|
||||||
|
bar = 42
|
||||||
|
end
|
||||||
|
|
||||||
|
function baz() bar = 0 end
|
||||||
|
|
||||||
|
return foo() + baz()
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK_EQ(result.warnings.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMulti")
|
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMulti")
|
||||||
{
|
{
|
||||||
LintResult result = lint(R"(
|
LintResult result = lint(R"(
|
||||||
|
@ -1988,8 +1988,6 @@ TEST_CASE_FIXTURE(Fixture, "function_type_matching_parenthesis")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type")
|
TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
AstStat* stat = parse(R"(
|
AstStat* stat = parse(R"(
|
||||||
type A<T = string> = {}
|
type A<T = string> = {}
|
||||||
type B<T... = ...number> = {}
|
type B<T... = ...number> = {}
|
||||||
@ -2005,8 +2003,6 @@ type G<T... = ...number, U... = (string, number, boolean)> = (U...) -> T...
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type_errors")
|
TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type_errors")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
matchParseError("type Y<T = number, U> = {}", "Expected default type after type name", Location{{0, 20}, {0, 21}});
|
matchParseError("type Y<T = number, U> = {}", "Expected default type after type name", Location{{0, 20}, {0, 21}});
|
||||||
matchParseError("type Y<T... = ...number, U...> = {}", "Expected default type pack after type pack name", Location{{0, 29}, {0, 30}});
|
matchParseError("type Y<T... = ...number, U...> = {}", "Expected default type pack after type pack name", Location{{0, 29}, {0, 30}});
|
||||||
matchParseError("type Y<T... = (string) -> number> = {}", "Expected type pack after '=', got type", Location{{0, 14}, {0, 32}});
|
matchParseError("type Y<T... = (string) -> number> = {}", "Expected type pack after '=', got type", Location{{0, 14}, {0, 32}});
|
||||||
@ -2574,8 +2570,6 @@ do end
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "recover_expected_type_pack")
|
TEST_CASE_FIXTURE(Fixture, "recover_expected_type_pack")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
ParseResult result = tryParse(R"(
|
ParseResult result = tryParse(R"(
|
||||||
type Y<T..., U = T...> = (T...) -> U...
|
type Y<T..., U = T...> = (T...) -> U...
|
||||||
)");
|
)");
|
||||||
|
@ -96,8 +96,6 @@ n2 [label="number"];
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "function")
|
TEST_CASE_FIXTURE(Fixture, "function")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauQuantifyInPlace2{"LuauQuantifyInPlace2", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a, ...: string) return a end
|
local function f(a, ...: string) return a end
|
||||||
)");
|
)");
|
||||||
|
@ -500,8 +500,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
|
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: number, b: string) end
|
local function f(a: number, b: string) end
|
||||||
local function test<T..., U...>(...: T...): U...
|
local function test<T..., U...>(...: T...): U...
|
||||||
|
@ -641,9 +641,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_to_string")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_alias_default_type_parameters")
|
TEST_CASE_FIXTURE(Fixture, "transpile_type_alias_default_type_parameters")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
std::string code = R"(
|
std::string code = R"(
|
||||||
type Packed<T = string, U = T, V... = ...boolean, W... = (T, U, V...)> = (T, U, V...)->(W...)
|
type Packed<T = string, U = T, V... = ...boolean, W... = (T, U, V...)> = (T, U, V...)->(W...)
|
||||||
local a: Packed<number>
|
local a: Packed<number>
|
||||||
|
@ -625,9 +625,8 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni
|
|||||||
ScopedFastFlag sff[] = {
|
ScopedFastFlag sff[] = {
|
||||||
{"LuauTwoPassAliasDefinitionFix", true},
|
{"LuauTwoPassAliasDefinitionFix", true},
|
||||||
|
|
||||||
// We also force these two flags because this surfaced an unfortunate interaction.
|
// We also force this flag because it surfaced an unfortunate interaction.
|
||||||
{"LuauErrorRecoveryType", true},
|
{"LuauErrorRecoveryType", true},
|
||||||
{"LuauQuantifyInPlace2", true},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -934,4 +934,31 @@ TEST_CASE_FIXTURE(Fixture, "assert_returns_false_and_string_iff_it_knows_the_fir
|
|||||||
CHECK_EQ("(nil) -> nil", toString(requireType("f")));
|
CHECK_EQ("(nil) -> nil", toString(requireType("f")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "table_freeze_is_generic")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local t1: {a: number} = {a = 42}
|
||||||
|
local t2: {b: string} = {b = "hello"}
|
||||||
|
local t3: {boolean} = {false, true}
|
||||||
|
|
||||||
|
local tf1 = table.freeze(t1)
|
||||||
|
local tf2 = table.freeze(t2)
|
||||||
|
local tf3 = table.freeze(t3)
|
||||||
|
|
||||||
|
local a = tf1.a
|
||||||
|
local b = tf2.b
|
||||||
|
local c = tf3[2]
|
||||||
|
|
||||||
|
local d = tf1.b
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0]));
|
||||||
|
|
||||||
|
CHECK_EQ("number", toString(requireType("a")));
|
||||||
|
CHECK_EQ("string", toString(requireType("b")));
|
||||||
|
CHECK_EQ("boolean", toString(requireType("c")));
|
||||||
|
CHECK_EQ("*unknown*", toString(requireType("d")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -697,4 +697,93 @@ end
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sffs[] = {
|
||||||
|
{ "LuauTableSubtypingVariance2", true },
|
||||||
|
{ "LuauUnsealedTableLiteral", true },
|
||||||
|
{ "LuauPropertiesGetExpectedType", true },
|
||||||
|
{ "LuauRecursiveTypeParameterRestriction", true },
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
-- At one point this produced a UAF
|
||||||
|
type T<a> = { a: U<a>, b: a }
|
||||||
|
type U<a> = { c: T<a>?, d : a }
|
||||||
|
local x: T<number> = { a = { c = nil, d = 5 }, b = 37 }
|
||||||
|
x.a.c = x
|
||||||
|
local y: T<string> = { a = { c = nil, d = 5 }, b = 37 }
|
||||||
|
y.a.c = y
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
CHECK_EQ(toString(result.errors[0]),
|
||||||
|
R"(Type 'y' could not be converted into 'T<string>'
|
||||||
|
caused by:
|
||||||
|
Property 'a' is not compatible. Type '{ c: T<string>?, d: number }' could not be converted into 'U<string>'
|
||||||
|
caused by:
|
||||||
|
Property 'd' is not compatible. Type 'number' could not be converted into 'string')");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauTxnLogSeesTypePacks2", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
type Dispatcher = {
|
||||||
|
useMemo: <T...>(create: () -> T...) -> T...
|
||||||
|
}
|
||||||
|
|
||||||
|
local TheDispatcher: Dispatcher = {
|
||||||
|
useMemo = function<U...>(create: () -> U...): U...
|
||||||
|
return create()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification2")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauTxnLogSeesTypePacks2", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
type Dispatcher = {
|
||||||
|
useMemo: <T...>(create: () -> T...) -> T...
|
||||||
|
}
|
||||||
|
|
||||||
|
local TheDispatcher: Dispatcher = {
|
||||||
|
useMemo = function(create)
|
||||||
|
return create()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification3")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauTxnLogSeesTypePacks2", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
type Dispatcher = {
|
||||||
|
useMemo: <S,T...>(arg: S, create: (S) -> T...) -> T...
|
||||||
|
}
|
||||||
|
|
||||||
|
local TheDispatcher: Dispatcher = {
|
||||||
|
useMemo = function<T,U...>(arg: T, create: (T) -> U...): U...
|
||||||
|
return create(arg)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauEqConstraint)
|
LUAU_FASTFLAG(LuauEqConstraint)
|
||||||
LUAU_FASTFLAG(LuauQuantifyInPlace2)
|
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
@ -40,16 +39,6 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||||||
end
|
end
|
||||||
)";
|
)";
|
||||||
|
|
||||||
const std::string old_expected = R"(
|
|
||||||
function f(a:{fn:()->(free,free...)}): ()
|
|
||||||
if type(a) == 'boolean'then
|
|
||||||
local a1:boolean=a
|
|
||||||
elseif a.fn()then
|
|
||||||
local a2:{fn:()->(free,free...)}=a
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)";
|
|
||||||
|
|
||||||
const std::string expected = R"(
|
const std::string expected = R"(
|
||||||
function f(a:{fn:()->(a,b...)}): ()
|
function f(a:{fn:()->(a,b...)}): ()
|
||||||
if type(a) == 'boolean'then
|
if type(a) == 'boolean'then
|
||||||
@ -60,10 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||||||
end
|
end
|
||||||
)";
|
)";
|
||||||
|
|
||||||
if (FFlag::LuauQuantifyInPlace2)
|
CHECK_EQ(expected, decorateWithTypes(code));
|
||||||
CHECK_EQ(expected, decorateWithTypes(code));
|
|
||||||
else
|
|
||||||
CHECK_EQ(old_expected, decorateWithTypes(code));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "xpcall_returns_what_f_returns")
|
TEST_CASE_FIXTURE(Fixture, "xpcall_returns_what_f_returns")
|
||||||
@ -135,46 +121,6 @@ TEST_CASE_FIXTURE(Fixture, "setmetatable_constrains_free_type_into_free_table")
|
|||||||
CHECK_EQ("number", toString(tm->givenType));
|
CHECK_EQ("number", toString(tm->givenType));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table")
|
|
||||||
{
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
local a: {x: number, y: number, [any]: any} | {y: number}
|
|
||||||
|
|
||||||
function f(t)
|
|
||||||
t.y = 1
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
local b = f(a)
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
|
|
||||||
// :(
|
|
||||||
// Should be the same as the type of a
|
|
||||||
REQUIRE_EQ("{| y: number |}", toString(requireType("b")));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table_2")
|
|
||||||
{
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
local a: {y: number} | {x: number, y: number, [any]: any}
|
|
||||||
|
|
||||||
function f(t)
|
|
||||||
t.y = 1
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
local b = f(a)
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
|
|
||||||
// :(
|
|
||||||
// Should be the same as the type of a
|
|
||||||
REQUIRE_EQ("{| [any]: any, x: number, y: number |}", toString(requireType("b")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Luau currently doesn't yet know how to allow assignments when the binding was refined.
|
// Luau currently doesn't yet know how to allow assignments when the binding was refined.
|
||||||
TEST_CASE_FIXTURE(Fixture, "while_body_are_also_refined")
|
TEST_CASE_FIXTURE(Fixture, "while_body_are_also_refined")
|
||||||
{
|
{
|
||||||
@ -557,25 +503,6 @@ TEST_CASE_FIXTURE(Fixture, "bail_early_on_typescript_port_of_Result_type" * doct
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
|
|
||||||
{
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
--!strict
|
|
||||||
local function setNumber(t: { p: number? }, x:number) t.p = x end
|
|
||||||
local function getString(t: { p: string? }):string return t.p or "" end
|
|
||||||
-- This shouldn't type-check!
|
|
||||||
local function oh(x:number): string
|
|
||||||
local t: {} = {}
|
|
||||||
setNumber(t, x)
|
|
||||||
return getString(t)
|
|
||||||
end
|
|
||||||
local s: string = oh(37)
|
|
||||||
)");
|
|
||||||
|
|
||||||
// Really this should return an error, but it doesn't
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be in TypeInfer.tables.test.cpp
|
// Should be in TypeInfer.tables.test.cpp
|
||||||
// It's unsound to instantiate tables containing generic methods,
|
// It's unsound to instantiate tables containing generic methods,
|
||||||
// since mutating properties means table properties should be invariant.
|
// since mutating properties means table properties should be invariant.
|
||||||
@ -600,25 +527,9 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param")
|
|
||||||
{
|
|
||||||
// Mutability in type function application right now can create strange recursive types
|
|
||||||
// TODO: instantiation right now is problematic, in this example should either leave the Table type alone
|
|
||||||
// or it should rename the type to 'Self' so that the result will be 'Self<Table>'
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
type Table = { a: number }
|
|
||||||
type Self<T> = T
|
|
||||||
local a: Self<Table>
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
CHECK_EQ(toString(requireType("a")), "Table<Table>");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "do_not_ice_when_trying_to_pick_first_of_generic_type_pack")
|
TEST_CASE_FIXTURE(Fixture, "do_not_ice_when_trying_to_pick_first_of_generic_type_pack")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauQuantifyInPlace2", true},
|
|
||||||
{"LuauReturnAnyInsteadOfICE", true},
|
{"LuauReturnAnyInsteadOfICE", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -664,8 +575,6 @@ TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauQuantifyInPlace2", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f() return end
|
local function f() return end
|
||||||
local g = function() return f() end
|
local g = function() return f() end
|
||||||
@ -676,8 +585,6 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
|
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauQuantifyInPlace2", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!strict
|
--!strict
|
||||||
local function f(...) return ... end
|
local function f(...) return ... end
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||||
LUAU_FASTFLAG(LuauWeakEqConstraint)
|
LUAU_FASTFLAG(LuauWeakEqConstraint)
|
||||||
LUAU_FASTFLAG(LuauQuantifyInPlace2)
|
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
@ -1179,20 +1178,14 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
|||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
if (FFlag::LuauQuantifyInPlace2)
|
CHECK_EQ("Type '{+ X: a, Y: b, Z: c +}' could not be converted into 'Instance'", toString(result.errors[0]));
|
||||||
CHECK_EQ("Type '{+ X: a, Y: b, Z: c +}' could not be converted into 'Instance'", toString(result.errors[0]));
|
|
||||||
else
|
|
||||||
CHECK_EQ("Type '{- X: a, Y: b, Z: c -}' could not be converted into 'Instance'", toString(result.errors[0]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector"
|
CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector"
|
||||||
|
|
||||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({7, 28}))); // typeof(vec) == "Instance"
|
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({7, 28}))); // typeof(vec) == "Instance"
|
||||||
|
|
||||||
if (FFlag::LuauQuantifyInPlace2)
|
CHECK_EQ("{+ X: a, Y: b, Z: c +}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance"
|
||||||
CHECK_EQ("{+ X: a, Y: b, Z: c +}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance"
|
|
||||||
else
|
|
||||||
CHECK_EQ("{- X: a, Y: b, Z: c -}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to_vector")
|
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to_vector")
|
||||||
|
@ -465,30 +465,32 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
|
|||||||
CHECK_EQ("<a, b...>((string) -> (b...), a) -> ()", toString(requireType("foo")));
|
CHECK_EQ("<a, b...>((string) -> (b...), a) -> ()", toString(requireType("foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
|
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
|
||||||
// {
|
{
|
||||||
// ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
// {"LuauParseSingletonTypes", true},
|
{"LuauParseSingletonTypes", true},
|
||||||
// {"LuauSingletonTypes", true},
|
{"LuauSingletonTypes", true},
|
||||||
// {"LuauDiscriminableUnions2", true},
|
{"LuauDiscriminableUnions2", true},
|
||||||
// {"LuauEqConstraint", true},
|
{"LuauEqConstraint", true},
|
||||||
// {"LuauWidenIfSupertypeIsFree", true},
|
{"LuauWidenIfSupertypeIsFree", true},
|
||||||
// {"LuauWeakEqConstraint", false},
|
{"LuauWeakEqConstraint", false},
|
||||||
// };
|
{"LuauDoNotAccidentallyDependOnPointerOrdering", true}
|
||||||
|
};
|
||||||
|
|
||||||
// CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
// local function foo(f, x): "hello"? -- anyone there?
|
local function foo(f, x): "hello"? -- anyone there?
|
||||||
// return if x == "hi"
|
return if x == "hi"
|
||||||
// then f(x)
|
then f(x)
|
||||||
// else nil
|
else nil
|
||||||
// end
|
end
|
||||||
// )");
|
)");
|
||||||
|
|
||||||
// LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
// CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
|
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
|
||||||
// CHECK_EQ(R"(<a, b...>((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo")));
|
CHECK_EQ(R"(<a, b, c...>((string) -> (a, c...), b) -> "hello"?)", toString(requireType("foo")));
|
||||||
// }
|
// CHECK_EQ(R"(<a, b...>((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
|
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
|
||||||
{
|
{
|
||||||
|
@ -1219,13 +1219,12 @@ TEST_CASE_FIXTURE(Fixture, "passing_compatible_unions_to_a_generic_table_without
|
|||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = {x: number, y: number, [any]: any} | {y: number}
|
type A = {x: number, y: number, [any]: any} | {y: number}
|
||||||
local a: A
|
|
||||||
|
|
||||||
function f(t)
|
function f(t)
|
||||||
t.y = 1
|
t.y = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
f(a)
|
f({y = 5} :: A)
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
@ -2165,6 +2164,44 @@ b()
|
|||||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
|
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sffs[] = {
|
||||||
|
{"LuauTableSubtypingVariance2", true},
|
||||||
|
{"LuauSubtypingAddOptPropsToUnsealedTables", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
local function setNumber(t: { p: number? }, x:number) t.p = x end
|
||||||
|
local function getString(t: { p: string? }):string return t.p or "" end
|
||||||
|
-- This shouldn't type-check!
|
||||||
|
local function oh(x:number): string
|
||||||
|
local t: {} = {}
|
||||||
|
setNumber(t, x)
|
||||||
|
return getString(t)
|
||||||
|
end
|
||||||
|
local s: string = oh(37)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "top_table_type")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
type Table = { [any] : any }
|
||||||
|
type HasTable = { p: Table? }
|
||||||
|
type HasHasTable = { p: HasTable? }
|
||||||
|
local t : Table = { p = 5 }
|
||||||
|
local u : HasTable = { p = { p = 5 } }
|
||||||
|
local v : HasHasTable = { p = { p = { p = 5 } } }
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "length_operator_union")
|
TEST_CASE_FIXTURE(Fixture, "length_operator_union")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -2257,4 +2294,44 @@ TEST_CASE_FIXTURE(Fixture, "confusing_indexing")
|
|||||||
CHECK_EQ("number | string", toString(requireType("foo")));
|
CHECK_EQ("number | string", toString(requireType("foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local a: {x: number, y: number, [any]: any} | {y: number}
|
||||||
|
|
||||||
|
function f(t)
|
||||||
|
t.y = 1
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = f(a)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
REQUIRE_EQ("{| [any]: any, x: number, y: number |} | {| y: number |}", toString(requireType("b")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table_2")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local a: {y: number} | {x: number, y: number, [any]: any}
|
||||||
|
|
||||||
|
function f(t)
|
||||||
|
t.y = 1
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = f(a)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
REQUIRE_EQ("{| [any]: any, x: number, y: number |} | {| y: number |}", toString(requireType("b")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -4044,6 +4044,49 @@ type t0<t32> = any
|
|||||||
CHECK(ttv->instantiatedTypeParams.empty());
|
CHECK(ttv->instantiatedTypeParams.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "instantiate_table_cloning_2")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type X<T> = T
|
||||||
|
type K = X<typeof(math)>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
std::optional<TypeId> ty = requireType("math");
|
||||||
|
REQUIRE(ty);
|
||||||
|
|
||||||
|
const TableTypeVar* ttv = get<TableTypeVar>(*ty);
|
||||||
|
REQUIRE(ttv);
|
||||||
|
CHECK(ttv->instantiatedTypeParams.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "instantiate_table_cloning_3")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type X<T> = T
|
||||||
|
local a = {}
|
||||||
|
a.x = 4
|
||||||
|
local b: X<typeof(a)>
|
||||||
|
a.y = 5
|
||||||
|
local c: X<typeof(a)>
|
||||||
|
c = b
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
std::optional<TypeId> ty = requireType("a");
|
||||||
|
REQUIRE(ty);
|
||||||
|
|
||||||
|
const TableTypeVar* ttv = get<TableTypeVar>(*ty);
|
||||||
|
REQUIRE(ttv);
|
||||||
|
CHECK(ttv->instantiatedTypeParams.empty());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "bound_free_table_export_is_ok")
|
TEST_CASE_FIXTURE(Fixture, "bound_free_table_export_is_ok")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -4065,6 +4108,21 @@ return m
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true};
|
||||||
|
|
||||||
|
// Mutability in type function application right now can create strange recursive types
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Table = { a: number }
|
||||||
|
type Self<T> = T
|
||||||
|
local a: Self<Table>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ(toString(requireType("a")), "Table");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "no_persistent_typelevel_change")
|
TEST_CASE_FIXTURE(Fixture, "no_persistent_typelevel_change")
|
||||||
{
|
{
|
||||||
TypeId mathTy = requireType(typeChecker.globalScope, "math");
|
TypeId mathTy = requireType(typeChecker.globalScope, "math");
|
||||||
@ -5284,4 +5342,17 @@ TEST_CASE_FIXTURE(Fixture, "inferred_properties_of_a_table_should_start_with_the
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "global_singleton_types_are_sealed")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: string)
|
||||||
|
local p = x:split('a')
|
||||||
|
p = table.pack(table.unpack(p, 1, #p - 1))
|
||||||
|
return p
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauQuantifyInPlace2);
|
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||||
@ -167,10 +165,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "typepack_unification_should_trim_free_tails"
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
if (FFlag::LuauQuantifyInPlace2)
|
CHECK_EQ("(number) -> boolean", toString(requireType("f")));
|
||||||
CHECK_EQ("(number) -> boolean", toString(requireType("f")));
|
|
||||||
else
|
|
||||||
CHECK_EQ("(number) -> (boolean)", toString(requireType("f")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification")
|
TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification")
|
||||||
|
@ -622,9 +622,6 @@ type Other = Packed<number, string>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_explicit")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_explicit")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T, U = string> = { a: T, b: U }
|
type Y<T, U = string> = { a: T, b: U }
|
||||||
|
|
||||||
@ -654,9 +651,6 @@ local c: Y = { a = "s" }
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_self")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_self")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T, U = T> = { a: T, b: U }
|
type Y<T, U = T> = { a: T, b: U }
|
||||||
|
|
||||||
@ -682,9 +676,6 @@ local a: Y<number>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_chained")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_chained")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T, U = T, V = U> = { a: T, b: U, c: V }
|
type Y<T, U = T, V = U> = { a: T, b: U, c: V }
|
||||||
|
|
||||||
@ -700,9 +691,6 @@ local b: Y<number, string>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_explicit")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_explicit")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T... = (string, number)> = { a: (T...) -> () }
|
type Y<T... = (string, number)> = { a: (T...) -> () }
|
||||||
local a: Y<>
|
local a: Y<>
|
||||||
@ -715,9 +703,6 @@ local a: Y<>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_ty")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_ty")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T, U... = ...T> = { a: T, b: (U...) -> T }
|
type Y<T, U... = ...T> = { a: T, b: (U...) -> T }
|
||||||
|
|
||||||
@ -731,9 +716,6 @@ local a: Y<number>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_tp")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_tp")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T..., U... = T...> = { a: (T...) -> U... }
|
type Y<T..., U... = T...> = { a: (T...) -> U... }
|
||||||
local a: Y<number, string>
|
local a: Y<number, string>
|
||||||
@ -746,9 +728,6 @@ local a: Y<number, string>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_chained_tp")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_chained_tp")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T..., U... = T..., V... = U...> = { a: (T...) -> U..., b: (T...) -> V... }
|
type Y<T..., U... = T..., V... = U...> = { a: (T...) -> U..., b: (T...) -> V... }
|
||||||
local a: Y<number, string>
|
local a: Y<number, string>
|
||||||
@ -761,9 +740,6 @@ local a: Y<number, string>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_mixed_self")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_mixed_self")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T, U = T, V... = ...number, W... = (T, U, V...)> = { a: (T, U, V...) -> W... }
|
type Y<T, U = T, V... = ...number, W... = (T, U, V...)> = { a: (T, U, V...) -> W... }
|
||||||
local a: Y<number>
|
local a: Y<number>
|
||||||
@ -782,9 +758,6 @@ local d: Y<number, string, ...boolean, ...() -> ()>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T = T> = { a: T }
|
type Y<T = T> = { a: T }
|
||||||
local a: Y = { a = 2 }
|
local a: Y = { a = 2 }
|
||||||
@ -834,9 +807,6 @@ local a: Y<...number>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_export")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_export")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
fileResolver.source["Module/Types"] = R"(
|
fileResolver.source["Module/Types"] = R"(
|
||||||
export type A<T, U = string> = { a: T, b: U }
|
export type A<T, U = string> = { a: T, b: U }
|
||||||
export type B<T, U = T> = { a: T, b: U }
|
export type B<T, U = T> = { a: T, b: U }
|
||||||
@ -882,9 +852,6 @@ local h: Types.H<>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_skip_brackets")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_skip_brackets")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Y<T... = ...string> = (T...) -> number
|
type Y<T... = ...string> = (T...) -> number
|
||||||
local a: Y
|
local a: Y
|
||||||
@ -897,9 +864,6 @@ local a: Y
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_confusing_types")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_confusing_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A<T, U = T, V... = ...any, W... = V...> = (T, V...) -> (U, W...)
|
type A<T, U = T, V... = ...any, W... = V...> = (T, V...) -> (U, W...)
|
||||||
type B = A<string, (number)>
|
type B = A<string, (number)>
|
||||||
@ -914,9 +878,6 @@ type C = A<string, (number), (boolean)>
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_recursive_type")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_recursive_type")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
|
||||||
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type F<K = string, V = (K) -> ()> = (K) -> V
|
type F<K = string, V = (K) -> ()> = (K) -> V
|
||||||
type R = { m: F<R> }
|
type R = { m: F<R> }
|
||||||
|
@ -400,10 +400,10 @@ local e = a.z
|
|||||||
CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[3]));
|
CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[3]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "unify_sealed_table_union_check")
|
TEST_CASE_FIXTURE(Fixture, "unify_unsealed_table_union_check")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local x: { x: number } = { x = 3 }
|
local x = { x = 3 }
|
||||||
type A = number?
|
type A = number?
|
||||||
type B = string?
|
type B = string?
|
||||||
local y: { x: number, y: A | B }
|
local y: { x: number, y: A | B }
|
||||||
@ -413,7 +413,7 @@ y = x
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
result = check(R"(
|
result = check(R"(
|
||||||
local x: { x: number } = { x = 3 }
|
local x = { x = 3 }
|
||||||
|
|
||||||
local a: number? = 2
|
local a: number? = 2
|
||||||
local y = {}
|
local y = {}
|
||||||
@ -426,6 +426,31 @@ y = x
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "unify_sealed_table_union_check")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sffs[] = {
|
||||||
|
{"LuauTableSubtypingVariance2", true},
|
||||||
|
{"LuauUnsealedTableLiteral", true},
|
||||||
|
{"LuauSubtypingAddOptPropsToUnsealedTables", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
-- the difference between this and unify_unsealed_table_union_check is the type annotation on x
|
||||||
|
local t = { x = 3, y = true }
|
||||||
|
local x: { x: number } = t
|
||||||
|
type A = number?
|
||||||
|
type B = string?
|
||||||
|
local y: { x: number, y: A | B }
|
||||||
|
-- Shouldn't typecheck!
|
||||||
|
y = x
|
||||||
|
-- If it does, we can convert any type to any other type
|
||||||
|
y.y = 5
|
||||||
|
local oh : boolean = t.y
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part")
|
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -512,4 +512,42 @@ do
|
|||||||
assert(#t == 7)
|
assert(#t == 7)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- test clone
|
||||||
|
do
|
||||||
|
local t = {a = 1, b = 2, 3, 4, 5}
|
||||||
|
local tt = table.clone(t)
|
||||||
|
|
||||||
|
assert(#tt == 3)
|
||||||
|
assert(tt.a == 1 and tt.b == 2)
|
||||||
|
|
||||||
|
t.c = 3
|
||||||
|
assert(tt.c == nil)
|
||||||
|
|
||||||
|
t = table.freeze({"test"})
|
||||||
|
tt = table.clone(t)
|
||||||
|
assert(table.isfrozen(t) and not table.isfrozen(tt))
|
||||||
|
|
||||||
|
t = setmetatable({}, {})
|
||||||
|
tt = table.clone(t)
|
||||||
|
assert(getmetatable(t) == getmetatable(tt))
|
||||||
|
|
||||||
|
t = setmetatable({}, {__metatable = "protected"})
|
||||||
|
assert(not pcall(table.clone, t))
|
||||||
|
|
||||||
|
function order(t)
|
||||||
|
local r = ''
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
r ..= tostring(v)
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
|
||||||
|
t = {a = 1, b = 2, c = 3, d = 4, e = 5, f = 6}
|
||||||
|
tt = table.clone(t)
|
||||||
|
assert(order(t) == order(tt))
|
||||||
|
|
||||||
|
assert(not pcall(table.clone))
|
||||||
|
assert(not pcall(table.clone, 42))
|
||||||
|
end
|
||||||
|
|
||||||
return"OK"
|
return"OK"
|
||||||
|
Loading…
Reference in New Issue
Block a user