Sync to upstream/release/520 (#427)

This commit is contained in:
Arseny Kapoulkine 2022-03-24 15:04:14 -07:00 committed by GitHub
parent 5e7e462104
commit 2c339d52c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 804 additions and 562 deletions

View File

@ -96,6 +96,7 @@ struct CountMismatch
size_t expected; size_t expected;
size_t actual; size_t actual;
Context context = Arg; Context context = Arg;
bool isVariadic = false;
bool operator==(const CountMismatch& rhs) const; bool operator==(const CountMismatch& rhs) const;
}; };

View File

@ -32,6 +32,7 @@ struct ToStringOptions
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength); size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
std::optional<ToStringNameMap> nameMap; std::optional<ToStringNameMap> nameMap;
std::shared_ptr<Scope> scope; // If present, module names will be added and types that are not available in scope will be marked as 'invalid' std::shared_ptr<Scope> scope; // If present, module names will be added and types that are not available in scope will be marked as 'invalid'
std::vector<std::string> namedFunctionOverrideArgNames; // If present, named function argument names will be overridden
}; };
struct ToStringResult struct ToStringResult
@ -65,7 +66,7 @@ inline std::string toString(TypePackId ty)
std::string toString(const TypeVar& tv, const ToStringOptions& opts = {}); std::string toString(const TypeVar& tv, const ToStringOptions& opts = {});
std::string toString(const TypePackVar& tp, const ToStringOptions& opts = {}); std::string toString(const TypePackVar& tp, const ToStringOptions& opts = {});
std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeVar& ftv, ToStringOptions opts = {}); std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, const ToStringOptions& opts = {});
// It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class // It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression // These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression

View File

@ -119,9 +119,9 @@ bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
TypePackId follow(TypePackId tp); TypePackId follow(TypePackId tp);
TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper); TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper);
size_t size(TypePackId tp); size_t size(TypePackId tp, TxnLog* log = nullptr);
bool finite(TypePackId tp); bool finite(TypePackId tp, TxnLog* log = nullptr);
size_t size(const TypePack& tp); size_t size(const TypePack& tp, TxnLog* log = nullptr);
std::optional<TypeId> first(TypePackId tp); std::optional<TypeId> first(TypePackId tp);
TypePackVar* asMutable(TypePackId tp); TypePackVar* asMutable(TypePackId tp);

View File

@ -488,6 +488,9 @@ const TableTypeVar* getTableType(TypeId type);
// Returns nullptr if the type has no name. // Returns nullptr if the type has no name.
const std::string* getName(TypeId type); const std::string* getName(TypeId type);
// Returns name of the module where type was defined if type has that information
std::optional<ModuleName> getDefinitionModuleName(TypeId type);
// Checks whether a union contains all types of another union. // Checks whether a union contains all types of another union.
bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub); bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub);

View File

@ -90,7 +90,9 @@ private:
TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {}); TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {});
void cacheResult(TypeId subTy, TypeId superTy); bool canCacheResult(TypeId subTy, TypeId superTy);
void cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount);
void cacheResult_DEPRECATED(TypeId subTy, TypeId superTy);
public: public:
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false); void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);

View File

@ -2,6 +2,7 @@
#pragma once #pragma once
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -42,6 +43,7 @@ struct UnifierSharedState
DenseHashSet<void*> seenAny{nullptr}; DenseHashSet<void*> seenAny{nullptr};
DenseHashMap<TypeId, bool> skipCacheForType{nullptr}; DenseHashMap<TypeId, bool> skipCacheForType{nullptr};
DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}}; DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}};
DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}};
DenseHashSet<TypeId> tempSeenTy{nullptr}; DenseHashSet<TypeId> tempSeenTy{nullptr};
DenseHashSet<TypePackId> tempSeenTp{nullptr}; DenseHashSet<TypePackId> tempSeenTp{nullptr};

View File

