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 actual;
Context context = Arg;
bool isVariadic = false;
bool operator==(const CountMismatch& rhs) const;
};

View File

@ -32,6 +32,7 @@ struct ToStringOptions
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
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::vector<std::string> namedFunctionOverrideArgNames; // If present, named function argument names will be overridden
};
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 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
// 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, std::function<TypePackId(TypePackId)> mapper);
size_t size(TypePackId tp);
bool finite(TypePackId tp);
size_t size(const TypePack& tp);
size_t size(TypePackId tp, TxnLog* log = nullptr);
bool finite(TypePackId tp, TxnLog* log = nullptr);
size_t size(const TypePack& tp, TxnLog* log = nullptr);
std::optional<TypeId> first(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.
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.
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 = {});
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:
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);

View File

@ -2,6 +2,7 @@
#pragma once
#include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include "Luau/TypeVar.h"
#include "Luau/TypePack.h"
@ -42,6 +43,7 @@ struct UnifierSharedState
DenseHashSet<void*> seenAny{nullptr};
DenseHashMap<TypeId, bool> skipCacheForType{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<TypePackId> tempSeenTp{nullptr};

View File

@ -8,6 +8,7 @@
#include <stdexcept>
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)
{
@ -53,7 +54,32 @@ struct ErrorConverter
{
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)
{
@ -147,7 +173,7 @@ struct ErrorConverter
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
" are required here";
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");

View File

@ -14,6 +14,7 @@
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
LUAU_FASTFLAGVARIABLE(LuauLintNoRobloxBits, false)
namespace Luau
{
@ -1135,16 +1136,20 @@ private:
enum TypeKind
{
Kind_Invalid,
Kind_Unknown,
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_Userdata, // custom userdata type - Vector3/etc.
Kind_Vector, // 'vector' but only used when type is used
Kind_Userdata, // custom userdata type
// TODO: remove these with LuauLintNoRobloxBits
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.
};
bool containsPropName(TypeId ty, const std::string& propName)
{
LUAU_ASSERT(!FFlag::LuauLintNoRobloxBits);
if (auto ctv = get<ClassTypeVar>(ty))
return lookupClassProp(ctv, propName) != nullptr;
@ -1163,13 +1168,23 @@ private:
if (name == "vector")
return Kind_Vector;
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;
if (FFlag::LuauLintNoRobloxBits)
{
if (std::optional<TypeFun> maybeTy = context->scope->lookupType(name))
return Kind_Userdata;
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)
@ -1177,7 +1192,7 @@ private:
std::string name(expr->value.data, expr->value.size);
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());
return;
@ -1189,7 +1204,7 @@ private:
return;
// 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;
}
@ -1198,12 +1213,18 @@ private:
bool acceptsClassName(AstName method)
{
LUAU_ASSERT(!FFlag::LuauLintNoRobloxBits);
return method.value[0] == 'F' && (method == "FindFirstChildOfClass" || method == "FindFirstChildWhichIsA" ||
method == "FindFirstAncestorOfClass" || method == "FindFirstAncestorWhichIsA");
}
bool visit(AstExprCall* node) override
{
// TODO: Simply remove the override
if (FFlag::LuauLintNoRobloxBits)
return true;
if (AstExprIndexName* index = node->func->as<AstExprIndexName>())
{
AstExprConstantString* arg0 = node->args.size > 0 ? node->args.data[0]->as<AstExprConstantString>() : NULL;

View File

@ -12,10 +12,8 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAGVARIABLE(LuauCloneDeclaredGlobals, false)
LUAU_FASTFLAG(LuauImmutableTypes)
namespace Luau
{
@ -65,8 +63,7 @@ TypeId TypeArena::addTV(TypeVar&& tv)
{
TypeId allocated = typeVars.allocate(std::move(tv));
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated;
}
@ -75,8 +72,7 @@ TypeId TypeArena::freshType(TypeLevel level)
{
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated;
}
@ -85,8 +81,7 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated;
}
@ -95,8 +90,7 @@ TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated;
}
@ -105,8 +99,7 @@ TypePackId TypeArena::addTypePack(TypePack tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated;
}
@ -115,8 +108,7 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
asMutable(allocated)->owningArena = this;
return allocated;
}
@ -439,16 +431,9 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
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)
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
else
{
// 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;
}
}
return res;

View File

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

View File

