Merge branch 'upstream' into merge

This commit is contained in:
Arseny Kapoulkine 2022-03-04 08:19:48 -08:00
commit 75e66f13c5
55 changed files with 1267 additions and 774 deletions

View file

@ -12,6 +12,8 @@ LUAU_FASTFLAG(LuauShareTxnSeen);
namespace Luau
{
using TypeOrPackId = const void*;
// Log of where what TypeIds we are rebinding and what they used to be
// Remove with LuauUseCommitTxnLog
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())
, ownedSeen()
, sharedSeen(sharedSeen)
@ -48,15 +50,23 @@ struct DEPRECATED_TxnLog
void pushSeen(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:
std::vector<std::pair<TypeId, TypeVar>> typeVarChanges;
std::vector<std::pair<TypePackId, TypePackVar>> typePackChanges;
std::vector<std::pair<TableTypeVar*, std::optional<TypeId>>> tableChanges;
size_t originalSeenSize;
bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs);
void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs);
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
public:
std::vector<std::pair<TypeId, TypeId>> 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>> ownedSeen; // used to avoid infinite recursion when types are cyclic
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
@ -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)
{
}
TxnLog(TxnLog* parent, std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
TxnLog(TxnLog* parent, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
: parent(parent)
, sharedSeen(sharedSeen)
{
@ -173,6 +183,10 @@ struct TxnLog
void pushSeen(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
// 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
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
// this is an empty vector.
std::vector<std::pair<TypeId, TypeId>> ownedSeen;
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:
// Used to avoid infinite recursion when types are cyclic.
// Shared with all the descendent TxnLogs.
std::vector<std::pair<TypeId, TypeId>>* sharedSeen;
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
};
} // namespace Luau

View file

@ -73,24 +73,6 @@ struct Instantiation : Substitution
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
struct Anyification : Substitution
{

View file

@ -298,7 +298,7 @@ struct TableTypeVar
TableTypeVar() = default;
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;
std::optional<TableIndexer> indexer;
@ -477,6 +477,9 @@ bool isOptional(TypeId ty);
bool isTableIntersection(TypeId ty);
bool isOverloadedFunction(TypeId ty);
// True when string is a subtype of ty
bool maybeString(TypeId ty);
std::optional<TypeId> getMetatable(TypeId type);
TableTypeVar* getMutableTableType(TypeId type);
const TableTypeVar* getTableType(TypeId type);

View file

@ -56,7 +56,7 @@ struct Unifier
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState,
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);
// Test whether the two type vars unify. Never commits the result.

View file

@ -16,7 +16,6 @@
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false);
LUAU_FASTFLAGVARIABLE(PreferToCallFunctionsForIntersects, false);
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = {
@ -272,55 +271,34 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
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;
// 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))
{
for (TypeId id : itv->parts)
{
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(id); ftv && checkFunctionType(ftv))
{
return TypeCorrectKind::CorrectFunctionResult;
}
}
}
}
else
return false;
};
// We also want to suggest functions that return compatible result
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty); ftv && checkFunctionType(ftv))
{
// We also want to suggest functions that return compatible result
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
return TypeCorrectKind::CorrectFunctionResult;
}
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
for (TypeId id : itv->parts)
{
auto [retHead, retTail] = flatten(ftv->retType);
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 FunctionTypeVar* ftv = get<FunctionTypeVar>(id); ftv && checkFunctionType(ftv))
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(vtp->ty, expectedType))
return TypeCorrectKind::CorrectFunctionResult;
return TypeCorrectKind::CorrectFunctionResult;
}
}
}

View file