@ -8,6 +8,7 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(BetterDiagnosticCodesInStudio, false); LUAU_FASTFLAGVARIABLE(BetterDiagnosticCodesInStudio, false);
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleName, false);
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
{ {
@ -53,7 +54,32 @@ struct ErrorConverter
{ {
std::string operator()(const Luau::TypeMismatch& tm) const std::string operator()(const Luau::TypeMismatch& tm) const
{ {
std::string result = "Type '" + Luau::toString(tm.givenType) + "' could not be converted into '" + Luau::toString(tm.wantedType) + "'"; std::string givenTypeName = Luau::toString(tm.givenType);
std::string wantedTypeName = Luau::toString(tm.wantedType);
std::string result;
if (FFlag::LuauTypeMismatchModuleName)
{
if (givenTypeName == wantedTypeName)
{
if (auto givenDefinitionModule = getDefinitionModuleName(tm.givenType))
{
if (auto wantedDefinitionModule = getDefinitionModuleName(tm.wantedType))
{
result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName +
"' from '" + *wantedDefinitionModule + "'";
}
}
}
if (result.empty())
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
}
else
{
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
}
if (tm.error) if (tm.error)
{ {
@ -147,7 +173,7 @@ struct ErrorConverter
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) + return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
" are required here"; " are required here";
case CountMismatch::Arg: case CountMismatch::Arg:
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual); return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual, /*argPrefix*/ nullptr, e.isVariadic);
} }
LUAU_ASSERT(!"Unknown context"); LUAU_ASSERT(!"Unknown context");

View File

@ -14,6 +14,7 @@
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false) LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
LUAU_FASTFLAGVARIABLE(LuauLintNoRobloxBits, false)
namespace Luau namespace Luau
{ {
@ -1135,16 +1136,20 @@ private:
enum TypeKind enum TypeKind
{ {
Kind_Invalid, Kind_Unknown,
Kind_Primitive, // primitive type supported by VM - boolean/userdata/etc. No differentiation between types of userdata. Kind_Primitive, // primitive type supported by VM - boolean/userdata/etc. No differentiation between types of userdata.
Kind_Vector, // For 'vector' but only used when type is used Kind_Vector, // 'vector' but only used when type is used
Kind_Userdata, // custom userdata type - Vector3/etc. Kind_Userdata, // custom userdata type
// TODO: remove these with LuauLintNoRobloxBits
Kind_Class, // custom userdata type that reflects Roblox Instance-derived hierarchy - Part/etc. Kind_Class, // custom userdata type that reflects Roblox Instance-derived hierarchy - Part/etc.
Kind_Enum, // custom userdata type referring to an enum item of enum classes, e.g. Enum.NormalId.Back/Enum.Axis.X/etc. Kind_Enum, // custom userdata type referring to an enum item of enum classes, e.g. Enum.NormalId.Back/Enum.Axis.X/etc.
}; };
bool containsPropName(TypeId ty, const std::string& propName) bool containsPropName(TypeId ty, const std::string& propName)
{ {
LUAU_ASSERT(!FFlag::LuauLintNoRobloxBits);
if (auto ctv = get<ClassTypeVar>(ty)) if (auto ctv = get<ClassTypeVar>(ty))
return lookupClassProp(ctv, propName) != nullptr; return lookupClassProp(ctv, propName) != nullptr;
@ -1163,13 +1168,23 @@ private:
if (name == "vector") if (name == "vector")
return Kind_Vector; return Kind_Vector;
if (std::optional<TypeFun> maybeTy = context->scope->lookupType(name)) if (FFlag::LuauLintNoRobloxBits)
// Kind_Userdata is probably not 100% precise but is close enough {
return containsPropName(maybeTy->type, "ClassName") ? Kind_Class : Kind_Userdata; if (std::optional<TypeFun> maybeTy = context->scope->lookupType(name))
else if (std::optional<TypeFun> maybeTy = context->scope->lookupImportedType("Enum", name)) return Kind_Userdata;
return Kind_Enum;
return Kind_Invalid; return Kind_Unknown;
}
else
{
if (std::optional<TypeFun> maybeTy = context->scope->lookupType(name))
// Kind_Userdata is probably not 100% precise but is close enough
return containsPropName(maybeTy->type, "ClassName") ? Kind_Class : Kind_Userdata;
else if (std::optional<TypeFun> maybeTy = context->scope->lookupImportedType("Enum", name))
return Kind_Enum;
return Kind_Unknown;
}
} }
void validateType(AstExprConstantString* expr, std::initializer_list<TypeKind> expected, const char* expectedString) void validateType(AstExprConstantString* expr, std::initializer_list<TypeKind> expected, const char* expectedString)
@ -1177,7 +1192,7 @@ private:
std::string name(expr->value.data, expr->value.size); std::string name(expr->value.data, expr->value.size);
TypeKind kind = getTypeKind(name); TypeKind kind = getTypeKind(name);
if (kind == Kind_Invalid) if (kind == Kind_Unknown)
{ {
emitWarning(*context, LintWarning::Code_UnknownType, expr->location, "Unknown type '%s'", name.c_str()); emitWarning(*context, LintWarning::Code_UnknownType, expr->location, "Unknown type '%s'", name.c_str());
return; return;
@ -1189,7 +1204,7 @@ private:
return; return;
// as a special case, Instance and EnumItem are both a userdata type (as returned by typeof) and a class type // as a special case, Instance and EnumItem are both a userdata type (as returned by typeof) and a class type
if (ek == Kind_Userdata && (name == "Instance" || name == "EnumItem")) if (!FFlag::LuauLintNoRobloxBits && ek == Kind_Userdata && (name == "Instance" || name == "EnumItem"))
return; return;
} }
@ -1198,12 +1213,18 @@ private:
bool acceptsClassName(AstName method) bool acceptsClassName(AstName method)
{ {
LUAU_ASSERT(!FFlag::LuauLintNoRobloxBits);
return method.value[0] == 'F' && (method == "FindFirstChildOfClass" || method == "FindFirstChildWhichIsA" || return method.value[0] == 'F' && (method == "FindFirstChildOfClass" || method == "FindFirstChildWhichIsA" ||
method == "FindFirstAncestorOfClass" || method == "FindFirstAncestorWhichIsA"); method == "FindFirstAncestorOfClass" || method == "FindFirstAncestorWhichIsA");
} }
bool visit(AstExprCall* node) override bool visit(AstExprCall* node) override
{ {
// TODO: Simply remove the override
if (FFlag::LuauLintNoRobloxBits)
return true;
if (AstExprIndexName* index = node->func->as<AstExprIndexName>()) if (AstExprIndexName* index = node->func->as<AstExprIndexName>())
{ {
AstExprConstantString* arg0 = node->args.size > 0 ? node->args.data[0]->as<AstExprConstantString>() : NULL; AstExprConstantString* arg0 = node->args.size > 0 ? node->args.data[0]->as<AstExprConstantString>() : NULL;

View File

@ -12,10 +12,8 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAGVARIABLE(LuauCloneDeclaredGlobals, false) LUAU_FASTFLAGVARIABLE(LuauCloneDeclaredGlobals, false)
LUAU_FASTFLAG(LuauImmutableTypes)
namespace Luau namespace Luau
{ {
@ -65,8 +63,7 @@ TypeId TypeArena::addTV(TypeVar&& tv)
{ {
TypeId allocated = typeVars.allocate(std::move(tv)); TypeId allocated = typeVars.allocate(std::move(tv));
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes) asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated; return allocated;
} }
@ -75,8 +72,7 @@ TypeId TypeArena::freshType(TypeLevel level)
{ {
TypeId allocated = typeVars.allocate(FreeTypeVar{level}); TypeId allocated = typeVars.allocate(FreeTypeVar{level});
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes) asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated; return allocated;
} }
@ -85,8 +81,7 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{ {
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes) asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated; return allocated;
} }
@ -95,8 +90,7 @@ TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{ {
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes) asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated; return allocated;
} }
@ -105,8 +99,7 @@ TypePackId TypeArena::addTypePack(TypePack tp)
{ {
TypePackId allocated = typePacks.allocate(std::move(tp)); TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes) asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated; return allocated;
} }
@ -115,8 +108,7 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
{ {
TypePackId allocated = typePacks.allocate(std::move(tp)); TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes) asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated; return allocated;
} }
@ -439,16 +431,9 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState}; TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
if (FFlag::LuauImmutableTypes) // Persistent types are not being cloned and we get the original type back which might be read-only
{ if (!res->persistent)
// Persistent types are not being cloned and we get the original type back which might be read-only
if (!res->persistent)
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
else
{
asMutable(res)->documentationSymbol = typeId->documentationSymbol; asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
} }
return res; return res;

View File

@ -16,6 +16,7 @@
* Fair warning: Setting this will break a lot of Luau unit tests. * Fair warning: Setting this will break a lot of Luau unit tests.
*/ */
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false) LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false)
LUAU_FASTFLAGVARIABLE(LuauDocFuncParameters, false)
namespace Luau namespace Luau
{ {
@ -769,6 +770,7 @@ struct TypePackStringifier
else else
state.emit(", "); state.emit(", ");
// Do not respect opts.namedFunctionOverrideArgNames here
if (elemIndex < elemNames.size() && elemNames[elemIndex]) if (elemIndex < elemNames.size() && elemNames[elemIndex])
{ {
state.emit(elemNames[elemIndex]->name); state.emit(elemNames[elemIndex]->name);
@ -1090,13 +1092,13 @@ std::string toString(const TypePackVar& tp, const ToStringOptions& opts)
return toString(const_cast<TypePackId>(&tp), std::move(opts)); return toString(const_cast<TypePackId>(&tp), std::move(opts));
} }
std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeVar& ftv, ToStringOptions opts) std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, const ToStringOptions& opts)
{ {
ToStringResult result; ToStringResult result;
StringifierState state(opts, result, opts.nameMap); StringifierState state(opts, result, opts.nameMap);
TypeVarStringifier tvs{state}; TypeVarStringifier tvs{state};
state.emit(prefix); state.emit(funcName);
if (!opts.hideNamedFunctionTypeParameters) if (!opts.hideNamedFunctionTypeParameters)
tvs.stringify(ftv.generics, ftv.genericPacks); tvs.stringify(ftv.generics, ftv.genericPacks);
@ -1104,28 +1106,59 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
state.emit("("); state.emit("(");
auto argPackIter = begin(ftv.argTypes); auto argPackIter = begin(ftv.argTypes);
auto argNameIter = ftv.argNames.begin();
bool first = true; bool first = true;
while (argPackIter != end(ftv.argTypes)) if (FFlag::LuauDocFuncParameters)
{ {
if (!first) size_t idx = 0;
state.emit(", "); while (argPackIter != end(ftv.argTypes))
first = false;
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (argNameIter != ftv.argNames.end())
{ {
state.emit((*argNameIter ? (*argNameIter)->name : "_") + ": "); if (!first)
++argNameIter; state.emit(", ");
} first = false;
else
{
state.emit("_: ");
}
tvs.stringify(*argPackIter); // We don't respect opts.functionTypeArguments
++argPackIter; if (idx < opts.namedFunctionOverrideArgNames.size())
{
state.emit(opts.namedFunctionOverrideArgNames[idx] + ": ");
}
else if (idx < ftv.argNames.size() && ftv.argNames[idx])
{
state.emit(ftv.argNames[idx]->name + ": ");
}
else
{
state.emit("_: ");
}
tvs.stringify(*argPackIter);
++argPackIter;
++idx;
}
}
else
{
auto argNameIter = ftv.argNames.begin();
while (argPackIter != end(ftv.argTypes))
{
if (!first)
state.emit(", ");
first = false;
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (argNameIter != ftv.argNames.end())
{
state.emit((*argNameIter ? (*argNameIter)->name : "_") + ": ");
++argNameIter;
}
else
{
state.emit("_: ");
}
tvs.stringify(*argPackIter);
++argPackIter;
}
} }
if (argPackIter.tail()) if (argPackIter.tail())
@ -1134,7 +1167,6 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
state.emit(", "); state.emit(", ");
state.emit("...: "); state.emit("...: ");
if (auto vtp = get<VariadicTypePack>(*argPackIter.tail())) if (auto vtp = get<VariadicTypePack>(*argPackIter.tail()))
tvs.stringify(vtp->ty); tvs.stringify(vtp->ty);
else else

View File

@ -27,10 +27,8 @@ LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as fals
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false) LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
LUAU_FASTFLAGVARIABLE(LuauSealExports, false) LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false) LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false) LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
@ -38,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false) LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauStatFunctionSimplify2, false) LUAU_FASTFLAGVARIABLE(LuauStatFunctionSimplify2, false)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false) LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAG(LuauTypeMismatchModuleName)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false) LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false) LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
@ -47,6 +46,8 @@ LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false)
LUAU_FASTFLAGVARIABLE(LuauFixArgumentCountMismatchAmountWithGenericTypes, false) LUAU_FASTFLAGVARIABLE(LuauFixArgumentCountMismatchAmountWithGenericTypes, false)
LUAU_FASTFLAGVARIABLE(LuauFixIncorrectLineNumberDuplicateType, false) LUAU_FASTFLAGVARIABLE(LuauFixIncorrectLineNumberDuplicateType, false)
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional) LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
LUAU_FASTFLAGVARIABLE(LuauDecoupleOperatorInferenceFromUnifiedTypeInference, false)
LUAU_FASTFLAGVARIABLE(LuauArgCountMismatchSaysAtLeastWhenVariadic, false)
namespace Luau namespace Luau
{ {
@ -291,6 +292,7 @@ ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optiona
// Clear unifier cache since it's keyed off internal types that get deallocated // Clear unifier cache since it's keyed off internal types that get deallocated
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs. // This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
unifierState.cachedUnify.clear(); unifierState.cachedUnify.clear();
unifierState.cachedUnifyError.clear();
unifierState.skipCacheForType.clear(); unifierState.skipCacheForType.clear();
if (FFlag::LuauTwoPassAliasDefinitionFix) if (FFlag::LuauTwoPassAliasDefinitionFix)
@ -1303,7 +1305,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
{ {
// If the table is already named and we want to rename the type function, we have to bind new alias to a copy // If the table is already named and we want to rename the type function, we have to bind new alias to a copy
// Additionally, we can't modify types that come from other modules // Additionally, we can't modify types that come from other modules
if (ttv->name || (FFlag::LuauImmutableTypes && follow(ty)->owningArena != &currentModule->internalTypes)) if (ttv->name || follow(ty)->owningArena != &currentModule->internalTypes)
{ {
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(), bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
binding->typeParams.end(), [](auto&& itp, auto&& tp) { binding->typeParams.end(), [](auto&& itp, auto&& tp) {
@ -1315,7 +1317,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
}); });
// Copy can be skipped if this is an identical alias // Copy can be skipped if this is an identical alias
if ((FFlag::LuauImmutableTypes && !ttv->name) || ttv->name != name || !sameTys || !sameTps) if (!ttv->name || ttv->name != name || !sameTys || !sameTps)
{ {
// This is a shallow clone, original recursive links to self are not updated // This is a shallow clone, original recursive links to self are not updated
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
@ -1349,7 +1351,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty))) else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
{ {
// We can't modify types that come from other modules // We can't modify types that come from other modules
if (!FFlag::LuauImmutableTypes || follow(ty)->owningArena == &currentModule->internalTypes) if (follow(ty)->owningArena == &currentModule->internalTypes)
mtv->syntheticName = name; mtv->syntheticName = name;
} }
@ -1512,14 +1514,14 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
result = {nilType}; result = {nilType};
else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>()) else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>())
{ {
if (FFlag::LuauSingletonTypes && (forceSingleton || (expectedType && maybeSingleton(*expectedType)))) if (forceSingleton || (expectedType && maybeSingleton(*expectedType)))
result = {singletonType(bexpr->value)}; result = {singletonType(bexpr->value)};
else else
result = {booleanType}; result = {booleanType};
} }
else if (const AstExprConstantString* sexpr = expr.as<AstExprConstantString>()) else if (const AstExprConstantString* sexpr = expr.as<AstExprConstantString>())
{ {
if (FFlag::LuauSingletonTypes && (forceSingleton || (expectedType && maybeSingleton(*expectedType)))) if (forceSingleton || (expectedType && maybeSingleton(*expectedType)))
result = {singletonType(std::string(sexpr->value.data, sexpr->value.size))}; result = {singletonType(std::string(sexpr->value.data, sexpr->value.size))};
else else
result = {stringType}; result = {stringType};
@ -2490,12 +2492,24 @@ TypeId TypeChecker::checkBinaryOperation(
lhsType = follow(lhsType); lhsType = follow(lhsType);
rhsType = follow(rhsType); rhsType = follow(rhsType);
if (!isNonstrictMode() && get<FreeTypeVar>(lhsType)) if (FFlag::LuauDecoupleOperatorInferenceFromUnifiedTypeInference)
{ {
auto name = getIdentifierOfBaseVar(expr.left); if (!isNonstrictMode() && get<FreeTypeVar>(lhsType))
reportError(expr.location, CannotInferBinaryOperation{expr.op, name, CannotInferBinaryOperation::Operation}); {
if (!FFlag::LuauErrorRecoveryType) auto name = getIdentifierOfBaseVar(expr.left);
return errorRecoveryType(scope); reportError(expr.location, CannotInferBinaryOperation{expr.op, name, CannotInferBinaryOperation::Operation});
// We will fall-through to the `return anyType` check below.
}
}
else
{
if (!isNonstrictMode() && get<FreeTypeVar>(lhsType))
{
auto name = getIdentifierOfBaseVar(expr.left);
reportError(expr.location, CannotInferBinaryOperation{expr.op, name, CannotInferBinaryOperation::Operation});
if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope);
}
} }
// If we know nothing at all about the lhs type, we can usually say nothing about the result. // If we know nothing at all about the lhs type, we can usually say nothing about the result.
@ -3452,7 +3466,8 @@ void TypeChecker::checkArgumentList(
{ {
if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes) if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes)
minParams = getMinParameterCount(&state.log, paramPack); minParams = getMinParameterCount(&state.log, paramPack);
state.reportError(TypeError{state.location, CountMismatch{minParams, paramIndex}}); bool isVariadic = FFlag::LuauArgCountMismatchSaysAtLeastWhenVariadic && !finite(paramPack, &state.log);
state.reportError(TypeError{state.location, CountMismatch{minParams, paramIndex, CountMismatch::Context::Arg, isVariadic}});
return; return;
} }
++paramIter; ++paramIter;
@ -4163,13 +4178,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
if (FFlag::LuauImmutableTypes) return *moduleType;
return *moduleType;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState);
} }
void TypeChecker::tablify(TypeId type) void TypeChecker::tablify(TypeId type)
@ -4941,10 +4950,19 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (const auto& indexer = table->indexer) if (const auto& indexer = table->indexer)
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType)); tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
return addType(TableTypeVar{ if (FFlag::LuauTypeMismatchModuleName)
props, tableIndexer, scope->level, {
TableState::Sealed // FIXME: probably want a way to annotate other kinds of tables maybe TableTypeVar ttv{props, tableIndexer, scope->level, TableState::Sealed};
}); ttv.definitionModuleName = currentModuleName;
return addType(std::move(ttv));
}
else
{
return addType(TableTypeVar{
props, tableIndexer, scope->level,
TableState::Sealed // FIXME: probably want a way to annotate other kinds of tables maybe
});
}
} }
else if (const auto& func = annotation.as<AstTypeFunction>()) else if (const auto& func = annotation.as<AstTypeFunction>())
{ {
@ -5206,6 +5224,9 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
{ {
ttv->instantiatedTypeParams = typeParams; ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams; ttv->instantiatedTypePackParams = typePackParams;
if (FFlag::LuauTypeMismatchModuleName)
ttv->definitionModuleName = currentModuleName;
} }
return instantiated; return instantiated;

View File

@ -222,20 +222,21 @@ TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper)
} }
} }
size_t size(TypePackId tp) size_t size(TypePackId tp, TxnLog* log)
{ {
if (auto pack = get<TypePack>(follow(tp))) tp = log ? log->follow(tp) : follow(tp);
return size(*pack); if (auto pack = get<TypePack>(tp))
return size(*pack, log);
else else
return 0; return 0;
} }
bool finite(TypePackId tp) bool finite(TypePackId tp, TxnLog* log)
{ {
tp = follow(tp); tp = log ? log->follow(tp) : follow(tp);
if (auto pack = get<TypePack>(tp)) if (auto pack = get<TypePack>(tp))
return pack->tail ? finite(*pack->tail) : true; return pack->tail ? finite(*pack->tail, log) : true;
if (get<VariadicTypePack>(tp)) if (get<VariadicTypePack>(tp))
return false; return false;
@ -243,14 +244,14 @@ bool finite(TypePackId tp)
return true; return true;
} }
size_t size(const TypePack& tp) size_t size(const TypePack& tp, TxnLog* log)
{ {
size_t result = tp.head.size(); size_t result = tp.head.size();
if (tp.tail) if (tp.tail)
{ {
const TypePack* tail = get<TypePack>(follow(*tp.tail)); const TypePack* tail = get<TypePack>(log ? log->follow(*tp.tail) : follow(*tp.tail));
if (tail) if (tail)
result += size(*tail); result += size(*tail, log);
} }
return result; return result;
} }