@ -27,10 +27,8 @@ LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as fals
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
@ -38,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauStatFunctionSimplify2, false)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAG(LuauTypeMismatchModuleName)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
@ -47,6 +46,8 @@ LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false)
LUAU_FASTFLAGVARIABLE(LuauFixArgumentCountMismatchAmountWithGenericTypes, false)
LUAU_FASTFLAGVARIABLE(LuauFixIncorrectLineNumberDuplicateType, false)
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
LUAU_FASTFLAGVARIABLE(LuauDecoupleOperatorInferenceFromUnifiedTypeInference, false)
LUAU_FASTFLAGVARIABLE(LuauArgCountMismatchSaysAtLeastWhenVariadic, false)
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
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
unifierState.cachedUnify.clear();
unifierState.cachedUnifyError.clear();
unifierState.skipCacheForType.clear();
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
// 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(),
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
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
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)))
{
// 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;
}
@ -1512,14 +1514,14 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
result = {nilType};
else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>())
{
if (FFlag::LuauSingletonTypes && (forceSingleton || (expectedType && maybeSingleton(*expectedType))))
if (forceSingleton || (expectedType && maybeSingleton(*expectedType)))
result = {singletonType(bexpr->value)};
else
result = {booleanType};
}
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))};
else
result = {stringType};
@ -2490,12 +2492,24 @@ TypeId TypeChecker::checkBinaryOperation(
lhsType = follow(lhsType);
rhsType = follow(rhsType);
if (!isNonstrictMode() && get<FreeTypeVar>(lhsType))
if (FFlag::LuauDecoupleOperatorInferenceFromUnifiedTypeInference)
{
auto name = getIdentifierOfBaseVar(expr.left);
reportError(expr.location, CannotInferBinaryOperation{expr.op, name, CannotInferBinaryOperation::Operation});
if (!FFlag::LuauErrorRecoveryType)
return errorRecoveryType(scope);
if (!isNonstrictMode() && get<FreeTypeVar>(lhsType))
{
auto name = getIdentifierOfBaseVar(expr.left);
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.
@ -3452,7 +3466,8 @@ void TypeChecker::checkArgumentList(
{
if (FFlag::LuauFixArgumentCountMismatchAmountWithGenericTypes)
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;
}
++paramIter;
@ -4163,13 +4178,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
return errorRecoveryType(scope);
}
if (FFlag::LuauImmutableTypes)
return *moduleType;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState);
return *moduleType;
}
void TypeChecker::tablify(TypeId type)
@ -4941,10 +4950,19 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (const auto& indexer = table->indexer)
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
return addType(TableTypeVar{
props, tableIndexer, scope->level,
TableState::Sealed // FIXME: probably want a way to annotate other kinds of tables maybe
});
if (FFlag::LuauTypeMismatchModuleName)
{
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>())
{
@ -5206,6 +5224,9 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
{
ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams;
if (FFlag::LuauTypeMismatchModuleName)
ttv->definitionModuleName = currentModuleName;
}
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)))
return size(*pack);
tp = log ? log->follow(tp) : follow(tp);
if (auto pack = get<TypePack>(tp))
return size(*pack, log);
else
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))
return pack->tail ? finite(*pack->tail) : true;
return pack->tail ? finite(*pack->tail, log) : true;
if (get<VariadicTypePack>(tp))
return false;
@ -243,14 +244,14 @@ bool finite(TypePackId tp)
return true;
}
size_t size(const TypePack& tp)
size_t size(const TypePack& tp, TxnLog* log)
{
size_t result = tp.head.size();
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)
result += size(*tail);
result += size(*tail, log);
}
return result;
}

View File