@ -9,6 +9,7 @@
#include <algorithm>
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
LUAU_FASTFLAGVARIABLE(LuauTableCloneType, false)
/** 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, "select"), magicFunctionSelect);
auto tableLib = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table"));
attachMagicFunction(tableLib->props["pack"].type, magicFunctionPack);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table")))
{
// 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);
}

View file

@ -170,7 +170,6 @@ declare function gcinfo(): number
move: <V>({V}, number, number, number, {V}?) -> {V},
clear: <K, V>({[K]: V}) -> (),
freeze: <K, V>({[K]: V}) -> {[K]: V},
isfrozen: <K, V>({[K]: V}) -> boolean,
}

View file

@ -5,8 +5,6 @@
#include "Luau/StringUtils.h"
#include "Luau/Common.h"
LUAU_FASTFLAG(LuauTypeAliasDefaults)
namespace Luau
{
@ -369,38 +367,24 @@ struct AstJsonEncoder : public AstVisitor
void write(const AstGenericType& genericType)
{
if (FFlag::LuauTypeAliasDefaults)
{
writeRaw("{");
bool c = pushComma();
write("name", genericType.name);
if (genericType.defaultValue)
write("type", genericType.defaultValue);
popComma(c);
writeRaw("}");
}
else
{
write(genericType.name);
}
writeRaw("{");
bool c = pushComma();
write("name", genericType.name);
if (genericType.defaultValue)
write("type", genericType.defaultValue);
popComma(c);
writeRaw("}");
}
void write(const AstGenericTypePack& genericTypePack)
{
if (FFlag::LuauTypeAliasDefaults)
{
writeRaw("{");
bool c = pushComma();
write("name", genericTypePack.name);
if (genericTypePack.defaultValue)
write("type", genericTypePack.defaultValue);
popComma(c);
writeRaw("}");
}
else
{
write(genericTypePack.name);
}
writeRaw("{");
bool c = pushComma();
write("name", genericTypePack.name);
if (genericTypePack.defaultValue)
write("type", genericTypePack.defaultValue);
popComma(c);
writeRaw("}");
}
void write(AstExprTable::Item::Kind kind)

View file

@ -13,6 +13,7 @@
#include <limits.h>
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
namespace Luau
{
@ -233,6 +234,20 @@ public:
}
private:
struct FunctionInfo
{
explicit FunctionInfo(AstExprFunction* ast)
: ast(ast)
, dominatedGlobals({})
, conditionalExecution(false)
{
}
AstExprFunction* ast;
DenseHashSet<AstName> dominatedGlobals;
bool conditionalExecution;
};
struct Global
{
AstExprGlobal* firstRef = nullptr;
@ -241,6 +256,9 @@ private:
bool assigned = false;
bool builtin = false;
bool definedInModuleScope = false;
bool definedAsFunction = false;
bool readBeforeWritten = false;
std::optional<const char*> deprecated;
};
@ -248,7 +266,8 @@ private:
DenseHashMap<AstName, Global> globals;
std::vector<AstExprGlobal*> globalRefs;
std::vector<AstExprFunction*> functionStack;
std::vector<FunctionInfo> functionStack;
LintGlobalLocal()
: 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",
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
{
functionStack.push_back(node);
functionStack.emplace_back(node);
node->body->visit(this);
@ -307,6 +332,11 @@ private:
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);
if (node->name == context->placeholder)
@ -335,6 +365,21 @@ private:
{
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)
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);
@ -369,7 +414,14 @@ private:
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);
else
{
g.assigned = true;
if (FFlag::LuauLintGlobalNeverReadBeforeWritten)
{
g.definedAsFunction = true;
g.definedInModuleScope = functionStack.empty();
}
}
trackGlobalRef(gv);
}
@ -377,6 +429,98 @@ private:
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)
{
Global& g = globals[node->name];
@ -390,7 +534,12 @@ private:
// to reduce the cost of tracking we only track this for user globals
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
@ -401,7 +550,7 @@ private:
// we need to find a common prefix between all uses of a global
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++;
g.functionRef.resize(prefix);

View file

@ -14,7 +14,6 @@
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAG(LuauTypeAliasDefaults)
LUAU_FASTFLAG(LuauImmutableTypes)
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);
std::optional<TypeId> defaultValue;
if (FFlag::LuauTypeAliasDefaults && param.defaultValue)
if (param.defaultValue)
defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState);
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);
std::optional<TypePackId> defaultValue;
if (FFlag::LuauTypeAliasDefaults && param.defaultValue)
if (param.defaultValue)
defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState);
result.typePackParams.push_back({tp, defaultValue});

View file

@ -10,8 +10,6 @@
#include <algorithm>
#include <stdexcept>
LUAU_FASTFLAG(LuauTypeAliasDefaults)
/*
* Prefix generic typenames with gen-
* Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4
@ -288,28 +286,15 @@ struct TypeVarStringifier
else
first = false;
if (FFlag::LuauTypeAliasDefaults)
{
bool wrap = !singleTp && get<TypePack>(follow(tp));
bool wrap = !singleTp && get<TypePack>(follow(tp));
if (wrap)
state.emit("(");
if (wrap)
state.emit("(");
stringify(tp);
stringify(tp);
if (wrap)
state.emit(")");
}
else
{
if (!singleTp)
state.emit("(");
stringify(tp);
if (!singleTp)
state.emit(")");
}
if (wrap)
state.emit(")");
}
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));
}
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)
{
if (!FFlag::LuauTypeAliasDefaults)
return toStringNamedFunction_DEPRECATED(prefix, ftv, opts);
ToStringResult result;
StringifierState state(opts, result, opts.nameMap);
TypeVarStringifier tvs{state};

View file

@ -10,8 +10,6 @@
#include <limits>
#include <math.h>
LUAU_FASTFLAG(LuauTypeAliasDefaults)
namespace
{
bool isIdentifierStartChar(char c)
@ -796,21 +794,14 @@ struct Printer
{
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)
{
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*o.defaultValue);
}
}
else
if (o.defaultValue)
{
writer.identifier(o.name.value);
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*o.defaultValue);
}
}
@ -818,23 +809,15 @@ struct Printer
{
comma();
if (FFlag::LuauTypeAliasDefaults)
{
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.symbol("...");
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.symbol("...");
if (o.defaultValue)
{
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypePackAnnotation(*o.defaultValue, false);
}
}
else
if (o.defaultValue)
{
writer.identifier(o.name.value);
writer.symbol("...");
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypePackAnnotation(*o.defaultValue, false);
}
}
@ -882,18 +865,14 @@ struct Printer
{
comma();
if (FFlag::LuauTypeAliasDefaults)
writer.advance(o.location.begin);
writer.advance(o.location.begin);
writer.identifier(o.name.value);
}
for (const auto& o : func.genericPacks)
{
comma();
if (FFlag::LuauTypeAliasDefaults)
writer.advance(o.location.begin);
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.symbol("...");
}
@ -1023,18 +1002,14 @@ struct Printer
{
comma();
if (FFlag::LuauTypeAliasDefaults)
writer.advance(o.location.begin);
writer.advance(o.location.begin);
writer.identifier(o.name.value);
}
for (const auto& o : a->genericPacks)
{
comma();
if (FFlag::LuauTypeAliasDefaults)
writer.advance(o.location.begin);
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.symbol("...");
}

View file

@ -61,22 +61,52 @@ void DEPRECATED_TxnLog::concat(DEPRECATED_TxnLog rhs)
bool DEPRECATED_TxnLog::haveSeen(TypeId lhs, TypeId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs);
}
void DEPRECATED_TxnLog::pushSeen(TypeId lhs, TypeId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
sharedSeen->push_back(sortedPair);
pushSeen((TypeOrPackId)lhs, (TypeOrPackId)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);
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());
sharedSeen->pop_back();
}
@ -186,10 +216,40 @@ TxnLog TxnLog::inverse()
}
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);
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))
{
return true;
@ -203,19 +263,19 @@ bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const
return false;
}
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{
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);
}
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{
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());
sharedSeen->pop_back();
}

View file

@ -29,22 +29,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false)
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree)
LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false)
LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false)
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 g(x:number):number return f(x) end
// ```
if (FFlag::LuauQuantifyInPlace2 ? containsFunctionCallOrReturn(**protoIter) : containsFunctionCall(**protoIter))
if (containsFunctionCallOrReturn(**protoIter))
{
while (checkIter != protoIter)
{
@ -1676,7 +1674,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
}
else if (tableType->state == TableState::Free)
{
TypeId result = FFlag::LuauAscribeCorrectLevelToInferredProperitesOfFreeTables ? freshType(tableType->level) : freshType(scope);
TypeId result = freshType(tableType->level);
tableType->props[name] = {result};
return result;
}
@ -1776,31 +1774,62 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
{
std::set<TypeId> s;
for (TypeId t : types)
if (FFlag::LuauDoNotAccidentallyDependOnPointerOrdering)
{
if (const UnionTypeVar* utv = get<UnionTypeVar>(follow(t)))
std::vector<TypeId> result;
for (TypeId t : types)
{
std::vector<TypeId> r = reduceUnion(utv->options);
for (TypeId ty : r)
s.insert(ty);
t = follow(t);
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
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.
for (TypeId t : s)
return result;
}
else
{
t = follow(t);
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
return {t};
}
std::set<TypeId> s;
std::vector<TypeId> r(s.begin(), s.end());
std::sort(r.begin(), r.end());
return r;
for (TypeId t : types)
{
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)
@ -2811,7 +2840,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
}
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)};
return resultType;
}
@ -4453,51 +4482,6 @@ TypePackId ReplaceGenerics::clean(TypePackId tp)
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)
{
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())
return ty;
if (FFlag::LuauQuantifyInPlace2)
{
Luau::quantify(ty, scope->level);
return ty;
}
Quantification quantification{&currentModule->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;
Luau::quantify(ty, scope->level);
return ty;
}
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())
return tf->type;
bool hasDefaultTypes = false;
bool hasDefaultPacks = 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) {
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())
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);
}
@ -4986,72 +4934,69 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (typePackParams.empty() && !extraTypes.empty())
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();
size_t typesRequired = tf->typeParams.size();
// 'applyTypeFunction' is used to substitute default types that reference previous generic types
ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes, scope->level};
size_t packsProvided = typePackParams.size();
size_t packsRequired = tf->typePackParams.size();
for (size_t i = 0; i < typesProvided; ++i)
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i];
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)
if (typesProvided < typesRequired)
{
// 'applyTypeFunction' is used to substitute default types that reference previous generic types
ApplyTypeFunction applyTypeFunction{&currentModule->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)
{
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);
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);
reportError(annotation.location, UnificationTooComplex{});
maybeInstantiated = errorRecoveryType(scope);
}
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = *maybeInstantiated;
typeParams.push_back(*maybeInstantiated);
}
}
for (size_t i = 0; i < packsProvided; ++i)
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = typePackParams[i];
for (size_t i = 0; i < packsProvided; ++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);
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);
reportError(annotation.location, UnificationTooComplex{});
maybeInstantiated = errorRecoveryTypePack(scope);
}
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;
// TODO: CLI-46926 it's not a good idea to rename the type here
TypeId target = follow(instantiated);
bool needsClone = follow(tf.type) == target;
bool shouldMutate = (!FFlag::LuauOnlyMutateInstantiatedTables || getTableType(tf.type));
TableTypeVar* ttv = getMutableTableType(target);
if (ttv && needsClone)
if (shouldMutate && ttv && needsClone)
{
// 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
@ -5368,7 +5313,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
}
}
if (ttv)
if (shouldMutate && ttv)
{
ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams;
@ -5382,7 +5327,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
{
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;
@ -5390,7 +5335,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
{
std::optional<TypeId> defaultValue;
if (FFlag::LuauTypeAliasDefaults && generic.defaultValue)
if (generic.defaultValue)
defaultValue = resolveType(scope, *generic.defaultValue);
Name n = generic.name.value;
@ -5426,7 +5371,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
{
std::optional<TypePackId> defaultValue;
if (FFlag::LuauTypeAliasDefaults && genericPack.defaultValue)
if (genericPack.defaultValue)
defaultValue = resolveTypePack(scope, *genericPack.defaultValue);
Name n = genericPack.name.value;

View file

@ -24,6 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
LUAU_FASTFLAG(LuauDiscriminableUnions2)
namespace Luau
@ -157,6 +158,7 @@ bool isNumber(TypeId ty)
return isPrim(ty, PrimitiveTypeVar::Number);
}
// Returns true when ty is a subtype of string
bool isString(TypeId ty)
{
if (isPrim(ty, PrimitiveTypeVar::String) || get<StringSingleton>(get<SingletonTypeVar>(follow(ty))))
@ -168,6 +170,27 @@ bool isString(TypeId ty)
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)
{
return isPrim(ty, PrimitiveTypeVar::Thread);
@ -684,7 +707,7 @@ TypeId SingletonTypes::makeStringMetatable()
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}},
{"upper", {stringToStringType}},
{"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{
arena->addTypePack(TypePack{{stringType}, anyTypePack}),
oneStringPack,
@ -761,6 +784,8 @@ void persist(TypeId ty)
}
else if (auto ttv = get<TableTypeVar>(t))
{
LUAU_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed);
for (const auto& [_name, prop] : ttv->props)
queue.push_back(prop.type);

View file

@ -18,11 +18,13 @@ LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree, false)
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, true)
namespace Luau
{
@ -329,7 +331,7 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance
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)
: types(types)
, mode(mode)
@ -656,26 +658,85 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
failed = true;
}
if (FFlag::LuauUseCommittingTxnLog)
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter)
{
if (i == count - 1)
{
log.concat(std::move(innerState.log));
}
if (!FFlag::LuauUseCommittingTxnLog)
innerState.DEPRECATED_log.rollback();
}
else
{
if (i != count - 1)
if (FFlag::LuauUseCommittingTxnLog)
{
innerState.DEPRECATED_log.rollback();
if (i == count - 1)
{
log.concat(std::move(innerState.log));
}
}
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)
@ -1163,6 +1224,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (superTp == subTp)
return;
if (FFlag::LuauTxnLogSeesTypePacks2 && log.haveSeen(superTp, subTp))
return;
if (log.getMutable<Unifiable::Free>(superTp))
{
occursCheck(superTp, subTp);
@ -1365,6 +1429,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (superTp == subTp)
return;
if (FFlag::LuauTxnLogSeesTypePacks2 && DEPRECATED_log.haveSeen(superTp, subTp))
return;
if (get<Unifiable::Free>(superTp))
{
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]);
}
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;
if (!isFunctionCall)
@ -1708,6 +1786,17 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
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--)
{
if (FFlag::LuauUseCommittingTxnLog)
@ -1760,7 +1849,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
std::vector<std::string> extraProperties;
// 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)
{
@ -1769,7 +1858,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
bool isAny =
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);
}
@ -1781,7 +1870,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
}
// 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)
{
for (const auto& [propName, subProp] : subTable->props)
@ -1790,7 +1879,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
bool isAny =
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);
}
@ -1830,7 +1919,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
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: 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();
}
}
else if (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type)))
// TODO: this case is unsound, but without it our test suite fails. CLI-46031
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
// 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: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
{
}
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
// 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: 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 (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 (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
{
}
else if (superTable->state == TableState::Free)
@ -2333,7 +2422,7 @@ void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersec
bool errorReported = false;
// 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)
{

View file

@ -11,7 +11,6 @@
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false)
LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false)
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
@ -779,7 +778,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
if (!name)
name = Name(nameError, lexer.current().location);
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ FFlag::LuauParseTypeAliasDefaults);
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ true);
expectAndConsume('=', "type alias");

View file

@ -413,29 +413,22 @@ static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer)
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");
if (const char* home = getenv("HOME"))
{
historyFilepath = joinPaths(home, 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) */);
path = joinPaths(home, name);
}
else if (const char* userProfile = getenv("USERPROFILE"))
{
path = joinPaths(userProfile, name);
}
~LinenoiseScopedHistory() {}
std::string historyFilepath;
};
if (!path.empty())
ic_set_history(path.c_str(), -1 /* default entries (= 200) */);
}
static void runReplImpl(lua_State* L)
{
@ -447,8 +440,10 @@ static void runReplImpl(lua_State* L)
// Prevent auto insertion of braces
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;
LinenoiseScopedHistory scopedHistory;
for (;;)
{

View file

@ -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_clonefunction(lua_State* L, int idx);
/*
** 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 */
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_getargument(lua_State* L, int level, int n);
LUA_API const char* lua_getlocal(lua_State* L, int level, int n);

View file

@ -14,7 +14,7 @@
#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"
"$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");
hvalue(obj)->metatable = mt;
if (mt)
{
if (FFlag::LuauGcForwardMetatableBarrier)
{
luaC_objbarrier(L, hvalue(obj), mt);
}
else
{
luaC_objbarriert(L, hvalue(obj), mt);
}
}
luaC_objbarrier(L, hvalue(obj), mt);
break;
}
case LUA_TUSERDATA:
@ -1069,6 +1060,8 @@ int lua_gc(lua_State* L, int what, int data)
g->GCthreshold = 0;
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
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 (g->gcstate != GCSpause)
{
@ -1299,6 +1317,18 @@ void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*))
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)
{
return &L->global->cb;

View file

@ -11,8 +11,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauSchubfach)
/* convert a stack index to positive */
#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))
{
case LUA_TNUMBER:
if (FFlag::LuauSchubfach)
{
double n = lua_tonumber(L, idx);
char s[LUAI_MAXNUM2STR];
char* e = luai_num2str(s, n);
lua_pushlstring(L, s, e - s);
}
else
{
lua_pushstring(L, lua_tostring(L, idx));
}
{
double n = lua_tonumber(L, idx);
char s[LUAI_MAXNUM2STR];
char* e = luai_num2str(s, n);
lua_pushlstring(L, s, e - s);
break;
}
case LUA_TSTRING:
lua_pushvalue(L, idx);
break;
@ -505,29 +498,18 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
{
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];
char* e = s;
for (int i = 0; i < LUA_VECTOR_SIZE; ++i)
if (i != 0)
{
if (i != 0)
{
*e++ = ',';
*e++ = ' ';
}
e = luai_num2str(e, v[i]);
*e++ = ',';
*e++ = ' ';
}
lua_pushlstring(L, s, e - s);
}
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
e = luai_num2str(e, v[i]);
}
lua_pushlstring(L, s, e - s);
break;
}
default:

View file

@ -168,6 +168,11 @@ static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f,
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 status = 0;

View file

@ -11,6 +11,8 @@
#include "lmem.h"
#include "ludata.h"
LUAU_FASTFLAGVARIABLE(LuauGcAdditionalStats, false)
#include <string.h>
#define GC_SWEEPMAX 40
@ -53,17 +55,28 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
case GCSpause:
// record root mark time if we have switched to next state
if (g->gcstate == GCSpropagate)
{
g->gcstats.currcycle.marktime += seconds;
if (FFlag::LuauGcAdditionalStats && assist)
g->gcstats.currcycle.markassisttime += seconds;
}
break;
case GCSpropagate:
case GCSpropagateagain:
g->gcstats.currcycle.marktime += seconds;
if (FFlag::LuauGcAdditionalStats && assist)
g->gcstats.currcycle.markassisttime += seconds;
break;
case GCSatomic:
g->gcstats.currcycle.atomictime += seconds;
break;
case GCSsweep:
g->gcstats.currcycle.sweeptime += seconds;
if (FFlag::LuauGcAdditionalStats && assist)
g->gcstats.currcycle.sweepassisttime += seconds;
break;
default:
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)
{
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)
@ -585,10 +598,21 @@ static size_t atomic(lua_State* L)
LUAU_ASSERT(g->gcstate == GCSatomic);
size_t work = 0;
double currts = lua_clock();
double prevts = currts;
/* remark occasional upvalues of (maybe) dead threads */
work += remarkupvals(g);
/* traverse objects caught by write barrier and by 'remarkupvals' */
work += propagateall(g);
if (FFlag::LuauGcAdditionalStats)
{
currts = lua_clock();
g->gcstats.currcycle.atomictimeupval += currts - prevts;
prevts = currts;
}
/* remark weak tables */
g->gray = g->weak;
g->weak = NULL;
@ -596,16 +620,41 @@ static size_t atomic(lua_State* L)
markobject(g, L); /* mark running thread */
markmt(g); /* mark basic metatables (again) */
work += propagateall(g);
if (FFlag::LuauGcAdditionalStats)
{
currts = lua_clock();
g->gcstats.currcycle.atomictimeweak += currts - prevts;
prevts = currts;
}
/* remark gray again */
g->gray = g->grayagain;
g->grayagain = NULL;
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;
if (FFlag::LuauGcAdditionalStats)
{
currts = lua_clock();
g->gcstats.currcycle.atomictimeclear += currts - prevts;
}
/* flip current white */
g->currentwhite = cast_byte(otherwhite(g));
g->sweepgcopage = g->allgcopages;
g->gcstate = GCSsweep;
return work;
}
@ -693,6 +742,9 @@ static size_t gcstep(lua_State* L, size_t limit)
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
g->gray = g->grayagain;
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 (FFlag::LuauGcAdditionalStats)
g->gcstats.currcycle.propagateagainwork =
g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork - g->gcstats.currcycle.propagatework;
g->gcstate = GCSatomic;
}
break;
@ -811,6 +867,12 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal)
void luaC_step(lua_State* L, bool assist)
{
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 */
LUAU_ASSERT(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);
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
if (g->gcstate == GCSpause)
{
@ -844,6 +911,9 @@ void luaC_step(lua_State* L, bool assist)
finishGcCycleStats(g);
if (FFlag::LuauGcAdditionalStats)
g->gcstats.currcycle.starttotalsizebytes = g->totalbytes;
g->gcstats.currcycle.heapgoalsizebytes = heapgoal;
g->gcstats.currcycle.heaptriggersizebytes = heaptrigger;
}

View file

@ -111,13 +111,6 @@
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) \
{ \
if (iscollectable(tv) && iswhite(gcvalue(tv)) && (!(uv) || ((UpVal*)uv)->v != &((UpVal*)uv)->u.value)) \

View file

@ -6,7 +6,6 @@
#include "lcommon.h"
#include <string.h>
#include <stdio.h> // TODO: Remove with LuauSchubfach
#ifdef _MSC_VER
#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.
LUAU_FASTFLAGVARIABLE(LuauSchubfach, false)
// 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
// 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)
{
if (!FFlag::LuauSchubfach)
{
snprintf(buf, LUAI_MAXNUM2STR, LUA_NUMBER_FMT, n);
return buf + strlen(buf);
}
// IEEE-754
union
{

View file

@ -55,7 +55,6 @@ LUAU_FASTMATH_END
#define luai_num2unsigned(i, n) ((i) = (unsigned)(long long)(n))
#endif
#define LUA_NUMBER_FMT "%.14g" /* TODO: Remove with LuauSchubfach */
#define LUAI_MAXNUM2STR 48
LUAI_FUNC char* luai_num2str(char* buf, double n);

View file

@ -77,25 +77,46 @@ typedef struct CallInfo
struct GCCycleStats
{
size_t starttotalsizebytes = 0;
size_t heapgoalsizebytes = 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 endtimestamp = 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;
size_t atomicstarttotalsizebytes = 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 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 explicitwork = 0;
size_t propagatework = 0;
size_t propagateagainwork = 0;
size_t endtotalsizebytes = 0;
};

View file

@ -447,7 +447,8 @@ void luaH_free(lua_State* L, Table* t, lua_Page* page)
{
if (t->node != dummynode)
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);
}

View file

@ -2,6 +2,7 @@
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
#include "lualib.h"
#include "lapi.h"
#include "lstate.h"
#include "ltable.h"
#include "lstring.h"
@ -9,6 +10,8 @@
#include "ldebug.h"
#include "lvm.h"
LUAU_FASTFLAGVARIABLE(LuauTableClone, false)
static int foreachi(lua_State* L)
{
luaL_checktype(L, 1, LUA_TTABLE);
@ -507,6 +510,23 @@ static int tisfrozen(lua_State* L)
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[] = {
{"concat", tconcat},
{"foreach", foreach},
@ -524,6 +544,7 @@ static const luaL_Reg tab_funcs[] = {
{"clear", tclear},
{"freeze", tfreeze},
{"isfrozen", tisfrozen},
{"clone", tclone},
{NULL, NULL},
};

View file

@ -74,4 +74,7 @@ function test()
end
end
bench.runs = 6
bench.extraRuns = 2
bench.runCode(test, "GC: Boehm tree")

View file

@ -40,7 +40,7 @@ function test()
local tree = { id = 0 }
for i = 1,1000 do
for i = 1,100 do
fill_tree(tree, 10)
prune_tree(tree, 0)

View file

@ -42,7 +42,7 @@ function test()
local tree = { id = 0 }
fill_tree(tree, 16)
for i = 1,1000 do
for i = 1,100 do
local small_tree = { id = 0 }
fill_tree(small_tree, 8)

View file

@ -46,7 +46,7 @@ function test()
local tree = { id = 0 }
for i = 1,1000 do
for i = 1,100 do
fill_tree(tree, 10)
prune_tree(tree, 0)

View file

@ -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 `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.
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 );
/// \}

View file

@ -6,8 +6,6 @@
#include <string.h>
#include <stdlib.h>
LUAU_FASTFLAG(LuauSchubfach);
#define LUAI_MAXNUM2STR 48
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)
return 0;
FFlag::LuauSchubfach.value = true;
double num;
memcpy(&num, Data, 8);

View file

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

View file

@ -15,6 +15,7 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAG(LuauTableCloneType)
using namespace Luau;
@ -262,7 +263,7 @@ TEST_CASE_FIXTURE(ACFixture, "get_member_completions")
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("pack"));
CHECK(!ac.entryMap.count("math"));
@ -2235,7 +2236,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource")
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("pack"));
CHECK(!ac.entryMap.count("math"));
@ -2695,8 +2696,6 @@ local r4 = t:bar1(@4)
TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_parameters")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
check(R"(
type A<T = @1> = () -> T
)");
@ -2709,8 +2708,6 @@ type A<T = @1> = () -> T
TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_pack_parameters")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
check(R"(
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")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
ScopedFastFlag preferToCallFunctionsForIntersects("PreferToCallFunctionsForIntersects", true);
check(R"(
local bar: ((number) -> number) & (number, number) -> number)

View file

@ -241,6 +241,8 @@ TEST_CASE("Math")
TEST_CASE("Table")
{
ScopedFastFlag sff("LuauTableClone", true);
runConformance("nextvar.lua");
}
@ -465,6 +467,8 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
TEST_CASE("Types")
{
ScopedFastFlag sff("LuauTableCloneType", true);
runConformance("types.lua", [](lua_State* L) {
Luau::NullModuleResolver moduleResolver;
Luau::InternalErrorReporter iceHandler;
@ -959,8 +963,6 @@ TEST_CASE("Coverage")
TEST_CASE("StringConversion")
{
ScopedFastFlag sff{"LuauSchubfach", true};
runConformance("strconv.lua");
}

View file

@ -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");
}
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")
{
LintResult result = lint(R"(

View file

@ -1988,8 +1988,6 @@ TEST_CASE_FIXTURE(Fixture, "function_type_matching_parenthesis")
TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
AstStat* stat = parse(R"(
type A<T = string> = {}
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")
{
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 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}});
@ -2574,8 +2570,6 @@ do end
TEST_CASE_FIXTURE(Fixture, "recover_expected_type_pack")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ParseResult result = tryParse(R"(
type Y<T..., U = T...> = (T...) -> U...
)");

View file

@ -96,8 +96,6 @@ n2 [label="number"];
TEST_CASE_FIXTURE(Fixture, "function")
{
ScopedFastFlag luauQuantifyInPlace2{"LuauQuantifyInPlace2", true};
CheckResult result = check(R"(
local function f(a, ...: string) return a end
)");

View file

@ -500,8 +500,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
{
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
local function f(a: number, b: string) end
local function test<T..., U...>(...: T...): U...

View file

@ -641,9 +641,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_to_string")
TEST_CASE_FIXTURE(Fixture, "transpile_type_alias_default_type_parameters")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
std::string code = R"(
type Packed<T = string, U = T, V... = ...boolean, W... = (T, U, V...)> = (T, U, V...)->(W...)
local a: Packed<number>

View file

@ -625,9 +625,8 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni
ScopedFastFlag sff[] = {
{"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},
{"LuauQuantifyInPlace2", true},
};
CheckResult result = check(R"(

View file

@ -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")));
}
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();

View file

@ -697,4 +697,93 @@ end
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();

View file

@ -8,7 +8,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauEqConstraint)
LUAU_FASTFLAG(LuauQuantifyInPlace2)
using namespace Luau;
@ -40,16 +39,6 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
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"(
function f(a:{fn:()->(a,b...)}): ()
if type(a) == 'boolean'then
@ -60,10 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end
)";
if (FFlag::LuauQuantifyInPlace2)
CHECK_EQ(expected, decorateWithTypes(code));
else
CHECK_EQ(old_expected, decorateWithTypes(code));
CHECK_EQ(expected, decorateWithTypes(code));
}
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));
}
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.
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
// It's unsound to instantiate tables containing generic methods,
// 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);
}
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")
{
ScopedFastFlag sff[]{
{"LuauQuantifyInPlace2", 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")
{
ScopedFastFlag sff{"LuauQuantifyInPlace2", true};
CheckResult result = check(R"(
local function f() return 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")
{
ScopedFastFlag sff{"LuauQuantifyInPlace2", true};
CheckResult result = check(R"(
--!strict
local function f(...) return ... end

View file

@ -8,7 +8,6 @@
LUAU_FASTFLAG(LuauDiscriminableUnions2)
LUAU_FASTFLAG(LuauWeakEqConstraint)
LUAU_FASTFLAG(LuauQuantifyInPlace2)
using namespace Luau;
@ -1179,20 +1178,14 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
{
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]));
else
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]));
}
CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector"
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"
else
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"
}
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to_vector")

View file

@ -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")));
}
// TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
// {
// ScopedFastFlag sff[]{
// {"LuauParseSingletonTypes", true},
// {"LuauSingletonTypes", true},
// {"LuauDiscriminableUnions2", true},
// {"LuauEqConstraint", true},
// {"LuauWidenIfSupertypeIsFree", true},
// {"LuauWeakEqConstraint", false},
// };
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", true},
{"LuauEqConstraint", true},
{"LuauWidenIfSupertypeIsFree", true},
{"LuauWeakEqConstraint", false},
{"LuauDoNotAccidentallyDependOnPointerOrdering", true}
};
// CheckResult result = check(R"(
// local function foo(f, x): "hello"? -- anyone there?
// return if x == "hi"
// then f(x)
// else nil
// end
// )");
CheckResult result = check(R"(
local function foo(f, x): "hello"? -- anyone there?
return if x == "hi"
then f(x)
else nil
end
)");
// LUAU_REQUIRE_NO_ERRORS(result);
LUAU_REQUIRE_NO_ERRORS(result);
// CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
// CHECK_EQ(R"(<a, b...>((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo")));
// }
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
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")
{

View file

@ -1219,13 +1219,12 @@ TEST_CASE_FIXTURE(Fixture, "passing_compatible_unions_to_a_generic_table_without
{
CheckResult result = check(R"(
type A = {x: number, y: number, [any]: any} | {y: number}
local a: A
function f(t)
t.y = 1
end
f(a)
f({y = 5} :: A)
)");
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 }, { } })");
}
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")
{
CheckResult result = check(R"(
@ -2257,4 +2294,44 @@ TEST_CASE_FIXTURE(Fixture, "confusing_indexing")
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();

View file

@ -4044,6 +4044,49 @@ type t0<t32> = any
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")
{
CheckResult result = check(R"(
@ -4065,6 +4108,21 @@ return m
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")
{
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);
}
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();

View file

@ -7,8 +7,6 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauQuantifyInPlace2);
using namespace Luau;
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
@ -167,10 +165,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "typepack_unification_should_trim_free_tails"
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauQuantifyInPlace2)
CHECK_EQ("(number) -> boolean", toString(requireType("f")));
else
CHECK_EQ("(number) -> (boolean)", toString(requireType("f")));
CHECK_EQ("(number) -> boolean", toString(requireType("f")));
}
TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification")

View file

@ -622,9 +622,6 @@ type Other = Packed<number, string>
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_explicit")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
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")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
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")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
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")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type Y<T... = (string, number)> = { a: (T...) -> () }
local a: Y<>
@ -715,9 +703,6 @@ local a: Y<>
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_ty")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
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")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type Y<T..., U... = T...> = { a: (T...) -> U... }
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")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type Y<T..., U... = T..., V... = U...> = { a: (T...) -> U..., b: (T...) -> V... }
local a: Y<number, string>
@ -761,9 +740,6 @@ local a: Y<number, string>
TEST_CASE_FIXTURE(Fixture, "type_alias_default_mixed_self")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type Y<T, U = T, V... = ...number, W... = (T, U, V...)> = { a: (T, U, V...) -> W... }
local a: Y<number>
@ -782,9 +758,6 @@ local d: Y<number, string, ...boolean, ...() -> ()>
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type Y<T = T> = { a: T }
local a: Y = { a = 2 }
@ -834,9 +807,6 @@ local a: Y<...number>
TEST_CASE_FIXTURE(Fixture, "type_alias_default_export")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
fileResolver.source["Module/Types"] = R"(
export type A<T, U = string> = { 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")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type Y<T... = ...string> = (T...) -> number
local a: Y
@ -897,9 +864,6 @@ local a: Y
TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_confusing_types")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type A<T, U = T, V... = ...any, W... = V...> = (T, V...) -> (U, W...)
type B = A<string, (number)>
@ -914,9 +878,6 @@ type C = A<string, (number), (boolean)>
TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_recursive_type")
{
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true};
CheckResult result = check(R"(
type F<K = string, V = (K) -> ()> = (K) -> V
type R = { m: F<R> }

View file

@ -400,10 +400,10 @@ local e = a.z
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"(
local x: { x: number } = { x = 3 }
local x = { x = 3 }
type A = number?
type B = string?
local y: { x: number, y: A | B }
@ -413,7 +413,7 @@ y = x
LUAU_REQUIRE_NO_ERRORS(result);
result = check(R"(
local x: { x: number } = { x = 3 }
local x = { x = 3 }
local a: number? = 2
local y = {}
@ -426,6 +426,31 @@ y = x
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")
{
CheckResult result = check(R"(

View file

@ -512,4 +512,42 @@ do
assert(#t == 7)
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"