View File

@ -290,6 +290,24 @@ const std::string* getName(TypeId type)
return nullptr; return nullptr;
} }
std::optional<ModuleName> getDefinitionModuleName(TypeId type)
{
type = follow(type);
if (auto ttv = get<TableTypeVar>(type))
{
if (!ttv->definitionModuleName.empty())
return ttv->definitionModuleName;
}
else if (auto ftv = get<FunctionTypeVar>(type))
{
if (ftv->definition)
return ftv->definition->definitionModuleName;
}
return std::nullopt;
}
bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub) bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub)
{ {
std::unordered_set<TypeId> superTypes; std::unordered_set<TypeId> superTypes;

View File

@ -14,10 +14,9 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000); LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
LUAU_FASTFLAGVARIABLE(LuauExtendedIndexerError, false);
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false); LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false) LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false) LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
@ -26,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogCheckForInvalidation, false) LUAU_FASTFLAGVARIABLE(LuauTxnLogCheckForInvalidation, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false) LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogDontRetryForIndexers, false) LUAU_FASTFLAGVARIABLE(LuauTxnLogDontRetryForIndexers, false)
LUAU_FASTFLAGVARIABLE(LuauUnifierCacheErrors, false)
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional) LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
namespace Luau namespace Luau
@ -63,7 +63,7 @@ struct PromoteTypeLevels
bool operator()(TID ty, const T&) bool operator()(TID ty, const T&)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) if (ty->owningArena != typeArena)
return false; return false;
return true; return true;
@ -83,7 +83,7 @@ struct PromoteTypeLevels
bool operator()(TypeId ty, const FunctionTypeVar&) bool operator()(TypeId ty, const FunctionTypeVar&)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) if (ty->owningArena != typeArena)
return false; return false;
promote(ty, log.getMutable<FunctionTypeVar>(ty)); promote(ty, log.getMutable<FunctionTypeVar>(ty));
@ -93,7 +93,7 @@ struct PromoteTypeLevels
bool operator()(TypeId ty, const TableTypeVar& ttv) bool operator()(TypeId ty, const TableTypeVar& ttv)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) if (ty->owningArena != typeArena)
return false; return false;
if (ttv.state != TableState::Free && ttv.state != TableState::Generic) if (ttv.state != TableState::Free && ttv.state != TableState::Generic)
@ -118,7 +118,7 @@ struct PromoteTypeLevels
static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty) static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) if (ty->owningArena != typeArena)
return; return;
PromoteTypeLevels ptl{log, typeArena, minLevel}; PromoteTypeLevels ptl{log, typeArena, minLevel};
@ -130,7 +130,7 @@ static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel
void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp) void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena) if (tp->owningArena != typeArena)
return; return;
PromoteTypeLevels ptl{log, typeArena, minLevel}; PromoteTypeLevels ptl{log, typeArena, minLevel};
@ -170,7 +170,7 @@ struct SkipCacheForType
bool operator()(TypeId ty, const TableTypeVar&) bool operator()(TypeId ty, const TableTypeVar&)
{ {
// Types from other modules don't contain mutable elements and are ok to cache // Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) if (ty->owningArena != typeArena)
return false; return false;
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty); TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
@ -194,7 +194,7 @@ struct SkipCacheForType
bool operator()(TypeId ty, const T& t) bool operator()(TypeId ty, const T& t)
{ {
// Types from other modules don't contain mutable elements and are ok to cache // Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) if (ty->owningArena != typeArena)
return false; return false;
const bool* prev = skipCacheForType.find(ty); const bool* prev = skipCacheForType.find(ty);
@ -212,7 +212,7 @@ struct SkipCacheForType
bool operator()(TypePackId tp, const T&) bool operator()(TypePackId tp, const T&)
{ {
// Types from other modules don't contain mutable elements and are ok to cache // Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena) if (tp->owningArena != typeArena)
return false; return false;
return true; return true;
@ -445,12 +445,33 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy)) if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
return tryUnifyWithAny(superTy, subTy); return tryUnifyWithAny(superTy, subTy);
bool cacheEnabled = !isFunctionCall && !isIntersection; bool cacheEnabled;
auto& cache = sharedState.cachedUnify; auto& cache = sharedState.cachedUnify;
// What if the types are immutable and we proved their relation before // What if the types are immutable and we proved their relation before
if (cacheEnabled && cache.contains({superTy, subTy}) && (variance == Covariant || cache.contains({subTy, superTy}))) if (FFlag::LuauUnifierCacheErrors)
return; {
cacheEnabled = !isFunctionCall && !isIntersection && variance == Invariant;
if (cacheEnabled)
{
if (cache.contains({subTy, superTy}))
return;
if (auto error = sharedState.cachedUnifyError.find({subTy, superTy}))
{
reportError(TypeError{location, *error});
return;
}
}
}
else
{
cacheEnabled = !isFunctionCall && !isIntersection;
if (cacheEnabled && cache.contains({superTy, subTy}) && (variance == Covariant || cache.contains({subTy, superTy})))
return;
}
// If we have seen this pair of types before, we are currently recursing into cyclic types. // If we have seen this pair of types before, we are currently recursing into cyclic types.
// Here, we assume that the types unify. If they do not, we will find out as we roll back // Here, we assume that the types unify. If they do not, we will find out as we roll back
@ -461,6 +482,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
log.pushSeen(superTy, subTy); log.pushSeen(superTy, subTy);
size_t errorCount = errors.size();
if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(subTy)) if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(subTy))
{ {
tryUnifyUnionWithType(subTy, uv, superTy); tryUnifyUnionWithType(subTy, uv, superTy);
@ -480,8 +503,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
else if (log.getMutable<PrimitiveTypeVar>(superTy) && log.getMutable<PrimitiveTypeVar>(subTy)) else if (log.getMutable<PrimitiveTypeVar>(superTy) && log.getMutable<PrimitiveTypeVar>(subTy))
tryUnifyPrimitives(subTy, superTy); tryUnifyPrimitives(subTy, superTy);
else if (FFlag::LuauSingletonTypes && (log.getMutable<PrimitiveTypeVar>(superTy) || log.getMutable<SingletonTypeVar>(superTy)) && else if ((log.getMutable<PrimitiveTypeVar>(superTy) || log.getMutable<SingletonTypeVar>(superTy)) && log.getMutable<SingletonTypeVar>(subTy))
log.getMutable<SingletonTypeVar>(subTy))
tryUnifySingletons(subTy, superTy); tryUnifySingletons(subTy, superTy);
else if (log.getMutable<FunctionTypeVar>(superTy) && log.getMutable<FunctionTypeVar>(subTy)) else if (log.getMutable<FunctionTypeVar>(superTy) && log.getMutable<FunctionTypeVar>(subTy))
@ -491,8 +513,11 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
tryUnifyTables(subTy, superTy, isIntersection); tryUnifyTables(subTy, superTy, isIntersection);
if (cacheEnabled && errors.empty()) if (!FFlag::LuauUnifierCacheErrors)
cacheResult(subTy, superTy); {
if (cacheEnabled && errors.empty())
cacheResult_DEPRECATED(subTy, superTy);
}
} }
// tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical. // tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical.
@ -512,6 +537,9 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
else else
reportError(TypeError{location, TypeMismatch{superTy, subTy}}); reportError(TypeError{location, TypeMismatch{superTy, subTy}});
if (FFlag::LuauUnifierCacheErrors && cacheEnabled)
cacheResult(subTy, superTy, errorCount);
log.popSeen(superTy, subTy); log.popSeen(superTy, subTy);
} }
@ -646,10 +674,21 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
{ {
TypeId type = uv->options[i]; TypeId type = uv->options[i];
if (cache.contains({type, subTy}) && (variance == Covariant || cache.contains({subTy, type}))) if (FFlag::LuauUnifierCacheErrors)
{ {
startIndex = i; if (cache.contains({subTy, type}))
break; {
startIndex = i;
break;
}
}
else
{
if (cache.contains({type, subTy}) && (variance == Covariant || cache.contains({subTy, type})))
{
startIndex = i;
break;
}
} }
} }
} }
@ -737,10 +776,21 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
{ {
TypeId type = uv->parts[i]; TypeId type = uv->parts[i];
if (cache.contains({superTy, type}) && (variance == Covariant || cache.contains({type, superTy}))) if (FFlag::LuauUnifierCacheErrors)
{ {
startIndex = i; if (cache.contains({type, superTy}))
break; {
startIndex = i;
break;
}
}
else
{
if (cache.contains({superTy, type}) && (variance == Covariant || cache.contains({type, superTy})))
{
startIndex = i;
break;
}
} }
} }
} }
@ -771,17 +821,17 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
} }
} }
void Unifier::cacheResult(TypeId subTy, TypeId superTy) bool Unifier::canCacheResult(TypeId subTy, TypeId superTy)
{ {
bool* superTyInfo = sharedState.skipCacheForType.find(superTy); bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
if (superTyInfo && *superTyInfo) if (superTyInfo && *superTyInfo)
return; return false;
bool* subTyInfo = sharedState.skipCacheForType.find(subTy); bool* subTyInfo = sharedState.skipCacheForType.find(subTy);
if (subTyInfo && *subTyInfo) if (subTyInfo && *subTyInfo)
return; return false;
auto skipCacheFor = [this](TypeId ty) { auto skipCacheFor = [this](TypeId ty) {
SkipCacheForType visitor{sharedState.skipCacheForType, types}; SkipCacheForType visitor{sharedState.skipCacheForType, types};
@ -793,9 +843,33 @@ void Unifier::cacheResult(TypeId subTy, TypeId superTy)
}; };
if (!superTyInfo && skipCacheFor(superTy)) if (!superTyInfo && skipCacheFor(superTy))
return; return false;
if (!subTyInfo && skipCacheFor(subTy)) if (!subTyInfo && skipCacheFor(subTy))
return false;
return true;
}
void Unifier::cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount)
{
if (errors.size() == prevErrorCount)
{
if (canCacheResult(subTy, superTy))
sharedState.cachedUnify.insert({subTy, superTy});
}
else if (errors.size() == prevErrorCount + 1)
{
if (canCacheResult(subTy, superTy))
sharedState.cachedUnifyError[{subTy, superTy}] = errors.back().data;
}
}
void Unifier::cacheResult_DEPRECATED(TypeId subTy, TypeId superTy)
{
LUAU_ASSERT(!FFlag::LuauUnifierCacheErrors);
if (!canCacheResult(subTy, superTy))
return; return;
sharedState.cachedUnify.insert({superTy, subTy}); sharedState.cachedUnify.insert({superTy, subTy});
@ -1283,24 +1357,6 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
subFunction = log.getMutable<FunctionTypeVar>(subTy); subFunction = log.getMutable<FunctionTypeVar>(subTy);
} }
if (!FFlag::LuauImmutableTypes)
{
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
{
PendingType* newSubTy = log.queue(subTy);
FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy);
LUAU_ASSERT(newSubFtv);
newSubFtv->definition = superFunction->definition;
}
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
{
PendingType* newSuperTy = log.queue(superTy);
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy);
LUAU_ASSERT(newSuperFtv);
newSuperFtv->definition = subFunction->definition;
}
}
ctx = context; ctx = context;
if (FFlag::LuauTxnLogSeesTypePacks2) if (FFlag::LuauTxnLogSeesTypePacks2)
@ -1563,8 +1619,25 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
variance = Invariant; variance = Invariant;
Unifier innerState = makeChildUnifier(); Unifier innerState = makeChildUnifier();
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy); if (FFlag::LuauExtendedIndexerError)
{
innerState.tryUnify_(subTable->indexer->indexType, superTable->indexer->indexType);
bool reported = !innerState.errors.empty();
checkChildUnifierTypeMismatch(innerState.errors, "[indexer key]", superTy, subTy);
innerState.tryUnify_(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
if (!reported)
checkChildUnifierTypeMismatch(innerState.errors, "[indexer value]", superTy, subTy);
}
else
{
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
}
if (innerState.errors.empty()) if (innerState.errors.empty())
log.concat(std::move(innerState.log)); log.concat(std::move(innerState.log));
@ -1771,6 +1844,7 @@ void Unifier::DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isInt
void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy) void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
{ {
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
TableTypeVar* freeTable = log.getMutable<TableTypeVar>(superTy); TableTypeVar* freeTable = log.getMutable<TableTypeVar>(superTy);
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy); TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
@ -1840,6 +1914,7 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection) void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection)
{ {
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy); TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy); TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
@ -2120,6 +2195,8 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
void Unifier::tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer) void Unifier::tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer)
{ {
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2 || !FFlag::LuauExtendedIndexerError);
tryUnify_(subIndexer.indexType, superIndexer.indexType); tryUnify_(subIndexer.indexType, superIndexer.indexType);
tryUnify_(subIndexer.indexResultType, superIndexer.indexResultType); tryUnify_(subIndexer.indexResultType, superIndexer.indexResultType);
} }
@ -2211,7 +2288,7 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
queue.pop_back(); queue.pop_back();
// Types from other modules don't have free types // Types from other modules don't have free types
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) if (ty->owningArena != typeArena)
continue; continue;
if (seen.find(ty)) if (seen.find(ty))