@ -290,6 +290,24 @@ const std::string* getName(TypeId type)
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)
{
std::unordered_set<TypeId> superTypes;

View File

@ -14,10 +14,9 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
LUAU_FASTFLAGVARIABLE(LuauExtendedIndexerError, false);
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
@ -26,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogCheckForInvalidation, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogDontRetryForIndexers, false)
LUAU_FASTFLAGVARIABLE(LuauUnifierCacheErrors, false)
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
namespace Luau
@ -63,7 +63,7 @@ struct PromoteTypeLevels
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
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
if (ty->owningArena != typeArena)
return false;
return true;
@ -83,7 +83,7 @@ struct PromoteTypeLevels
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
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
if (ty->owningArena != typeArena)
return false;
promote(ty, log.getMutable<FunctionTypeVar>(ty));
@ -93,7 +93,7 @@ struct PromoteTypeLevels
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
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
if (ty->owningArena != typeArena)
return false;
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)
{
// 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;
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)
{
// 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;
PromoteTypeLevels ptl{log, typeArena, minLevel};
@ -170,7 +170,7 @@ struct SkipCacheForType
bool operator()(TypeId ty, const TableTypeVar&)
{
// 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;
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
@ -194,7 +194,7 @@ struct SkipCacheForType
bool operator()(TypeId ty, const T& t)
{
// 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;
const bool* prev = skipCacheForType.find(ty);
@ -212,7 +212,7 @@ struct SkipCacheForType
bool operator()(TypePackId tp, const T&)
{
// 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 true;
@ -445,12 +445,33 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
return tryUnifyWithAny(superTy, subTy);
bool cacheEnabled = !isFunctionCall && !isIntersection;
bool cacheEnabled;
auto& cache = sharedState.cachedUnify;
// What if the types are immutable and we proved their relation before
if (cacheEnabled && cache.contains({superTy, subTy}) && (variance == Covariant || cache.contains({subTy, superTy})))
return;
if (FFlag::LuauUnifierCacheErrors)
{
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.
// 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);
size_t errorCount = errors.size();
if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(subTy))
{
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))
tryUnifyPrimitives(subTy, superTy);
else if (FFlag::LuauSingletonTypes && (log.getMutable<PrimitiveTypeVar>(superTy) || log.getMutable<SingletonTypeVar>(superTy)) &&
log.getMutable<SingletonTypeVar>(subTy))
else if ((log.getMutable<PrimitiveTypeVar>(superTy) || log.getMutable<SingletonTypeVar>(superTy)) && log.getMutable<SingletonTypeVar>(subTy))
tryUnifySingletons(subTy, superTy);
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);
if (cacheEnabled && errors.empty())
cacheResult(subTy, superTy);
if (!FFlag::LuauUnifierCacheErrors)
{
if (cacheEnabled && errors.empty())
cacheResult_DEPRECATED(subTy, superTy);
}
}
// 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
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
if (FFlag::LuauUnifierCacheErrors && cacheEnabled)
cacheResult(subTy, superTy, errorCount);
log.popSeen(superTy, subTy);
}
@ -646,10 +674,21 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
{
TypeId type = uv->options[i];
if (cache.contains({type, subTy}) && (variance == Covariant || cache.contains({subTy, type})))
if (FFlag::LuauUnifierCacheErrors)
{
startIndex = i;
break;
if (cache.contains({subTy, type}))
{
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];
if (cache.contains({superTy, type}) && (variance == Covariant || cache.contains({type, superTy})))
if (FFlag::LuauUnifierCacheErrors)
{
startIndex = i;
break;
if (cache.contains({type, superTy}))
{
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);
if (superTyInfo && *superTyInfo)
return;
return false;
bool* subTyInfo = sharedState.skipCacheForType.find(subTy);
if (subTyInfo && *subTyInfo)
return;
return false;
auto skipCacheFor = [this](TypeId ty) {
SkipCacheForType visitor{sharedState.skipCacheForType, types};
@ -793,9 +843,33 @@ void Unifier::cacheResult(TypeId subTy, TypeId superTy)
};
if (!superTyInfo && skipCacheFor(superTy))
return;
return false;
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;
sharedState.cachedUnify.insert({superTy, subTy});
@ -1283,24 +1357,6 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
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;
if (FFlag::LuauTxnLogSeesTypePacks2)
@ -1563,8 +1619,25 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
variance = Invariant;
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())
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)
{
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
TableTypeVar* freeTable = log.getMutable<TableTypeVar>(superTy);
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)
{
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
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)
{
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2 || !FFlag::LuauExtendedIndexerError);
tryUnify_(subIndexer.indexType, superIndexer.indexType);
tryUnify_(subIndexer.indexResultType, superIndexer.indexResultType);
}
@ -2211,7 +2288,7 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
queue.pop_back();
// Types from other modules don't have free types
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
if (ty->owningArena != typeArena)
continue;
if (seen.find(ty))

View File