View File

@ -10,8 +10,6 @@
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
namespace Luau namespace Luau
{ {
@ -1233,8 +1231,7 @@ AstType* Parser::parseTableTypeAnnotation()
while (lexer.current().type != '}') while (lexer.current().type != '}')
{ {
if (FFlag::LuauParseSingletonTypes && lexer.current().type == '[' && if (lexer.current().type == '[' && (lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
{ {
const Lexeme begin = lexer.current(); const Lexeme begin = lexer.current();
nextLexeme(); // [ nextLexeme(); // [
@ -1500,17 +1497,17 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
nextLexeme(); nextLexeme();
return {allocator.alloc<AstTypeReference>(begin, std::nullopt, nameNil), {}}; return {allocator.alloc<AstTypeReference>(begin, std::nullopt, nameNil), {}};
} }
else if (FFlag::LuauParseSingletonTypes && lexer.current().type == Lexeme::ReservedTrue) else if (lexer.current().type == Lexeme::ReservedTrue)
{ {
nextLexeme(); nextLexeme();
return {allocator.alloc<AstTypeSingletonBool>(begin, true)}; return {allocator.alloc<AstTypeSingletonBool>(begin, true)};
} }
else if (FFlag::LuauParseSingletonTypes && lexer.current().type == Lexeme::ReservedFalse) else if (lexer.current().type == Lexeme::ReservedFalse)
{ {
nextLexeme(); nextLexeme();
return {allocator.alloc<AstTypeSingletonBool>(begin, false)}; return {allocator.alloc<AstTypeSingletonBool>(begin, false)};
} }
else if (FFlag::LuauParseSingletonTypes && (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)) else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
{ {
if (std::optional<AstArray<char>> value = parseCharArray()) if (std::optional<AstArray<char>> value = parseCharArray())
{ {
@ -1520,7 +1517,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
else else
return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")}; return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
} }
else if (FFlag::LuauParseSingletonTypes && lexer.current().type == Lexeme::BrokenString) else if (lexer.current().type == Lexeme::BrokenString)
{ {
Location location = lexer.current().location; Location location = lexer.current().location;
nextLexeme(); nextLexeme();
@ -2189,11 +2186,8 @@ AstExpr* Parser::parseTableConstructor()
AstExpr* key = allocator.alloc<AstExprConstantString>(name.location, nameString); AstExpr* key = allocator.alloc<AstExprConstantString>(name.location, nameString);
AstExpr* value = parseExpr(); AstExpr* value = parseExpr();
if (FFlag::LuauTableFieldFunctionDebugname) if (AstExprFunction* func = value->as<AstExprFunction>())
{ func->debugname = name.name;
if (AstExprFunction* func = value->as<AstExprFunction>())
func->debugname = name.name;
}
items.push_back({AstExprTable::Item::Record, key, value}); items.push_back({AstExprTable::Item::Record, key, value});
} }

View File

@ -14,8 +14,6 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAG(LuauGcAdditionalStats)
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n" const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
"$URL: www.lua.org $\n"; "$URL: www.lua.org $\n";
@ -1060,8 +1058,11 @@ int lua_gc(lua_State* L, int what, int data)
g->GCthreshold = 0; g->GCthreshold = 0;
bool waspaused = g->gcstate == GCSpause; bool waspaused = g->gcstate == GCSpause;
double startmarktime = g->gcstats.currcycle.marktime;
double startsweeptime = g->gcstats.currcycle.sweeptime; #ifdef LUAI_GCMETRICS
double startmarktime = g->gcmetrics.currcycle.marktime;
double startsweeptime = g->gcmetrics.currcycle.sweeptime;
#endif
// track how much work the loop will actually perform // track how much work the loop will actually perform
size_t actualwork = 0; size_t actualwork = 0;
@ -1079,31 +1080,30 @@ int lua_gc(lua_State* L, int what, int data)
} }
} }
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
// record explicit step statistics
GCCycleMetrics* cyclemetrics = g->gcstate == GCSpause ? &g->gcmetrics.lastcycle : &g->gcmetrics.currcycle;
double totalmarktime = cyclemetrics->marktime - startmarktime;
double totalsweeptime = cyclemetrics->sweeptime - startsweeptime;
if (totalmarktime > 0.0)
{ {
// record explicit step statistics cyclemetrics->markexplicitsteps++;
GCCycleStats* cyclestats = g->gcstate == GCSpause ? &g->gcstats.lastcycle : &g->gcstats.currcycle;
double totalmarktime = cyclestats->marktime - startmarktime; if (totalmarktime > cyclemetrics->markmaxexplicittime)
double totalsweeptime = cyclestats->sweeptime - startsweeptime; cyclemetrics->markmaxexplicittime = totalmarktime;
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 (totalsweeptime > 0.0)
{
cyclemetrics->sweepexplicitsteps++;
if (totalsweeptime > cyclemetrics->sweepmaxexplicittime)
cyclemetrics->sweepmaxexplicittime = totalsweeptime;
}
#endif
// if cycle hasn't finished, advance threshold forward for the amount of extra work performed // if cycle hasn't finished, advance threshold forward for the amount of extra work performed
if (g->gcstate != GCSpause) if (g->gcstate != GCSpause)
{ {

View File

@ -17,8 +17,6 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAG(LuauReduceStackReallocs)
/* /*
** {====================================================== ** {======================================================
** Error-recovery functions ** Error-recovery functions
@ -33,6 +31,15 @@ struct lua_jmpbuf
jmp_buf buf; jmp_buf buf;
}; };
/* use POSIX versions of setjmp/longjmp if possible: they don't save/restore signal mask and are therefore faster */
#if defined(__linux__) || defined(__APPLE__)
#define LUAU_SETJMP(buf) _setjmp(buf)
#define LUAU_LONGJMP(buf, code) _longjmp(buf, code)
#else
#define LUAU_SETJMP(buf) setjmp(buf)
#define LUAU_LONGJMP(buf, code) longjmp(buf, code)
#endif
int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud) int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud)
{ {
lua_jmpbuf jb; lua_jmpbuf jb;
@ -40,7 +47,7 @@ int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud)
jb.status = 0; jb.status = 0;
L->global->errorjmp = &jb; L->global->errorjmp = &jb;
if (setjmp(jb.buf) == 0) if (LUAU_SETJMP(jb.buf) == 0)
f(L, ud); f(L, ud);
L->global->errorjmp = jb.prev; L->global->errorjmp = jb.prev;
@ -52,7 +59,7 @@ l_noret luaD_throw(lua_State* L, int errcode)
if (lua_jmpbuf* jb = L->global->errorjmp) if (lua_jmpbuf* jb = L->global->errorjmp)
{ {
jb->status = errcode; jb->status = errcode;
longjmp(jb->buf, 1); LUAU_LONGJMP(jb->buf, 1);
} }
if (L->global->cb.panic) if (L->global->cb.panic)
@ -165,8 +172,8 @@ static void correctstack(lua_State* L, TValue* oldstack)
void luaD_reallocstack(lua_State* L, int newsize) void luaD_reallocstack(lua_State* L, int newsize)
{ {
TValue* oldstack = L->stack; TValue* oldstack = L->stack;
int realsize = newsize + (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK); int realsize = newsize + EXTRA_STACK;
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK)); LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
luaM_reallocarray(L, L->stack, L->stacksize, realsize, TValue, L->memcat); luaM_reallocarray(L, L->stack, L->stacksize, realsize, TValue, L->memcat);
TValue* newstack = L->stack; TValue* newstack = L->stack;
for (int i = L->stacksize; i < realsize; i++) for (int i = L->stacksize; i < realsize; i++)
@ -514,7 +521,7 @@ static void callerrfunc(lua_State* L, void* ud)
static void restore_stack_limit(lua_State* L) static void restore_stack_limit(lua_State* L)
{ {
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK)); LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
if (L->size_ci > LUAI_MAXCALLS) if (L->size_ci > LUAI_MAXCALLS)
{ /* there was an overflow? */ { /* there was an overflow? */
int inuse = cast_int(L->ci - L->base_ci); int inuse = cast_int(L->ci - L->base_ci);

View File

@ -11,7 +11,7 @@
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \
luaD_growstack(L, n); \ luaD_growstack(L, n); \
else \ else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK))); condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK));
#define incr_top(L) \ #define incr_top(L) \
{ \ { \

View File

@ -11,8 +11,6 @@
#include "lmem.h" #include "lmem.h"
#include "ludata.h" #include "ludata.h"
LUAU_FASTFLAGVARIABLE(LuauGcAdditionalStats, false)
#include <string.h> #include <string.h>
#define GC_SWEEPMAX 40 #define GC_SWEEPMAX 40
@ -48,7 +46,8 @@ LUAU_FASTFLAGVARIABLE(LuauGcAdditionalStats, false)
reallymarkobject(g, obj2gco(t)); \ reallymarkobject(g, obj2gco(t)); \
} }
static void recordGcStateTime(global_State* g, int startgcstate, double seconds, bool assist) #ifdef LUAI_GCMETRICS
static void recordGcStateStep(global_State* g, int startgcstate, double seconds, bool assist, size_t work)
{ {
switch (startgcstate) switch (startgcstate)
{ {
@ -56,58 +55,76 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
// record root mark time if we have switched to next state // record root mark time if we have switched to next state
if (g->gcstate == GCSpropagate) if (g->gcstate == GCSpropagate)
{ {
g->gcstats.currcycle.marktime += seconds; g->gcmetrics.currcycle.marktime += seconds;
if (FFlag::LuauGcAdditionalStats && assist) if (assist)
g->gcstats.currcycle.markassisttime += seconds; g->gcmetrics.currcycle.markassisttime += seconds;
} }
break; break;
case GCSpropagate: case GCSpropagate:
case GCSpropagateagain: case GCSpropagateagain:
g->gcstats.currcycle.marktime += seconds; g->gcmetrics.currcycle.marktime += seconds;
g->gcmetrics.currcycle.markrequests += g->gcstepsize;
if (FFlag::LuauGcAdditionalStats && assist) if (assist)
g->gcstats.currcycle.markassisttime += seconds; g->gcmetrics.currcycle.markassisttime += seconds;
break; break;
case GCSatomic: case GCSatomic:
g->gcstats.currcycle.atomictime += seconds; g->gcmetrics.currcycle.atomictime += seconds;
break; break;
case GCSsweep: case GCSsweep:
g->gcstats.currcycle.sweeptime += seconds; g->gcmetrics.currcycle.sweeptime += seconds;
g->gcmetrics.currcycle.sweeprequests += g->gcstepsize;
if (FFlag::LuauGcAdditionalStats && assist) if (assist)
g->gcstats.currcycle.sweepassisttime += seconds; g->gcmetrics.currcycle.sweepassisttime += seconds;
break; break;
default: default:
LUAU_ASSERT(!"Unexpected GC state"); LUAU_ASSERT(!"Unexpected GC state");
} }
if (assist) if (assist)
g->gcstats.stepassisttimeacc += seconds; {
g->gcmetrics.stepassisttimeacc += seconds;
g->gcmetrics.currcycle.assistwork += work;
g->gcmetrics.currcycle.assistrequests += g->gcstepsize;
}
else else
g->gcstats.stepexplicittimeacc += seconds; {
g->gcmetrics.stepexplicittimeacc += seconds;
g->gcmetrics.currcycle.explicitwork += work;
g->gcmetrics.currcycle.explicitrequests += g->gcstepsize;
}
} }
static void startGcCycleStats(global_State* g) static double recordGcDeltaTime(double& timer)
{ {
g->gcstats.currcycle.starttimestamp = lua_clock(); double now = lua_clock();
g->gcstats.currcycle.pausetime = g->gcstats.currcycle.starttimestamp - g->gcstats.lastcycle.endtimestamp; double delta = now - timer;
timer = now;
return delta;
} }
static void finishGcCycleStats(global_State* g) static void startGcCycleMetrics(global_State* g)
{ {
g->gcstats.currcycle.endtimestamp = lua_clock(); g->gcmetrics.currcycle.starttimestamp = lua_clock();
g->gcstats.currcycle.endtotalsizebytes = g->totalbytes; g->gcmetrics.currcycle.pausetime = g->gcmetrics.currcycle.starttimestamp - g->gcmetrics.lastcycle.endtimestamp;
g->gcstats.completedcycles++;
g->gcstats.lastcycle = g->gcstats.currcycle;
g->gcstats.currcycle = GCCycleStats();
g->gcstats.cyclestatsacc.marktime += g->gcstats.lastcycle.marktime;
g->gcstats.cyclestatsacc.atomictime += g->gcstats.lastcycle.atomictime;
g->gcstats.cyclestatsacc.sweeptime += g->gcstats.lastcycle.sweeptime;
} }
static void finishGcCycleMetrics(global_State* g)
{
g->gcmetrics.currcycle.endtimestamp = lua_clock();
g->gcmetrics.currcycle.endtotalsizebytes = g->totalbytes;
g->gcmetrics.completedcycles++;
g->gcmetrics.lastcycle = g->gcmetrics.currcycle;
g->gcmetrics.currcycle = GCCycleMetrics();
g->gcmetrics.currcycle.starttotalsizebytes = g->totalbytes;
g->gcmetrics.currcycle.heaptriggersizebytes = g->GCthreshold;
}
#endif
static void removeentry(LuaNode* n) static void removeentry(LuaNode* n)
{ {
LUAU_ASSERT(ttisnil(gval(n))); LUAU_ASSERT(ttisnil(gval(n)));
@ -598,20 +615,19 @@ static size_t atomic(lua_State* L)
LUAU_ASSERT(g->gcstate == GCSatomic); LUAU_ASSERT(g->gcstate == GCSatomic);
size_t work = 0; size_t work = 0;
#ifdef LUAI_GCMETRICS
double currts = lua_clock(); double currts = lua_clock();
double prevts = currts; #endif
/* remark occasional upvalues of (maybe) dead threads */ /* remark occasional upvalues of (maybe) dead threads */
work += remarkupvals(g); work += remarkupvals(g);
/* traverse objects caught by write barrier and by 'remarkupvals' */ /* traverse objects caught by write barrier and by 'remarkupvals' */
work += propagateall(g); work += propagateall(g);
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
{ g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts);
currts = lua_clock(); #endif
g->gcstats.currcycle.atomictimeupval += currts - prevts;
prevts = currts;
}
/* remark weak tables */ /* remark weak tables */
g->gray = g->weak; g->gray = g->weak;
@ -621,34 +637,26 @@ static size_t atomic(lua_State* L)
markmt(g); /* mark basic metatables (again) */ markmt(g); /* mark basic metatables (again) */
work += propagateall(g); work += propagateall(g);
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
{ g->gcmetrics.currcycle.atomictimeweak += recordGcDeltaTime(currts);
currts = lua_clock(); #endif
g->gcstats.currcycle.atomictimeweak += currts - prevts;
prevts = currts;
}
/* remark gray again */ /* remark gray again */
g->gray = g->grayagain; g->gray = g->grayagain;
g->grayagain = NULL; g->grayagain = NULL;
work += propagateall(g); work += propagateall(g);
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
{ g->gcmetrics.currcycle.atomictimegray += recordGcDeltaTime(currts);
currts = lua_clock(); #endif
g->gcstats.currcycle.atomictimegray += currts - prevts;
prevts = currts;
}
/* remove collected objects from weak tables */ /* remove collected objects from weak tables */
work += cleartable(L, g->weak); work += cleartable(L, g->weak);
g->weak = NULL; g->weak = NULL;
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
{ g->gcmetrics.currcycle.atomictimeclear += recordGcDeltaTime(currts);
currts = lua_clock(); #endif
g->gcstats.currcycle.atomictimeclear += currts - prevts;
}
/* flip current white */ /* flip current white */
g->currentwhite = cast_byte(otherwhite(g)); g->currentwhite = cast_byte(otherwhite(g));
@ -742,8 +750,9 @@ static size_t gcstep(lua_State* L, size_t limit)
if (!g->gray) if (!g->gray)
{ {
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
g->gcstats.currcycle.propagatework = g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork; g->gcmetrics.currcycle.propagatework = g->gcmetrics.currcycle.explicitwork + g->gcmetrics.currcycle.assistwork;
#endif
// perform one iteration over 'gray again' list // perform one iteration over 'gray again' list
g->gray = g->grayagain; g->gray = g->grayagain;
@ -762,9 +771,10 @@ static size_t gcstep(lua_State* L, size_t limit)
if (!g->gray) /* no more `gray' objects */ if (!g->gray) /* no more `gray' objects */
{ {
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
g->gcstats.currcycle.propagateagainwork = g->gcmetrics.currcycle.propagateagainwork =
g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork - g->gcstats.currcycle.propagatework; g->gcmetrics.currcycle.explicitwork + g->gcmetrics.currcycle.assistwork - g->gcmetrics.currcycle.propagatework;
#endif
g->gcstate = GCSatomic; g->gcstate = GCSatomic;
} }
@ -772,8 +782,13 @@ static size_t gcstep(lua_State* L, size_t limit)
} }
case GCSatomic: case GCSatomic:
{ {
g->gcstats.currcycle.atomicstarttimestamp = lua_clock(); #ifdef LUAI_GCMETRICS
g->gcstats.currcycle.atomicstarttotalsizebytes = g->totalbytes; g->gcmetrics.currcycle.atomicstarttimestamp = lua_clock();
g->gcmetrics.currcycle.atomicstarttotalsizebytes = g->totalbytes;
#endif
g->gcstats.atomicstarttimestamp = lua_clock();
g->gcstats.atomicstarttotalsizebytes = g->totalbytes;
cost = atomic(L); /* finish mark phase */ cost = atomic(L); /* finish mark phase */
@ -809,18 +824,20 @@ static size_t gcstep(lua_State* L, size_t limit)
return cost; return cost;
} }
static int64_t getheaptriggererroroffset(GCHeapTriggerStats* triggerstats, GCCycleStats* cyclestats) static int64_t getheaptriggererroroffset(global_State* g)
{ {
// adjust for error using Proportional-Integral controller // adjust for error using Proportional-Integral controller
// https://en.wikipedia.org/wiki/PID_controller // https://en.wikipedia.org/wiki/PID_controller
int32_t errorKb = int32_t((cyclestats->atomicstarttotalsizebytes - cyclestats->heapgoalsizebytes) / 1024); int32_t errorKb = int32_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.heapgoalsizebytes) / 1024);
// we use sliding window for the error integral to avoid error sum 'windup' when the desired target cannot be reached // we use sliding window for the error integral to avoid error sum 'windup' when the desired target cannot be reached
int32_t* slot = &triggerstats->terms[triggerstats->termpos % triggerstats->termcount]; const size_t triggertermcount = sizeof(g->gcstats.triggerterms) / sizeof(g->gcstats.triggerterms[0]);
int32_t* slot = &g->gcstats.triggerterms[g->gcstats.triggertermpos % triggertermcount];
int32_t prev = *slot; int32_t prev = *slot;
*slot = errorKb; *slot = errorKb;
triggerstats->integral += errorKb - prev; g->gcstats.triggerintegral += errorKb - prev;
triggerstats->termpos++; g->gcstats.triggertermpos++;
// controller tuning // controller tuning
// https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method // https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method
@ -832,7 +849,7 @@ static int64_t getheaptriggererroroffset(GCHeapTriggerStats* triggerstats, GCCyc
const double Ki = 0.54 * Ku / Ti; // integral gain const double Ki = 0.54 * Ku / Ti; // integral gain
double proportionalTerm = Kp * errorKb; double proportionalTerm = Kp * errorKb;
double integralTerm = Ki * triggerstats->integral; double integralTerm = Ki * g->gcstats.triggerintegral;
double totalTerm = proportionalTerm + integralTerm; double totalTerm = proportionalTerm + integralTerm;
@ -841,23 +858,20 @@ static int64_t getheaptriggererroroffset(GCHeapTriggerStats* triggerstats, GCCyc
static size_t getheaptrigger(global_State* g, size_t heapgoal) static size_t getheaptrigger(global_State* g, size_t heapgoal)
{ {
GCCycleStats* lastcycle = &g->gcstats.lastcycle;
GCCycleStats* currcycle = &g->gcstats.currcycle;
// adjust threshold based on a guess of how many bytes will be allocated between the cycle start and sweep phase // adjust threshold based on a guess of how many bytes will be allocated between the cycle start and sweep phase
// our goal is to begin the sweep when used memory has reached the heap goal // our goal is to begin the sweep when used memory has reached the heap goal
const double durationthreshold = 1e-3; const double durationthreshold = 1e-3;
double allocationduration = currcycle->atomicstarttimestamp - lastcycle->endtimestamp; double allocationduration = g->gcstats.atomicstarttimestamp - g->gcstats.endtimestamp;
// avoid measuring intervals smaller than 1ms // avoid measuring intervals smaller than 1ms
if (allocationduration < durationthreshold) if (allocationduration < durationthreshold)
return heapgoal; return heapgoal;
double allocationrate = (currcycle->atomicstarttotalsizebytes - lastcycle->endtotalsizebytes) / allocationduration; double allocationrate = (g->gcstats.atomicstarttotalsizebytes - g->gcstats.endtotalsizebytes) / allocationduration;
double markduration = currcycle->atomicstarttimestamp - currcycle->starttimestamp; double markduration = g->gcstats.atomicstarttimestamp - g->gcstats.starttimestamp;
int64_t expectedgrowth = int64_t(markduration * allocationrate); int64_t expectedgrowth = int64_t(markduration * allocationrate);
int64_t offset = getheaptriggererroroffset(&g->gcstats.triggerstats, currcycle); int64_t offset = getheaptriggererroroffset(g);
int64_t heaptrigger = heapgoal - (expectedgrowth + offset); int64_t heaptrigger = heapgoal - (expectedgrowth + offset);
// clamp the trigger between memory use at the end of the cycle and the heap goal // clamp the trigger between memory use at the end of the cycle and the heap goal
@ -868,11 +882,6 @@ void luaC_step(lua_State* L, bool assist)
{ {
global_State* g = L->global; global_State* g = L->global;
if (assist)
g->gcstats.currcycle.assistrequests += g->gcstepsize;
else
g->gcstats.currcycle.explicitrequests += g->gcstepsize;
int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */ int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
LUAU_ASSERT(g->totalbytes >= g->GCthreshold); LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
size_t debt = g->totalbytes - g->GCthreshold; size_t debt = g->totalbytes - g->GCthreshold;
@ -881,24 +890,23 @@ void luaC_step(lua_State* L, bool assist)
// at the start of the new cycle // at the start of the new cycle
if (g->gcstate == GCSpause) if (g->gcstate == GCSpause)
startGcCycleStats(g); g->gcstats.starttimestamp = lua_clock();
#ifdef LUAI_GCMETRICS
if (g->gcstate == GCSpause)
startGcCycleMetrics(g);
double lasttimestamp = lua_clock();
#endif
int lastgcstate = g->gcstate; int lastgcstate = g->gcstate;
double lasttimestamp = lua_clock();
size_t work = gcstep(L, lim); size_t work = gcstep(L, lim);
(void)work;
if (assist) #ifdef LUAI_GCMETRICS
g->gcstats.currcycle.assistwork += work; recordGcStateStep(g, lastgcstate, lua_clock() - lasttimestamp, assist, work);
else #endif
g->gcstats.currcycle.explicitwork += work;
recordGcStateTime(g, lastgcstate, lua_clock() - lasttimestamp, assist);
if (lastgcstate == GCSpropagate)
g->gcstats.currcycle.markrequests += g->gcstepsize;
else if (lastgcstate == GCSsweep)
g->gcstats.currcycle.sweeprequests += g->gcstepsize;
// at the end of the last cycle // at the end of the last cycle
if (g->gcstate == GCSpause) if (g->gcstate == GCSpause)
@ -909,13 +917,13 @@ void luaC_step(lua_State* L, bool assist)
g->GCthreshold = heaptrigger; g->GCthreshold = heaptrigger;
finishGcCycleStats(g); g->gcstats.heapgoalsizebytes = heapgoal;
g->gcstats.endtimestamp = lua_clock();
g->gcstats.endtotalsizebytes = g->totalbytes;
if (FFlag::LuauGcAdditionalStats) #ifdef LUAI_GCMETRICS
g->gcstats.currcycle.starttotalsizebytes = g->totalbytes; finishGcCycleMetrics(g);
#endif
g->gcstats.currcycle.heapgoalsizebytes = heapgoal;
g->gcstats.currcycle.heaptriggersizebytes = heaptrigger;
} }
else else
{ {
@ -933,8 +941,10 @@ void luaC_fullgc(lua_State* L)
{ {
global_State* g = L->global; global_State* g = L->global;
#ifdef LUAI_GCMETRICS
if (g->gcstate == GCSpause) if (g->gcstate == GCSpause)
startGcCycleStats(g); startGcCycleMetrics(g);
#endif
if (g->gcstate <= GCSatomic) if (g->gcstate <= GCSatomic)
{ {
@ -954,11 +964,12 @@ void luaC_fullgc(lua_State* L)
gcstep(L, SIZE_MAX); gcstep(L, SIZE_MAX);
} }
finishGcCycleStats(g); #ifdef LUAI_GCMETRICS
finishGcCycleMetrics(g);
startGcCycleMetrics(g);
#endif
/* run a full collection cycle */ /* run a full collection cycle */
startGcCycleStats(g);
markroot(L); markroot(L);
while (g->gcstate != GCSpause) while (g->gcstate != GCSpause)
{ {
@ -980,10 +991,11 @@ void luaC_fullgc(lua_State* L)
if (g->GCthreshold < g->totalbytes) if (g->GCthreshold < g->totalbytes)
g->GCthreshold = g->totalbytes; g->GCthreshold = g->totalbytes;
finishGcCycleStats(g); g->gcstats.heapgoalsizebytes = heapgoalsizebytes;
g->gcstats.currcycle.heapgoalsizebytes = heapgoalsizebytes; #ifdef LUAI_GCMETRICS
g->gcstats.currcycle.heaptriggersizebytes = g->GCthreshold; finishGcCycleMetrics(g);
#endif
} }
void luaC_barrierupval(lua_State* L, GCObject* v) void luaC_barrierupval(lua_State* L, GCObject* v)
@ -1075,21 +1087,21 @@ int64_t luaC_allocationrate(lua_State* L)
if (g->gcstate <= GCSatomic) if (g->gcstate <= GCSatomic)
{ {
double duration = lua_clock() - g->gcstats.lastcycle.endtimestamp; double duration = lua_clock() - g->gcstats.endtimestamp;
if (duration < durationthreshold) if (duration < durationthreshold)
return -1; return -1;
return int64_t((g->totalbytes - g->gcstats.lastcycle.endtotalsizebytes) / duration); return int64_t((g->totalbytes - g->gcstats.endtotalsizebytes) / duration);
} }
// totalbytes is unstable during the sweep, use the rate measured at the end of mark phase // totalbytes is unstable during the sweep, use the rate measured at the end of mark phase
double duration = g->gcstats.currcycle.atomicstarttimestamp - g->gcstats.lastcycle.endtimestamp; double duration = g->gcstats.atomicstarttimestamp - g->gcstats.endtimestamp;
if (duration < durationthreshold) if (duration < durationthreshold)
return -1; return -1;
return int64_t((g->gcstats.currcycle.atomicstarttotalsizebytes - g->gcstats.lastcycle.endtotalsizebytes) / duration); return int64_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.endtotalsizebytes) / duration);
} }
void luaC_wakethread(lua_State* L) void luaC_wakethread(lua_State* L)

View File

@ -82,7 +82,7 @@
#define luaC_checkGC(L) \ #define luaC_checkGC(L) \
{ \ { \
condhardstacktests(luaD_reallocstack(L, L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK))); \ condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
if (L->global->totalbytes >= L->global->GCthreshold) \ if (L->global->totalbytes >= L->global->GCthreshold) \
{ \ { \
condhardmemtests(luaC_validate(L), 1); \ condhardmemtests(luaC_validate(L), 1); \

View File

@ -10,8 +10,6 @@
#include "ldo.h" #include "ldo.h"
#include "ldebug.h" #include "ldebug.h"
LUAU_FASTFLAGVARIABLE(LuauReduceStackReallocs, false)
/* /*
** Main thread combines a thread state and the global state ** Main thread combines a thread state and the global state
*/ */
@ -35,7 +33,7 @@ static void stack_init(lua_State* L1, lua_State* L)
for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++)
setnilvalue(stack + i); /* erase new stack */ setnilvalue(stack + i); /* erase new stack */
L1->top = stack; L1->top = stack;
L1->stack_last = stack + (L1->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK)); L1->stack_last = stack + (L1->stacksize - EXTRA_STACK);
/* initialize first ci */ /* initialize first ci */
L1->ci->func = L1->top; L1->ci->func = L1->top;
setnilvalue(L1->top++); /* `function' entry for this `ci' */ setnilvalue(L1->top++); /* `function' entry for this `ci' */
@ -141,30 +139,16 @@ void lua_resetthread(lua_State* L)
ci->top = ci->base + LUA_MINSTACK; ci->top = ci->base + LUA_MINSTACK;
setnilvalue(ci->func); setnilvalue(ci->func);
L->ci = ci; L->ci = ci;
if (FFlag::LuauReduceStackReallocs) if (L->size_ci != BASIC_CI_SIZE)
{
if (L->size_ci != BASIC_CI_SIZE)
luaD_reallocCI(L, BASIC_CI_SIZE);
}
else
{
luaD_reallocCI(L, BASIC_CI_SIZE); luaD_reallocCI(L, BASIC_CI_SIZE);
}
/* clear thread state */ /* clear thread state */
L->status = LUA_OK; L->status = LUA_OK;
L->base = L->ci->base; L->base = L->ci->base;
L->top = L->ci->base; L->top = L->ci->base;
L->nCcalls = L->baseCcalls = 0; L->nCcalls = L->baseCcalls = 0;
/* clear thread stack */ /* clear thread stack */
if (FFlag::LuauReduceStackReallocs) if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK)
{
if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK)
luaD_reallocstack(L, BASIC_STACK_SIZE);
}
else
{
luaD_reallocstack(L, BASIC_STACK_SIZE); luaD_reallocstack(L, BASIC_STACK_SIZE);
}
for (int i = 0; i < L->stacksize; i++) for (int i = 0; i < L->stacksize; i++)
setnilvalue(L->stack + i); setnilvalue(L->stack + i);
} }
@ -234,6 +218,10 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
g->cb = lua_Callbacks(); g->cb = lua_Callbacks();
g->gcstats = GCStats(); g->gcstats = GCStats();
#ifdef LUAI_GCMETRICS
g->gcmetrics = GCMetrics();
#endif
if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0)
{ {
/* memory allocation error: free partial state */ /* memory allocation error: free partial state */

View File

@ -75,10 +75,26 @@ typedef struct CallInfo
#define f_isLua(ci) (!ci_func(ci)->isC) #define f_isLua(ci) (!ci_func(ci)->isC)
#define isLua(ci) (ttisfunction((ci)->func) && f_isLua(ci)) #define isLua(ci) (ttisfunction((ci)->func) && f_isLua(ci))
struct GCCycleStats struct GCStats
{
// data for proportional-integral controller of heap trigger value
int32_t triggerterms[32] = {0};
uint32_t triggertermpos = 0;
int32_t triggerintegral = 0;
size_t atomicstarttotalsizebytes = 0;
size_t endtotalsizebytes = 0;
size_t heapgoalsizebytes = 0;
double starttimestamp = 0;
double atomicstarttimestamp = 0;
double endtimestamp = 0;
};
#ifdef LUAI_GCMETRICS
struct GCCycleMetrics
{ {
size_t starttotalsizebytes = 0; size_t starttotalsizebytes = 0;
size_t heapgoalsizebytes = 0;
size_t heaptriggersizebytes = 0; size_t heaptriggersizebytes = 0;
double pausetime = 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
@ -120,16 +136,7 @@ struct GCCycleStats
size_t endtotalsizebytes = 0; size_t endtotalsizebytes = 0;
}; };
// data for proportional-integral controller of heap trigger value struct GCMetrics
struct GCHeapTriggerStats
{
static const unsigned termcount = 32;
int32_t terms[termcount] = {0};
uint32_t termpos = 0;
int32_t integral = 0;
};
struct GCStats
{ {
double stepexplicittimeacc = 0.0; double stepexplicittimeacc = 0.0;
double stepassisttimeacc = 0.0; double stepassisttimeacc = 0.0;
@ -137,14 +144,10 @@ struct GCStats
// when cycle is completed, last cycle values are updated // when cycle is completed, last cycle values are updated
uint64_t completedcycles = 0; uint64_t completedcycles = 0;
GCCycleStats lastcycle; GCCycleMetrics lastcycle;
GCCycleStats currcycle; GCCycleMetrics currcycle;
// only step count and their time is accumulated
GCCycleStats cyclestatsacc;
GCHeapTriggerStats triggerstats;
}; };
#endif
/* /*
** `global state', shared by all threads of this state ** `global state', shared by all threads of this state
@ -206,6 +209,9 @@ typedef struct global_State
GCStats gcstats; GCStats gcstats;
#ifdef LUAI_GCMETRICS
GCMetrics gcmetrics;
#endif
} global_State; } global_State;
// clang-format on // clang-format on

View File

@ -526,8 +526,8 @@ static TValue* newkey(lua_State* L, Table* t, const TValue* key)
LuaNode* othern; LuaNode* othern;
LuaNode* n = getfreepos(t); /* get a free place */ LuaNode* n = getfreepos(t); /* get a free place */
if (n == NULL) if (n == NULL)
{ /* cannot find a free place? */ { /* cannot find a free place? */
rehash(L, t, key); /* grow table */ rehash(L, t, key); /* grow table */
if (!FFlag::LuauTableRehashRework) if (!FFlag::LuauTableRehashRework)
{ {

View File

@ -2726,11 +2726,6 @@ end
TEST_CASE_FIXTURE(ACFixture, "autocomplete_on_string_singletons") TEST_CASE_FIXTURE(ACFixture, "autocomplete_on_string_singletons")
{ {
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
};
check(R"( check(R"(
--!strict --!strict
local foo: "hello" | "bye" = "hello" local foo: "hello" | "bye" = "hello"

View File

@ -496,8 +496,6 @@ TEST_CASE("DateTime")
TEST_CASE("Debug") TEST_CASE("Debug")
{ {
ScopedFastFlag luauTableFieldFunctionDebugname{"LuauTableFieldFunctionDebugname", true};
runConformance("debug.lua"); runConformance("debug.lua");
} }

View File

@ -132,21 +132,9 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
} }
CheckResult Fixture::check(Mode mode, std::string source) CheckResult Fixture::check(Mode mode, std::string source)
{
configResolver.defaultConfig.mode = mode;
fileResolver.source[mainModuleName] = std::move(source);
CheckResult result = frontend.check(fromString(mainModuleName));
configResolver.defaultConfig.mode = Mode::Strict;
return result;
}
CheckResult Fixture::check(const std::string& source)
{ {
ModuleName mm = fromString(mainModuleName); ModuleName mm = fromString(mainModuleName);
configResolver.defaultConfig.mode = Mode::Strict; configResolver.defaultConfig.mode = mode;
fileResolver.source[mm] = std::move(source); fileResolver.source[mm] = std::move(source);
frontend.markDirty(mm); frontend.markDirty(mm);
@ -155,6 +143,11 @@ CheckResult Fixture::check(const std::string& source)
return result; return result;
} }
CheckResult Fixture::check(const std::string& source)
{
return check(Mode::Strict, source);
}
LintResult Fixture::lint(const std::string& source, const std::optional<LintOptions>& lintOptions) LintResult Fixture::lint(const std::string& source, const std::optional<LintOptions>& lintOptions)
{ {
ParseOptions parseOptions; ParseOptions parseOptions;

View File

@ -597,6 +597,8 @@ return foo1
TEST_CASE_FIXTURE(Fixture, "UnknownType") TEST_CASE_FIXTURE(Fixture, "UnknownType")
{ {
ScopedFastFlag sff("LuauLintNoRobloxBits", true);
unfreeze(typeChecker.globalTypes); unfreeze(typeChecker.globalTypes);
TableTypeVar::Props instanceProps{ TableTypeVar::Props instanceProps{
{"ClassName", {typeChecker.anyType}}, {"ClassName", {typeChecker.anyType}},
@ -606,81 +608,26 @@ TEST_CASE_FIXTURE(Fixture, "UnknownType")
TypeId instanceType = typeChecker.globalTypes.addType(instanceTable); TypeId instanceType = typeChecker.globalTypes.addType(instanceTable);
TypeFun instanceTypeFun{{}, instanceType}; TypeFun instanceTypeFun{{}, instanceType};
ClassTypeVar::Props enumItemProps{
{"EnumType", {typeChecker.anyType}},
};
ClassTypeVar enumItemClass{"EnumItem", enumItemProps, std::nullopt, std::nullopt, {}, {}};
TypeId enumItemType = typeChecker.globalTypes.addType(enumItemClass);
TypeFun enumItemTypeFun{{}, enumItemType};
ClassTypeVar normalIdClass{"NormalId", {}, enumItemType, std::nullopt, {}, {}};
TypeId normalIdType = typeChecker.globalTypes.addType(normalIdClass);
TypeFun normalIdTypeFun{{}, normalIdType};
// Normally this would be defined externally, so hack it in for testing
addGlobalBinding(typeChecker, "game", typeChecker.anyType, "@test");
addGlobalBinding(typeChecker, "typeof", typeChecker.anyType, "@test");
typeChecker.globalScope->exportedTypeBindings["Part"] = instanceTypeFun; typeChecker.globalScope->exportedTypeBindings["Part"] = instanceTypeFun;
typeChecker.globalScope->exportedTypeBindings["Workspace"] = instanceTypeFun;
typeChecker.globalScope->exportedTypeBindings["RunService"] = instanceTypeFun;
typeChecker.globalScope->exportedTypeBindings["Instance"] = instanceTypeFun;
typeChecker.globalScope->exportedTypeBindings["ColorSequence"] = TypeFun{{}, typeChecker.anyType};
typeChecker.globalScope->exportedTypeBindings["EnumItem"] = enumItemTypeFun;
typeChecker.globalScope->importedTypeBindings["Enum"] = {{"NormalId", normalIdTypeFun}};
freeze(typeChecker.globalTypes);
LintResult result = lint(R"( LintResult result = lint(R"(
local _e01 = game:GetService("Foo") local game = ...
local _e02 = game:GetService("NormalId") local _e01 = type(game) == "Part"
local _e03 = game:FindService("table") local _e02 = typeof(game) == "Bar"
local _e04 = type(game) == "Part" local _e03 = typeof(game) == "vector"
local _e05 = type(game) == "NormalId"
local _e06 = typeof(game) == "Bar"
local _e07 = typeof(game) == "Part"
local _e08 = typeof(game) == "vector"
local _e09 = typeof(game) == "NormalId"
local _e10 = game:IsA("ColorSequence")
local _e11 = game:IsA("Enum.NormalId")
local _e12 = game:FindFirstChildWhichIsA("function")
local _o01 = game:GetService("Workspace") local _o01 = type(game) == "number"
local _o02 = game:FindService("RunService") local _o02 = type(game) == "vector"
local _o03 = type(game) == "number" local _o03 = typeof(game) == "Part"
local _o04 = type(game) == "vector"
local _o05 = typeof(game) == "string"
local _o06 = typeof(game) == "Instance"
local _o07 = typeof(game) == "EnumItem"
local _o08 = game:IsA("Part")
local _o09 = game:IsA("NormalId")
local _o10 = game:FindFirstChildWhichIsA("Part")
)"); )");
REQUIRE_EQ(result.warnings.size(), 12); REQUIRE_EQ(result.warnings.size(), 3);
CHECK_EQ(result.warnings[0].location.begin.line, 1); CHECK_EQ(result.warnings[0].location.begin.line, 2);
CHECK_EQ(result.warnings[0].text, "Unknown type 'Foo'"); CHECK_EQ(result.warnings[0].text, "Unknown type 'Part' (expected primitive type)");
CHECK_EQ(result.warnings[1].location.begin.line, 2); CHECK_EQ(result.warnings[1].location.begin.line, 3);
CHECK_EQ(result.warnings[1].text, "Unknown type 'NormalId' (expected class type)"); CHECK_EQ(result.warnings[1].text, "Unknown type 'Bar'");
CHECK_EQ(result.warnings[2].location.begin.line, 3); CHECK_EQ(result.warnings[2].location.begin.line, 4);
CHECK_EQ(result.warnings[2].text, "Unknown type 'table' (expected class type)"); CHECK_EQ(result.warnings[2].text, "Unknown type 'vector' (expected primitive or userdata type)");
CHECK_EQ(result.warnings[3].location.begin.line, 4);
CHECK_EQ(result.warnings[3].text, "Unknown type 'Part' (expected primitive type)");
CHECK_EQ(result.warnings[4].location.begin.line, 5);
CHECK_EQ(result.warnings[4].text, "Unknown type 'NormalId' (expected primitive type)");
CHECK_EQ(result.warnings[5].location.begin.line, 6);
CHECK_EQ(result.warnings[5].text, "Unknown type 'Bar'");
CHECK_EQ(result.warnings[6].location.begin.line, 7);
CHECK_EQ(result.warnings[6].text, "Unknown type 'Part' (expected primitive or userdata type)");
CHECK_EQ(result.warnings[7].location.begin.line, 8);
CHECK_EQ(result.warnings[7].text, "Unknown type 'vector' (expected primitive or userdata type)");
CHECK_EQ(result.warnings[8].location.begin.line, 9);
CHECK_EQ(result.warnings[8].text, "Unknown type 'NormalId' (expected primitive or userdata type)");
CHECK_EQ(result.warnings[9].location.begin.line, 10);
CHECK_EQ(result.warnings[9].text, "Unknown type 'ColorSequence' (expected class or enum type)");
CHECK_EQ(result.warnings[10].location.begin.line, 11);
CHECK_EQ(result.warnings[10].text, "Unknown type 'Enum.NormalId'");
CHECK_EQ(result.warnings[11].location.begin.line, 12);
CHECK_EQ(result.warnings[11].text, "Unknown type 'function' (expected class type)");
} }
TEST_CASE_FIXTURE(Fixture, "ForRangeTable") TEST_CASE_FIXTURE(Fixture, "ForRangeTable")

View File

@ -470,6 +470,7 @@ TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_id") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_id")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function id(x) return x end local function id(x) return x end
)"); )");
@ -482,6 +483,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_id")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function map(arr, fn) local function map(arr, fn)
local t = {} local t = {}
@ -500,6 +502,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(a: number, b: string) end local function f(a: number, b: string) end
local function test<T..., U...>(...: T...): U... local function test<T..., U...>(...: T...): U...
@ -516,6 +519,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
TEST_CASE("toStringNamedFunction_unit_f") TEST_CASE("toStringNamedFunction_unit_f")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
TypePackVar empty{TypePack{}}; TypePackVar empty{TypePack{}};
FunctionTypeVar ftv{&empty, &empty, {}, false}; FunctionTypeVar ftv{&empty, &empty, {}, false};
CHECK_EQ("f(): ()", toStringNamedFunction("f", ftv)); CHECK_EQ("f(): ()", toStringNamedFunction("f", ftv));
@ -523,6 +527,7 @@ TEST_CASE("toStringNamedFunction_unit_f")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f<a, b...>(x: a, ...): (a, a, b...) local function f<a, b...>(x: a, ...): (a, a, b...)
return x, x, ... return x, x, ...
@ -537,6 +542,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics2") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics2")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(): ...number local function f(): ...number
return 1, 2, 3 return 1, 2, 3
@ -551,6 +557,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics2")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics3") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics3")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(): (string, ...number) local function f(): (string, ...number)
return 'a', 1, 2, 3 return 'a', 1, 2, 3
@ -565,6 +572,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics3")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_type_annotation_has_partial_argnames") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_type_annotation_has_partial_argnames")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local f: (number, y: number) -> number local f: (number, y: number) -> number
)"); )");
@ -577,6 +585,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_type_annotation_has_partial_ar
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_type_params") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_type_params")
{ {
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f<T>(x: T, g: <U>(T) -> U)): () local function f<T>(x: T, g: <U>(T) -> U)): ()
end end
@ -590,4 +599,20 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_type_params")
CHECK_EQ("f(x: T, g: <U>(T) -> U): ()", toStringNamedFunction("f", *ftv, opts)); CHECK_EQ("f(x: T, g: <U>(T) -> U): ()", toStringNamedFunction("f", *ftv, opts));
} }
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_overrides_param_names")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function test(a, b : string, ... : number) return a end
)");
TypeId ty = requireType("test");
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(ty));
ToStringOptions opts;
opts.namedFunctionOverrideArgNames = {"first", "second", "third"};
CHECK_EQ("test<a>(first: a, second: string, ...: number): a", toStringNamedFunction("test", *ftv, opts));
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -651,8 +651,6 @@ local a: Packed<number>
TEST_CASE_FIXTURE(Fixture, "transpile_singleton_types") TEST_CASE_FIXTURE(Fixture, "transpile_singleton_types")
{ {
ScopedFastFlag luauParseSingletonTypes{"LuauParseSingletonTypes", true};
std::string code = R"( std::string code = R"(
type t1 = 'hello' type t1 = 'hello'
type t2 = true type t2 = true

View File

@ -887,8 +887,6 @@ TEST_CASE_FIXTURE(Fixture, "assert_removes_falsy_types")
TEST_CASE_FIXTURE(Fixture, "assert_removes_falsy_types2") TEST_CASE_FIXTURE(Fixture, "assert_removes_falsy_types2")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauAssertStripsFalsyTypes", true}, {"LuauAssertStripsFalsyTypes", true},
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true}, {"LuauWidenIfSupertypeIsFree2", true},

View File

@ -1335,4 +1335,80 @@ caused by:
toString(result.errors[0])); toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic")
{
ScopedFastFlag sff{"LuauArgCountMismatchSaysAtLeastWhenVariadic", true};
CheckResult result = check(R"(
function test(a: number, b: string, ...)
end
test(1)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto err = result.errors[0];
auto acm = get<CountMismatch>(err);
REQUIRE(acm);
CHECK_EQ(2, acm->expected);
CHECK_EQ(1, acm->actual);
CHECK_EQ(CountMismatch::Context::Arg, acm->context);
CHECK(acm->isVariadic);
}
TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic_generic")
{
ScopedFastFlag sff1{"LuauArgCountMismatchSaysAtLeastWhenVariadic", true};
ScopedFastFlag sff2{"LuauFixArgumentCountMismatchAmountWithGenericTypes", true};
CheckResult result = check(R"(
function test(a: number, b: string, ...)
return 1
end
function wrapper<A...>(f: (A...) -> number, ...: A...)
end
wrapper(test)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto err = result.errors[0];
auto acm = get<CountMismatch>(err);
REQUIRE(acm);
CHECK_EQ(3, acm->expected);
CHECK_EQ(1, acm->actual);
CHECK_EQ(CountMismatch::Context::Arg, acm->context);
CHECK(acm->isVariadic);
}
TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic_generic2")
{
ScopedFastFlag sff1{"LuauArgCountMismatchSaysAtLeastWhenVariadic", true};
ScopedFastFlag sff2{"LuauFixArgumentCountMismatchAmountWithGenericTypes", true};
CheckResult result = check(R"(
function test(a: number, b: string, ...)
return 1
end
function wrapper<A...>(f: (A...) -> number, ...: A...)
end
pcall(wrapper, test)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto err = result.errors[0];
auto acm = get<CountMismatch>(err);
REQUIRE(acm);
CHECK_EQ(4, acm->expected);
CHECK_EQ(2, acm->actual);
CHECK_EQ(CountMismatch::Context::Arg, acm->context);
CHECK(acm->isVariadic);
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -13,6 +13,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauTableSubtypingVariance2)
TEST_SUITE_BEGIN("TypeInferModules"); TEST_SUITE_BEGIN("TypeInferModules");
TEST_CASE_FIXTURE(Fixture, "require") TEST_CASE_FIXTURE(Fixture, "require")
@ -268,8 +270,6 @@ function x:Destroy(): () end
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_2") TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_2")
{ {
ScopedFastFlag immutableTypes{"LuauImmutableTypes", true};
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
export type Type = { x: { a: number } } export type Type = { x: { a: number } }
return {} return {}
@ -288,8 +288,6 @@ type Rename = typeof(x.x)
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_3") TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_3")
{ {
ScopedFastFlag immutableTypes{"LuauImmutableTypes", true};
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
local y = setmetatable({}, {}) local y = setmetatable({}, {})
export type Type = { x: typeof(y) } export type Type = { x: typeof(y) }
@ -307,4 +305,83 @@ type Rename = typeof(x.x)
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "module_type_conflict")
{
ScopedFastFlag luauTypeMismatchModuleName{"LuauTypeMismatchModuleName", true};
fileResolver.source["game/A"] = R"(
export type T = { x: number }
return {}
)";
fileResolver.source["game/B"] = R"(
export type T = { x: string }
return {}
)";
fileResolver.source["game/C"] = R"(
local A = require(game.A)
local B = require(game.B)
local a: A.T = { x = 2 }
local b: B.T = a
)";
CheckResult result = frontend.check("game/C");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauTableSubtypingVariance2)
{
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
caused by:
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
}
else
{
CHECK_EQ(toString(result.errors[0]), "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'");
}
}
TEST_CASE_FIXTURE(Fixture, "module_type_conflict_instantiated")
{
ScopedFastFlag luauTypeMismatchModuleName{"LuauTypeMismatchModuleName", true};
fileResolver.source["game/A"] = R"(
export type Wrap<T> = { x: T }
return {}
)";
fileResolver.source["game/B"] = R"(
local A = require(game.A)
export type T = A.Wrap<number>
return {}
)";
fileResolver.source["game/C"] = R"(
local A = require(game.A)
export type T = A.Wrap<string>
return {}
)";
fileResolver.source["game/D"] = R"(
local A = require(game.B)
local B = require(game.C)
local a: A.T = { x = 2 }
local b: B.T = a
)";
CheckResult result = frontend.check("game/D");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauTableSubtypingVariance2)
{
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
caused by:
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
}
else
{
CHECK_EQ(toString(result.errors[0]), "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'");
}
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -756,4 +756,30 @@ TEST_CASE_FIXTURE(Fixture, "refine_and_or")
CHECK_EQ("number", toString(requireType("u"))); CHECK_EQ("number", toString(requireType("u")));
} }
TEST_CASE_FIXTURE(Fixture, "infer_any_in_all_modes_when_lhs_is_unknown")
{
ScopedFastFlag sff{"LuauDecoupleOperatorInferenceFromUnifiedTypeInference", true};
CheckResult result = check(Mode::Strict, R"(
local function f(x, y)
return x + y
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Unknown type used in + operation; consider adding a type annotation to 'x'");
result = check(Mode::Nonstrict, R"(
local function f(x, y)
return x + y
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
// When type inference is unified, we could add an assertion that
// the strict and nonstrict types are equivalent. This isn't actually
// the case right now, though.
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -435,7 +435,6 @@ TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1002,8 +1001,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1028,8 +1025,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1066,8 +1061,6 @@ TEST_CASE_FIXTURE(Fixture, "and_or_peephole_refinement")
TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false") TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauAssertStripsFalsyTypes", true}, {"LuauAssertStripsFalsyTypes", true},
}; };
@ -1091,8 +1084,6 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false") TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauAssertStripsFalsyTypes", true}, {"LuauAssertStripsFalsyTypes", true},
}; };
@ -1134,8 +1125,6 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View File

@ -13,11 +13,6 @@ TEST_SUITE_BEGIN("TypeSingletons");
TEST_CASE_FIXTURE(Fixture, "bool_singletons") TEST_CASE_FIXTURE(Fixture, "bool_singletons")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: true = true local a: true = true
local b: false = false local b: false = false
@ -28,11 +23,6 @@ TEST_CASE_FIXTURE(Fixture, "bool_singletons")
TEST_CASE_FIXTURE(Fixture, "string_singletons") TEST_CASE_FIXTURE(Fixture, "string_singletons")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: "foo" = "foo" local a: "foo" = "foo"
local b: "bar" = "bar" local b: "bar" = "bar"
@ -43,11 +33,6 @@ TEST_CASE_FIXTURE(Fixture, "string_singletons")
TEST_CASE_FIXTURE(Fixture, "bool_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "bool_singletons_mismatch")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: true = false local a: true = false
)"); )");
@ -58,11 +43,6 @@ TEST_CASE_FIXTURE(Fixture, "bool_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "string_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "string_singletons_mismatch")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: "foo" = "bar" local a: "foo" = "bar"
)"); )");
@ -73,11 +53,6 @@ TEST_CASE_FIXTURE(Fixture, "string_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "string_singletons_escape_chars") TEST_CASE_FIXTURE(Fixture, "string_singletons_escape_chars")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: "\n" = "\000\r" local a: "\n" = "\000\r"
)"); )");
@ -88,11 +63,6 @@ TEST_CASE_FIXTURE(Fixture, "string_singletons_escape_chars")
TEST_CASE_FIXTURE(Fixture, "bool_singleton_subtype") TEST_CASE_FIXTURE(Fixture, "bool_singleton_subtype")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: true = true local a: true = true
local b: boolean = a local b: boolean = a
@ -103,11 +73,6 @@ TEST_CASE_FIXTURE(Fixture, "bool_singleton_subtype")
TEST_CASE_FIXTURE(Fixture, "string_singleton_subtype") TEST_CASE_FIXTURE(Fixture, "string_singleton_subtype")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: "foo" = "foo" local a: "foo" = "foo"
local b: string = a local b: string = a
@ -118,11 +83,6 @@ TEST_CASE_FIXTURE(Fixture, "string_singleton_subtype")
TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons") TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(a: true, b: "foo") end function f(a: true, b: "foo") end
f(true, "foo") f(true, "foo")
@ -133,11 +93,6 @@ TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons")
TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons_mismatch")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(a: true, b: "foo") end function f(a: true, b: "foo") end
f(true, "bar") f(true, "bar")
@ -149,11 +104,6 @@ TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons") TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(a, b) end function f(a, b) end
local g : ((true, string) -> ()) & ((false, number) -> ()) = (f::any) local g : ((true, string) -> ()) & ((false, number) -> ()) = (f::any)
@ -166,11 +116,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons")
TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(a, b) end function f(a, b) end
local g : ((true, string) -> ()) & ((false, number) -> ()) = (f::any) local g : ((true, string) -> ()) & ((false, number) -> ()) = (f::any)
@ -184,11 +129,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons") TEST_CASE_FIXTURE(Fixture, "enums_using_singletons")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type MyEnum = "foo" | "bar" | "baz" type MyEnum = "foo" | "bar" | "baz"
local a : MyEnum = "foo" local a : MyEnum = "foo"
@ -201,11 +141,6 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons")
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type MyEnum = "foo" | "bar" | "baz" type MyEnum = "foo" | "bar" | "baz"
local a : MyEnum = "bang" local a : MyEnum = "bang"
@ -218,11 +153,6 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping") TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type MyEnum1 = "foo" | "bar" type MyEnum1 = "foo" | "bar"
type MyEnum2 = MyEnum1 | "baz" type MyEnum2 = MyEnum1 | "baz"
@ -237,8 +167,6 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping")
TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons") TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true}, {"LuauExpectedTypesOfProperties", true},
}; };
@ -257,11 +185,6 @@ TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons")
TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons_mismatch")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type Dog = { tag: "Dog", howls: boolean } type Dog = { tag: "Dog", howls: boolean }
type Cat = { tag: "Cat", meows: boolean } type Cat = { tag: "Cat", meows: boolean }
@ -274,11 +197,6 @@ TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "tagged_unions_immutable_tag") TEST_CASE_FIXTURE(Fixture, "tagged_unions_immutable_tag")
{ {
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type Dog = { tag: "Dog", howls: boolean } type Dog = { tag: "Dog", howls: boolean }
type Cat = { tag: "Cat", meows: boolean } type Cat = { tag: "Cat", meows: boolean }
@ -292,10 +210,6 @@ TEST_CASE_FIXTURE(Fixture, "tagged_unions_immutable_tag")
TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings") TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings")
{ {
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
type T = { type T = {
@ -320,10 +234,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings")
} }
TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings_mismatch") TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings_mismatch")
{ {
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
type T = { type T = {
@ -341,10 +251,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings_mismatch")
TEST_CASE_FIXTURE(Fixture, "table_properties_alias_or_parens_is_indexer") TEST_CASE_FIXTURE(Fixture, "table_properties_alias_or_parens_is_indexer")
{ {
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
type S = "bar" type S = "bar"
@ -367,8 +273,7 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_alias_or_parens_is_indexer")
TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[]{
{"LuauParseSingletonTypes", true},
{"LuauUnsealedTableLiteral", true}, {"LuauUnsealedTableLiteral", true},
}; };
@ -386,8 +291,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true}, {"LuauExpectedTypesOfProperties", true},
}; };
@ -409,8 +312,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true}, {"LuauExpectedTypesOfProperties", true},
}; };
@ -432,8 +333,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options") TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true}, {"LuauExpectedTypesOfProperties", true},
}; };
@ -451,7 +350,6 @@ local a: Animal = if true then { tag = 'cat', catfood = 'something' } else { tag
TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton") TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauSingletonTypes", true},
{"LuauEqConstraint", true}, {"LuauEqConstraint", true},
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true}, {"LuauWidenIfSupertypeIsFree2", true},
@ -477,8 +375,6 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened") TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauEqConstraint", true}, {"LuauEqConstraint", true},
{"LuauWidenIfSupertypeIsFree2", true}, {"LuauWidenIfSupertypeIsFree2", true},
@ -504,8 +400,6 @@ TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere") TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauWidenIfSupertypeIsFree2", true}, {"LuauWidenIfSupertypeIsFree2", true},
}; };
@ -521,8 +415,6 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables") TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true}, {"LuauWidenIfSupertypeIsFree2", true},
}; };
@ -551,8 +443,6 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables
TEST_CASE_FIXTURE(Fixture, "table_insert_with_a_singleton_argument") TEST_CASE_FIXTURE(Fixture, "table_insert_with_a_singleton_argument")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauWidenIfSupertypeIsFree2", true}, {"LuauWidenIfSupertypeIsFree2", true},
}; };
@ -577,8 +467,6 @@ TEST_CASE_FIXTURE(Fixture, "table_insert_with_a_singleton_argument")
TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened") TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauWidenIfSupertypeIsFree2", true}, {"LuauWidenIfSupertypeIsFree2", true},
}; };
@ -595,7 +483,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -614,7 +501,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -633,7 +519,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -652,7 +537,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View File