@ -10,8 +10,6 @@
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
namespace Luau
{
@ -1233,8 +1231,7 @@ AstType* Parser::parseTableTypeAnnotation()
while (lexer.current().type != '}')
{
if (FFlag::LuauParseSingletonTypes && lexer.current().type == '[' &&
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
if (lexer.current().type == '[' && (lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
{
const Lexeme begin = lexer.current();
nextLexeme(); // [
@ -1500,17 +1497,17 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
nextLexeme();
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();
return {allocator.alloc<AstTypeSingletonBool>(begin, true)};
}
else if (FFlag::LuauParseSingletonTypes && lexer.current().type == Lexeme::ReservedFalse)
else if (lexer.current().type == Lexeme::ReservedFalse)
{
nextLexeme();
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())
{
@ -1520,7 +1517,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
else
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;
nextLexeme();
@ -2189,11 +2186,8 @@ AstExpr* Parser::parseTableConstructor()
AstExpr* key = allocator.alloc<AstExprConstantString>(name.location, nameString);
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});
}

View File

@ -14,8 +14,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauGcAdditionalStats)
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
"$URL: www.lua.org $\n";
@ -1060,8 +1058,11 @@ int lua_gc(lua_State* L, int what, int data)
g->GCthreshold = 0;
bool waspaused = g->gcstate == GCSpause;
double startmarktime = g->gcstats.currcycle.marktime;
double startsweeptime = g->gcstats.currcycle.sweeptime;
#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
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
GCCycleStats* cyclestats = g->gcstate == GCSpause ? &g->gcstats.lastcycle : &g->gcstats.currcycle;
cyclemetrics->markexplicitsteps++;
double totalmarktime = cyclestats->marktime - startmarktime;
double totalsweeptime = cyclestats->sweeptime - startsweeptime;
if (totalmarktime > 0.0)
{
cyclestats->markexplicitsteps++;
if (totalmarktime > cyclestats->markmaxexplicittime)
cyclestats->markmaxexplicittime = totalmarktime;
}
if (totalsweeptime > 0.0)
{
cyclestats->sweepexplicitsteps++;
if (totalsweeptime > cyclestats->sweepmaxexplicittime)
cyclestats->sweepmaxexplicittime = totalsweeptime;
}
if (totalmarktime > cyclemetrics->markmaxexplicittime)
cyclemetrics->markmaxexplicittime = totalmarktime;
}
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 (g->gcstate != GCSpause)
{

View File

@ -17,8 +17,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauReduceStackReallocs)
/*
** {======================================================
** Error-recovery functions
@ -33,6 +31,15 @@ struct lua_jmpbuf
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)
{
lua_jmpbuf jb;
@ -40,7 +47,7 @@ int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud)
jb.status = 0;
L->global->errorjmp = &jb;
if (setjmp(jb.buf) == 0)
if (LUAU_SETJMP(jb.buf) == 0)
f(L, ud);
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)
{
jb->status = errcode;
longjmp(jb->buf, 1);
LUAU_LONGJMP(jb->buf, 1);
}
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)
{
TValue* oldstack = L->stack;
int realsize = newsize + (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK);
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK));
int realsize = newsize + EXTRA_STACK;
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
luaM_reallocarray(L, L->stack, L->stacksize, realsize, TValue, L->memcat);
TValue* newstack = L->stack;
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)
{
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)
{ /* there was an overflow? */
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)) \
luaD_growstack(L, n); \
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) \
{ \

View File

@ -11,8 +11,6 @@
#include "lmem.h"
#include "ludata.h"
LUAU_FASTFLAGVARIABLE(LuauGcAdditionalStats, false)
#include <string.h>
#define GC_SWEEPMAX 40
@ -48,7 +46,8 @@ LUAU_FASTFLAGVARIABLE(LuauGcAdditionalStats, false)
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)
{
@ -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
if (g->gcstate == GCSpropagate)
{
g->gcstats.currcycle.marktime += seconds;
g->gcmetrics.currcycle.marktime += seconds;
if (FFlag::LuauGcAdditionalStats && assist)
g->gcstats.currcycle.markassisttime += seconds;
if (assist)
g->gcmetrics.currcycle.markassisttime += seconds;
}
break;
case GCSpropagate:
case GCSpropagateagain:
g->gcstats.currcycle.marktime += seconds;
g->gcmetrics.currcycle.marktime += seconds;
g->gcmetrics.currcycle.markrequests += g->gcstepsize;
if (FFlag::LuauGcAdditionalStats && assist)
g->gcstats.currcycle.markassisttime += seconds;
if (assist)
g->gcmetrics.currcycle.markassisttime += seconds;
break;
case GCSatomic:
g->gcstats.currcycle.atomictime += seconds;
g->gcmetrics.currcycle.atomictime += seconds;
break;
case GCSsweep:
g->gcstats.currcycle.sweeptime += seconds;
g->gcmetrics.currcycle.sweeptime += seconds;
g->gcmetrics.currcycle.sweeprequests += g->gcstepsize;
if (FFlag::LuauGcAdditionalStats && assist)
g->gcstats.currcycle.sweepassisttime += seconds;
if (assist)
g->gcmetrics.currcycle.sweepassisttime += seconds;
break;
default:
LUAU_ASSERT(!"Unexpected GC state");
}
if (assist)
g->gcstats.stepassisttimeacc += seconds;
{
g->gcmetrics.stepassisttimeacc += seconds;
g->gcmetrics.currcycle.assistwork += work;
g->gcmetrics.currcycle.assistrequests += g->gcstepsize;
}
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();
g->gcstats.currcycle.pausetime = g->gcstats.currcycle.starttimestamp - g->gcstats.lastcycle.endtimestamp;
double now = lua_clock();
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->gcstats.currcycle.endtotalsizebytes = g->totalbytes;
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;
g->gcmetrics.currcycle.starttimestamp = lua_clock();
g->gcmetrics.currcycle.pausetime = g->gcmetrics.currcycle.starttimestamp - g->gcmetrics.lastcycle.endtimestamp;
}
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)
{
LUAU_ASSERT(ttisnil(gval(n)));
@ -598,20 +615,19 @@ static size_t atomic(lua_State* L)
LUAU_ASSERT(g->gcstate == GCSatomic);
size_t work = 0;
#ifdef LUAI_GCMETRICS
double currts = lua_clock();
double prevts = currts;
#endif
/* remark occasional upvalues of (maybe) dead threads */
work += remarkupvals(g);
/* traverse objects caught by write barrier and by 'remarkupvals' */
work += propagateall(g);
if (FFlag::LuauGcAdditionalStats)
{
currts = lua_clock();
g->gcstats.currcycle.atomictimeupval += currts - prevts;
prevts = currts;
}
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts);
#endif
/* remark weak tables */
g->gray = g->weak;
@ -621,34 +637,26 @@ static size_t atomic(lua_State* L)
markmt(g); /* mark basic metatables (again) */
work += propagateall(g);
if (FFlag::LuauGcAdditionalStats)
{
currts = lua_clock();
g->gcstats.currcycle.atomictimeweak += currts - prevts;
prevts = currts;
}
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimeweak += recordGcDeltaTime(currts);
#endif
/* remark gray again */
g->gray = g->grayagain;
g->grayagain = NULL;
work += propagateall(g);
if (FFlag::LuauGcAdditionalStats)
{
currts = lua_clock();
g->gcstats.currcycle.atomictimegray += currts - prevts;
prevts = currts;
}
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimegray += recordGcDeltaTime(currts);
#endif
/* remove collected objects from weak tables */
work += cleartable(L, g->weak);
g->weak = NULL;
if (FFlag::LuauGcAdditionalStats)
{
currts = lua_clock();
g->gcstats.currcycle.atomictimeclear += currts - prevts;
}
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimeclear += recordGcDeltaTime(currts);
#endif
/* flip current white */
g->currentwhite = cast_byte(otherwhite(g));
@ -742,8 +750,9 @@ static size_t gcstep(lua_State* L, size_t limit)
if (!g->gray)
{
if (FFlag::LuauGcAdditionalStats)
g->gcstats.currcycle.propagatework = g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork;
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.propagatework = g->gcmetrics.currcycle.explicitwork + g->gcmetrics.currcycle.assistwork;
#endif
// perform one iteration over 'gray again' list
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 (FFlag::LuauGcAdditionalStats)
g->gcstats.currcycle.propagateagainwork =
g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork - g->gcstats.currcycle.propagatework;
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.propagateagainwork =
g->gcmetrics.currcycle.explicitwork + g->gcmetrics.currcycle.assistwork - g->gcmetrics.currcycle.propagatework;
#endif
g->gcstate = GCSatomic;
}
@ -772,8 +782,13 @@ static size_t gcstep(lua_State* L, size_t limit)
}
case GCSatomic:
{
g->gcstats.currcycle.atomicstarttimestamp = lua_clock();
g->gcstats.currcycle.atomicstarttotalsizebytes = g->totalbytes;
#ifdef LUAI_GCMETRICS
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 */
@ -809,18 +824,20 @@ static size_t gcstep(lua_State* L, size_t limit)
return cost;
}
static int64_t getheaptriggererroroffset(GCHeapTriggerStats* triggerstats, GCCycleStats* cyclestats)
static int64_t getheaptriggererroroffset(global_State* g)
{
// adjust for error using Proportional-Integral 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
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;
*slot = errorKb;
triggerstats->integral += errorKb - prev;
triggerstats->termpos++;
g->gcstats.triggerintegral += errorKb - prev;
g->gcstats.triggertermpos++;
// controller tuning
// 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
double proportionalTerm = Kp * errorKb;
double integralTerm = Ki * triggerstats->integral;
double integralTerm = Ki * g->gcstats.triggerintegral;
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)
{
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
// our goal is to begin the sweep when used memory has reached the heap goal
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
if (allocationduration < durationthreshold)
return heapgoal;
double allocationrate = (currcycle->atomicstarttotalsizebytes - lastcycle->endtotalsizebytes) / allocationduration;
double markduration = currcycle->atomicstarttimestamp - currcycle->starttimestamp;
double allocationrate = (g->gcstats.atomicstarttotalsizebytes - g->gcstats.endtotalsizebytes) / allocationduration;
double markduration = g->gcstats.atomicstarttimestamp - g->gcstats.starttimestamp;
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);
// 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;
if (assist)
g->gcstats.currcycle.assistrequests += g->gcstepsize;
else
g->gcstats.currcycle.explicitrequests += g->gcstepsize;
int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
size_t debt = g->totalbytes - g->GCthreshold;
@ -881,24 +890,23 @@ void luaC_step(lua_State* L, bool assist)
// at the start of the new cycle
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;
double lasttimestamp = lua_clock();
size_t work = gcstep(L, lim);
(void)work;
if (assist)
g->gcstats.currcycle.assistwork += work;
else
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;
#ifdef LUAI_GCMETRICS
recordGcStateStep(g, lastgcstate, lua_clock() - lasttimestamp, assist, work);
#endif
// at the end of the last cycle
if (g->gcstate == GCSpause)
@ -909,13 +917,13 @@ void luaC_step(lua_State* L, bool assist)
g->GCthreshold = heaptrigger;
finishGcCycleStats(g);
g->gcstats.heapgoalsizebytes = heapgoal;
g->gcstats.endtimestamp = lua_clock();
g->gcstats.endtotalsizebytes = g->totalbytes;
if (FFlag::LuauGcAdditionalStats)
g->gcstats.currcycle.starttotalsizebytes = g->totalbytes;
g->gcstats.currcycle.heapgoalsizebytes = heapgoal;
g->gcstats.currcycle.heaptriggersizebytes = heaptrigger;
#ifdef LUAI_GCMETRICS
finishGcCycleMetrics(g);
#endif
}
else
{
@ -933,8 +941,10 @@ void luaC_fullgc(lua_State* L)
{
global_State* g = L->global;
#ifdef LUAI_GCMETRICS
if (g->gcstate == GCSpause)
startGcCycleStats(g);
startGcCycleMetrics(g);
#endif
if (g->gcstate <= GCSatomic)
{
@ -954,11 +964,12 @@ void luaC_fullgc(lua_State* L)
gcstep(L, SIZE_MAX);
}
finishGcCycleStats(g);
#ifdef LUAI_GCMETRICS
finishGcCycleMetrics(g);
startGcCycleMetrics(g);
#endif
/* run a full collection cycle */
startGcCycleStats(g);
markroot(L);
while (g->gcstate != GCSpause)
{
@ -980,10 +991,11 @@ void luaC_fullgc(lua_State* L)
if (g->GCthreshold < g->totalbytes)
g->GCthreshold = g->totalbytes;
finishGcCycleStats(g);
g->gcstats.heapgoalsizebytes = heapgoalsizebytes;
g->gcstats.currcycle.heapgoalsizebytes = heapgoalsizebytes;
g->gcstats.currcycle.heaptriggersizebytes = g->GCthreshold;
#ifdef LUAI_GCMETRICS
finishGcCycleMetrics(g);
#endif
}
void luaC_barrierupval(lua_State* L, GCObject* v)
@ -1075,21 +1087,21 @@ int64_t luaC_allocationrate(lua_State* L)
if (g->gcstate <= GCSatomic)
{
double duration = lua_clock() - g->gcstats.lastcycle.endtimestamp;
double duration = lua_clock() - g->gcstats.endtimestamp;
if (duration < durationthreshold)
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
double duration = g->gcstats.currcycle.atomicstarttimestamp - g->gcstats.lastcycle.endtimestamp;
double duration = g->gcstats.atomicstarttimestamp - g->gcstats.endtimestamp;
if (duration < durationthreshold)
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)

View File

@ -82,7 +82,7 @@
#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) \
{ \
condhardmemtests(luaC_validate(L), 1); \

View File

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

View File

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

View File

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

View File

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

View File

@ -496,8 +496,6 @@ TEST_CASE("DateTime")
TEST_CASE("Debug")
{
ScopedFastFlag luauTableFieldFunctionDebugname{"LuauTableFieldFunctionDebugname", true};
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)
{
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);
configResolver.defaultConfig.mode = Mode::Strict;
configResolver.defaultConfig.mode = mode;
fileResolver.source[mm] = std::move(source);
frontend.markDirty(mm);
@ -155,6 +143,11 @@ CheckResult Fixture::check(const std::string& source)
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)
{
ParseOptions parseOptions;

View File

@ -597,6 +597,8 @@ return foo1
TEST_CASE_FIXTURE(Fixture, "UnknownType")
{
ScopedFastFlag sff("LuauLintNoRobloxBits", true);
unfreeze(typeChecker.globalTypes);
TableTypeVar::Props instanceProps{
{"ClassName", {typeChecker.anyType}},
@ -606,81 +608,26 @@ TEST_CASE_FIXTURE(Fixture, "UnknownType")
TypeId instanceType = typeChecker.globalTypes.addType(instanceTable);
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["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"(
local _e01 = game:GetService("Foo")
local _e02 = game:GetService("NormalId")
local _e03 = game:FindService("table")
local _e04 = type(game) == "Part"
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 game = ...
local _e01 = type(game) == "Part"
local _e02 = typeof(game) == "Bar"
local _e03 = typeof(game) == "vector"
local _o01 = game:GetService("Workspace")
local _o02 = game:FindService("RunService")
local _o03 = type(game) == "number"
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")
local _o01 = type(game) == "number"
local _o02 = type(game) == "vector"
local _o03 = typeof(game) == "Part"
)");
REQUIRE_EQ(result.warnings.size(), 12);
CHECK_EQ(result.warnings[0].location.begin.line, 1);
CHECK_EQ(result.warnings[0].text, "Unknown type 'Foo'");
CHECK_EQ(result.warnings[1].location.begin.line, 2);
CHECK_EQ(result.warnings[1].text, "Unknown type 'NormalId' (expected class type)");
CHECK_EQ(result.warnings[2].location.begin.line, 3);
CHECK_EQ(result.warnings[2].text, "Unknown type 'table' (expected class 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)");
REQUIRE_EQ(result.warnings.size(), 3);
CHECK_EQ(result.warnings[0].location.begin.line, 2);
CHECK_EQ(result.warnings[0].text, "Unknown type 'Part' (expected primitive type)");
CHECK_EQ(result.warnings[1].location.begin.line, 3);
CHECK_EQ(result.warnings[1].text, "Unknown type 'Bar'");
CHECK_EQ(result.warnings[2].location.begin.line, 4);
CHECK_EQ(result.warnings[2].text, "Unknown type 'vector' (expected primitive or userdata type)");
}
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")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function id(x) return x end
)");
@ -482,6 +483,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_id")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function map(arr, fn)
local t = {}
@ -500,6 +502,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function f(a: number, b: string) end
local function test<T..., U...>(...: T...): U...
@ -516,6 +519,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
TEST_CASE("toStringNamedFunction_unit_f")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
TypePackVar empty{TypePack{}};
FunctionTypeVar ftv{&empty, &empty, {}, false};
CHECK_EQ("f(): ()", toStringNamedFunction("f", ftv));
@ -523,6 +527,7 @@ TEST_CASE("toStringNamedFunction_unit_f")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function f<a, b...>(x: a, ...): (a, a, b...)
return x, x, ...
@ -537,6 +542,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics2")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function f(): ...number
return 1, 2, 3
@ -551,6 +557,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics2")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics3")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function f(): (string, ...number)
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")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
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")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local function f<T>(x: T, g: <U>(T) -> U)): ()
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));
}
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();