@ -2078,6 +2078,44 @@ caused by:
Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '<a>(a) -> ()'; different number of generic type parameters)"); Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '<a>(a) -> ()'; different number of generic type parameters)");
} }
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
{
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
ScopedFastFlag luauExtendedIndexerError{"LuauExtendedIndexerError", true};
CheckResult result = check(R"(
type A = { [number]: string }
type B = { [string]: string }
local a: A = { 'a', 'b' }
local b: B = a
)");
LUAU_REQUIRE_ERRORS(result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
caused by:
Property '[indexer key]' is not compatible. Type 'number' could not be converted into 'string')");
}
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
{
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
ScopedFastFlag luauExtendedIndexerError{"LuauExtendedIndexerError", true};
CheckResult result = check(R"(
type A = { [number]: number }
type B = { [number]: string }
local a: A = { 1, 2, 3 }
local b: B = a
)");
LUAU_REQUIRE_ERRORS(result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
caused by:
Property '[indexer value]' is not compatible. Type 'number' could not be converted into 'string')");
}
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table") TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table")
{ {
ScopedFastFlag sffs[]{ ScopedFastFlag sffs[]{

View File

@ -296,6 +296,7 @@ return f()
REQUIRE(acm); REQUIRE(acm);
CHECK_EQ(1, acm->expected); CHECK_EQ(1, acm->expected);
CHECK_EQ(0, acm->actual); CHECK_EQ(0, acm->actual);
CHECK_FALSE(acm->isVariadic);
} }
TEST_CASE_FIXTURE(Fixture, "optional_field_access_error") TEST_CASE_FIXTURE(Fixture, "optional_field_access_error")