View File

@ -651,8 +651,6 @@ local a: Packed<number>
TEST_CASE_FIXTURE(Fixture, "transpile_singleton_types")
{
ScopedFastFlag luauParseSingletonTypes{"LuauParseSingletonTypes", true};
std::string code = R"(
type t1 = 'hello'
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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauAssertStripsFalsyTypes", true},
{"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true},

View File

@ -1335,4 +1335,80 @@ caused by:
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();

View File

@ -13,6 +13,8 @@
using namespace Luau;
LUAU_FASTFLAG(LuauTableSubtypingVariance2)
TEST_SUITE_BEGIN("TypeInferModules");
TEST_CASE_FIXTURE(Fixture, "require")
@ -268,8 +270,6 @@ function x:Destroy(): () end
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_2")
{
ScopedFastFlag immutableTypes{"LuauImmutableTypes", true};
fileResolver.source["game/A"] = R"(
export type Type = { x: { a: number } }
return {}
@ -288,8 +288,6 @@ type Rename = typeof(x.x)
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_3")
{
ScopedFastFlag immutableTypes{"LuauImmutableTypes", true};
fileResolver.source["game/A"] = R"(
local y = setmetatable({}, {})
export type Type = { x: typeof(y) }
@ -307,4 +305,83 @@ type Rename = typeof(x.x)
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();

View File

@ -756,4 +756,30 @@ TEST_CASE_FIXTURE(Fixture, "refine_and_or")
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();

View File

@ -435,7 +435,6 @@ TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
};
CheckResult result = check(R"(
@ -1002,8 +1001,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
};
CheckResult result = check(R"(
@ -1028,8 +1025,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
};
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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", 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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", true},
{"LuauAssertStripsFalsyTypes", true},
};
@ -1134,8 +1125,6 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
};
CheckResult result = check(R"(

View File

@ -13,11 +13,6 @@ TEST_SUITE_BEGIN("TypeSingletons");
TEST_CASE_FIXTURE(Fixture, "bool_singletons")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
local a: true = true
local b: false = false
@ -28,11 +23,6 @@ TEST_CASE_FIXTURE(Fixture, "bool_singletons")
TEST_CASE_FIXTURE(Fixture, "string_singletons")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
local a: "foo" = "foo"
local b: "bar" = "bar"
@ -43,11 +33,6 @@ TEST_CASE_FIXTURE(Fixture, "string_singletons")
TEST_CASE_FIXTURE(Fixture, "bool_singletons_mismatch")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
local a: true = false
)");
@ -58,11 +43,6 @@ TEST_CASE_FIXTURE(Fixture, "bool_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "string_singletons_mismatch")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
local a: "foo" = "bar"
)");
@ -73,11 +53,6 @@ TEST_CASE_FIXTURE(Fixture, "string_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "string_singletons_escape_chars")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(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")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
local a: true = true
local b: boolean = a
@ -103,11 +73,6 @@ TEST_CASE_FIXTURE(Fixture, "bool_singleton_subtype")
TEST_CASE_FIXTURE(Fixture, "string_singleton_subtype")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
local a: "foo" = "foo"
local b: string = a
@ -118,11 +83,6 @@ TEST_CASE_FIXTURE(Fixture, "string_singleton_subtype")
TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
function f(a: true, b: "foo") end
f(true, "foo")
@ -133,11 +93,6 @@ TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons")
TEST_CASE_FIXTURE(Fixture, "function_call_with_singletons_mismatch")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
function f(a: true, b: "foo") end
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")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
function f(a, b) end
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")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
function f(a, b) end
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")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
type MyEnum = "foo" | "bar" | "baz"
local a : MyEnum = "foo"
@ -201,11 +141,6 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons")
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
type MyEnum = "foo" | "bar" | "baz"
local a : MyEnum = "bang"
@ -218,11 +153,6 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
type MyEnum1 = "foo" | "bar"
type MyEnum2 = MyEnum1 | "baz"
@ -237,8 +167,6 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping")
TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true},
};
@ -257,11 +185,6 @@ TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons")
TEST_CASE_FIXTURE(Fixture, "tagged_unions_using_singletons_mismatch")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
type Dog = { tag: "Dog", howls: 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")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
type Dog = { tag: "Dog", howls: 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")
{
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
--!strict
type T = {
@ -320,10 +234,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings")
}
TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings_mismatch")
{
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
--!strict
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")
{
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
};
CheckResult result = check(R"(
--!strict
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")
{
ScopedFastFlag sffs[] = {
{"LuauParseSingletonTypes", true},
ScopedFastFlag sffs[]{
{"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")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true},
};
@ -409,8 +312,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true},
};
@ -432,8 +333,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")
{
ScopedFastFlag sffs[] = {
{"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", 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")
{
ScopedFastFlag sff[]{
{"LuauSingletonTypes", true},
{"LuauEqConstraint", true},
{"LuauDiscriminableUnions2", 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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", true},
{"LuauEqConstraint", 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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", 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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauDiscriminableUnions2", 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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", 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")
{
ScopedFastFlag sff[]{
{"LuauParseSingletonTypes", true},
{"LuauSingletonTypes", true},
{"LuauWidenIfSupertypeIsFree2", true},
};
@ -595,7 +483,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
};
CheckResult result = check(R"(
@ -614,7 +501,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
};
CheckResult result = check(R"(
@ -633,7 +519,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
};
CheckResult result = check(R"(
@ -652,7 +537,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauSingletonTypes", true},
};
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)");
}
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")
{
ScopedFastFlag sffs[]{

View File

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