Sync to upstream/release/677 (#1872)

# What's Changed?

This week comes with many improvements to the new type solver and an important fix to the garbage collection to make it more robust in memory constrained scenarios.

# Runtime
- Garbage collection will no longer run out of memory itself, which could have happened when resizing arrays to a smaller size

# New Type Solver
- Type refinements on external types should now work and should no longer normalize the type into `never`
- Improved error reporting when `string.format` is used with a dynamic format string
- Updated type signature of `getmetatable` library function to use the corresponding type function and produce better type inference
- Restored a type mismatch error when converting function types with different number of generic parameters, like `() -> ()` into `<T>() -> ()`
- Types resulting from compound assignments have been simplified, reducing cyclic type introduction and inference failures
- Fixed function generic types leaking into tables during bidirectional type inference (Fixes #1808 and #1821 )
- Stability and performance improvements (Fixes #1860 )

# Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
vegorov-rbx 2025-06-06 11:52:47 -07:00 committed by GitHub
parent 7a27c38147
commit 68cdcc4a3a
Signed by: DevComp
GPG key ID: B5690EEEBB952194
61 changed files with 1801 additions and 4003 deletions

View file

@ -1,208 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/DenseHash.h"
#include "Luau/TypeFwd.h"
#include "Luau/UnifierSharedState.h"
#include <optional>
#include <string>
#include <unordered_set>
namespace Luau
{
struct DiffPathNode
{
// TODO: consider using Variants to simplify toString implementation
enum Kind
{
TableProperty,
FunctionArgument,
FunctionReturn,
Union,
Intersection,
Negation,
};
Kind kind;
// non-null when TableProperty
std::optional<Name> tableProperty;
// non-null when FunctionArgument (unless variadic arg), FunctionReturn (unless variadic arg), Union, or Intersection (i.e. anonymous fields)
std::optional<size_t> index;
/**
* Do not use for leaf nodes
*/
DiffPathNode(Kind kind)
: kind(kind)
{
}
DiffPathNode(Kind kind, std::optional<Name> tableProperty, std::optional<size_t> index)
: kind(kind)
, tableProperty(tableProperty)
, index(index)
{
}
std::string toString() const;
static DiffPathNode constructWithTableProperty(Name tableProperty);
static DiffPathNode constructWithKindAndIndex(Kind kind, size_t index);
static DiffPathNode constructWithKind(Kind kind);
};
struct DiffPathNodeLeaf
{
std::optional<TypeId> ty;
std::optional<Name> tableProperty;
std::optional<int> minLength;
bool isVariadic;
// TODO: Rename to anonymousIndex, for both union and Intersection
std::optional<size_t> unionIndex;
DiffPathNodeLeaf(
std::optional<TypeId> ty,
std::optional<Name> tableProperty,
std::optional<int> minLength,
bool isVariadic,
std::optional<size_t> unionIndex
)
: ty(ty)
, tableProperty(tableProperty)
, minLength(minLength)
, isVariadic(isVariadic)
, unionIndex(unionIndex)
{
}
static DiffPathNodeLeaf detailsNormal(TypeId ty);
static DiffPathNodeLeaf detailsTableProperty(TypeId ty, Name tableProperty);
static DiffPathNodeLeaf detailsUnionIndex(TypeId ty, size_t index);
static DiffPathNodeLeaf detailsLength(int minLength, bool isVariadic);
static DiffPathNodeLeaf nullopts();
};
struct DiffPath
{
std::vector<DiffPathNode> path;
std::string toString(bool prependDot) const;
};
struct DiffError
{
enum Kind
{
Normal,
MissingTableProperty,
MissingUnionMember,
MissingIntersectionMember,
IncompatibleGeneric,
LengthMismatchInFnArgs,
LengthMismatchInFnRets,
};
Kind kind;
DiffPath diffPath;
DiffPathNodeLeaf left;
DiffPathNodeLeaf right;
std::string leftRootName;
std::string rightRootName;
DiffError(Kind kind, DiffPathNodeLeaf left, DiffPathNodeLeaf right, std::string leftRootName, std::string rightRootName)
: kind(kind)
, left(left)
, right(right)
, leftRootName(leftRootName)
, rightRootName(rightRootName)
{
checkValidInitialization(left, right);
}
DiffError(Kind kind, DiffPath diffPath, DiffPathNodeLeaf left, DiffPathNodeLeaf right, std::string leftRootName, std::string rightRootName)
: kind(kind)
, diffPath(diffPath)
, left(left)
, right(right)
, leftRootName(leftRootName)
, rightRootName(rightRootName)
{
checkValidInitialization(left, right);
}
std::string toString(bool multiLine = false) const;
private:
std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const;
void checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right);
void checkNonMissingPropertyLeavesHaveNulloptTableProperty() const;
};
struct DifferResult
{
std::optional<DiffError> diffError;
DifferResult() {}
DifferResult(DiffError diffError)
: diffError(diffError)
{
}
void wrapDiffPath(DiffPathNode node);
};
struct DifferEnvironment
{
TypeId rootLeft;
TypeId rootRight;
std::optional<std::string> externalSymbolLeft;
std::optional<std::string> externalSymbolRight;
DenseHashMap<TypeId, TypeId> genericMatchedPairs;
DenseHashMap<TypePackId, TypePackId> genericTpMatchedPairs;
DifferEnvironment(
TypeId rootLeft,
TypeId rootRight,
std::optional<std::string> externalSymbolLeft,
std::optional<std::string> externalSymbolRight
)
: rootLeft(rootLeft)
, rootRight(rootRight)
, externalSymbolLeft(externalSymbolLeft)
, externalSymbolRight(externalSymbolRight)
, genericMatchedPairs(nullptr)
, genericTpMatchedPairs(nullptr)
{
}
bool isProvenEqual(TypeId left, TypeId right) const;
bool isAssumedEqual(TypeId left, TypeId right) const;
void recordProvenEqual(TypeId left, TypeId right);
void pushVisiting(TypeId left, TypeId right);
void popVisiting();
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingBegin() const;
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingEnd() const;
std::string getDevFixFriendlyNameLeft() const;
std::string getDevFixFriendlyNameRight() const;
private:
// TODO: consider using DenseHashSet
std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash> provenEqual;
// Ancestors of current types
std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash> visiting;
std::vector<std::pair<TypeId, TypeId>> visitingStack;
};
DifferResult diff(TypeId ty1, TypeId ty2);
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2);
/**
* True if ty is a "simple" type, i.e. cannot contain types.
* string, number, boolean are simple types.
* function and table are not simple types.
*/
bool isSimple(TypeId ty);
} // namespace Luau

View file

@ -470,6 +470,32 @@ struct UnexpectedArrayLikeTableItem
} }
}; };
struct CannotCheckDynamicStringFormatCalls
{
bool operator==(const CannotCheckDynamicStringFormatCalls&) const
{
return true;
}
};
// Error during subtyping when the number of generic types between compared types does not match
struct GenericTypeCountMismatch
{
size_t subTyGenericCount;
size_t superTyGenericCount;
bool operator==(const GenericTypeCountMismatch& rhs) const;
};
// Error during subtyping when the number of generic type packs between compared types does not match
struct GenericTypePackCountMismatch
{
size_t subTyGenericPackCount;
size_t superTyGenericPackCount;
bool operator==(const GenericTypePackCountMismatch& rhs) const;
};
using TypeErrorData = Variant< using TypeErrorData = Variant<
TypeMismatch, TypeMismatch,
UnknownSymbol, UnknownSymbol,
@ -521,7 +547,10 @@ using TypeErrorData = Variant<
ExplicitFunctionAnnotationRecommended, ExplicitFunctionAnnotationRecommended,
UserDefinedTypeFunctionError, UserDefinedTypeFunctionError,
ReservedIdentifier, ReservedIdentifier,
UnexpectedArrayLikeTableItem>; UnexpectedArrayLikeTableItem,
CannotCheckDynamicStringFormatCalls,
GenericTypeCountMismatch,
GenericTypePackCountMismatch>;
struct TypeErrorSummary struct TypeErrorSummary
{ {

View file

@ -20,7 +20,6 @@ struct SourceCode
None, None,
Module, Module,
Script, Script,
Local_DEPRECATED
}; };
std::string source; std::string source;

View file

@ -62,6 +62,12 @@ public:
bool operator==(const TypeIds& there) const; bool operator==(const TypeIds& there) const;
size_t getHash() const; size_t getHash() const;
bool isNever() const; bool isNever() const;
/**
* Moves the contents of this container into a `std::vector` and returns it.
* This container will be empty after `take` is called.
*/
std::vector<TypeId> take();
}; };
} // namespace Luau } // namespace Luau

View file

@ -318,4 +318,7 @@ std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, Type
*/ */
bool isRecord(const AstExprTable::Item& item); bool isRecord(const AstExprTable::Item& item);
// Unwraps any grouping expressions iteratively.
AstExpr* unwrapGroup(AstExpr* expr);
} // namespace Luau } // namespace Luau

View file

@ -21,6 +21,7 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include <algorithm> #include <algorithm>
#include <string_view>
/** FIXME: Many of these type definitions are not quite completely accurate. /** FIXME: Many of these type definitions are not quite completely accurate.
* *
@ -29,11 +30,12 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauStringFormatImprovements)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature)
LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature)
namespace Luau namespace Luau
{ {
@ -311,7 +313,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes; TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes; NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauEagerGeneralization2 Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauEagerGeneralization2
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
globalScope = globals.globalScope.get(); globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -378,12 +380,22 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT}); TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
// getmetatable : <MT>({ @metatable MT, {+ +} }) -> MT TypeId genericT = arena.addType(GenericType{globalScope, "T"});
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
if (FFlag::LuauSolverV2 && FFlag::LuauUpdateGetMetatableTypeSignature)
{
// getmetatable : <T>(T) -> getmetatable<T>
TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().getmetatableFunc, {genericT}});
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {getmtReturn}), "@luau");
}
else
{
// getmetatable : <MT>({ @metatable MT, {+ +} }) -> MT
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
}
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
if (FFlag::LuauUpdateSetMetatableTypeSignature) if (FFlag::LuauUpdateSetMetatableTypeSignature)
@ -633,110 +645,251 @@ bool MagicFormat::infer(const MagicFunctionCallContext& context)
{ {
TypeArena* arena = context.solver->arena; TypeArena* arena = context.solver->arena;
AstExprConstantString* fmt = nullptr; if (FFlag::LuauStringFormatImprovements)
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
{ {
if (auto group = index->expr->as<AstExprGroup>()) auto iter = begin(context.arguments);
fmt = group->expr->as<AstExprConstantString>();
else // we'll suppress any errors for `string.format` if the format string is error suppressing.
fmt = index->expr->as<AstExprConstantString>(); if (iter == end(context.arguments) || shouldSuppressErrors(context.solver->normalizer, follow(*iter)) == ErrorSuppression::Suppress)
{
TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType});
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
return true;
}
AstExprConstantString* fmt = nullptr;
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
fmt = unwrapGroup(index->expr)->as<AstExprConstantString>();
if (!context.callSite->self && context.callSite->args.size > 0)
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
std::optional<std::string_view> formatString;
if (fmt)
formatString = {fmt->value.data, fmt->value.size};
else if (auto singleton = get<SingletonType>(follow(*iter)))
{
if (auto stringSingleton = get<StringSingleton>(singleton))
formatString = {stringSingleton->value};
}
if (!formatString)
return false;
std::vector<TypeId> expected = parseFormatString(context.solver->builtinTypes, formatString->data(), formatString->size());
const auto& [params, tail] = flatten(context.arguments);
size_t paramOffset = 1;
// unify the prefix one argument at a time - needed if any of the involved types are free
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{
context.solver->unify(context.constraint, params[i + paramOffset], expected[i]);
}
// if we know the argument count or if we have too many arguments for sure, we can issue an error
size_t numActualParams = params.size();
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
// This is invoked at solve time, so we just need to provide a type for the result of :/.format
TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType});
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
return true;
} }
else
if (!context.callSite->self && context.callSite->args.size > 0)
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
if (!fmt)
return false;
std::vector<TypeId> expected = parseFormatString(context.solver->builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments);
size_t paramOffset = 1;
// unify the prefix one argument at a time - needed if any of the involved types are free
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{ {
context.solver->unify(context.constraint, params[i + paramOffset], expected[i]); AstExprConstantString* fmt = nullptr;
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
{
if (auto group = index->expr->as<AstExprGroup>())
fmt = group->expr->as<AstExprConstantString>();
else
fmt = index->expr->as<AstExprConstantString>();
}
if (!context.callSite->self && context.callSite->args.size > 0)
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
if (!fmt)
return false;
std::vector<TypeId> expected = parseFormatString(context.solver->builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments);
size_t paramOffset = 1;
// unify the prefix one argument at a time - needed if any of the involved types are free
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{
context.solver->unify(context.constraint, params[i + paramOffset], expected[i]);
}
// if we know the argument count or if we have too many arguments for sure, we can issue an error
size_t numActualParams = params.size();
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
// This is invoked at solve time, so we just need to provide a type for the result of :/.format
TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType});
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
return true;
} }
// if we know the argument count or if we have too many arguments for sure, we can issue an error
size_t numActualParams = params.size();
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
// This is invoked at solve time, so we just need to provide a type for the result of :/.format
TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType});
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
return true;
} }
bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context) bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
AstExprConstantString* fmt = nullptr; if (FFlag::LuauStringFormatImprovements)
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
{ {
if (auto group = index->expr->as<AstExprGroup>()) auto iter = begin(context.arguments);
fmt = group->expr->as<AstExprConstantString>();
else
fmt = index->expr->as<AstExprConstantString>();
}
if (!context.callSite->self && context.callSite->args.size > 0) if (iter == end(context.arguments))
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
if (!fmt)
{
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
return true;
}
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments);
size_t paramOffset = 1;
// Compare the expressions passed with the types the function expects to determine whether this function was called with : or .
bool calledWithSelf = expected.size() == context.callSite->args.size;
// unify the prefix one argument at a time
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{
TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i];
Location location =
FFlag::LuauFormatUseLastPosition
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
// use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
if (!result.isSubtype)
{ {
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
{ return true;
case ErrorSuppression::Suppress: }
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed) // we'll suppress any errors for `string.format` if the format string is error suppressing.
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); if (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, follow(*iter)) == ErrorSuppression::Suppress)
{
return true;
}
AstExprConstantString* fmt = nullptr;
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
fmt = unwrapGroup(index->expr)->as<AstExprConstantString>();
if (!context.callSite->self && context.callSite->args.size > 0)
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
std::optional<std::string_view> formatString;
if (fmt)
formatString = {fmt->value.data, fmt->value.size};
else if (auto singleton = get<SingletonType>(follow(*iter)))
{
if (auto stringSingleton = get<StringSingleton>(singleton))
formatString = {stringSingleton->value};
}
if (!formatString)
{
context.typechecker->reportError(CannotCheckDynamicStringFormatCalls{}, context.callSite->location);
return true;
}
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, formatString->data(), formatString->size());
const auto& [params, tail] = flatten(context.arguments);
size_t paramOffset = 1;
// Compare the expressions passed with the types the function expects to determine whether this function was called with : or .
bool calledWithSelf = expected.size() == context.callSite->args.size;
// unify the prefix one argument at a time
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{
TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i];
Location location = context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location;
// use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
if (!result.isSubtype)
{
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
} }
} }
}
return true; return true;
}
else
{
AstExprConstantString* fmt = nullptr;
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
{
if (auto group = index->expr->as<AstExprGroup>())
fmt = group->expr->as<AstExprConstantString>();
else
fmt = index->expr->as<AstExprConstantString>();
}
if (!context.callSite->self && context.callSite->args.size > 0)
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
if (!fmt)
{
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
return true;
}
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments);
size_t paramOffset = 1;
// Compare the expressions passed with the types the function expects to determine whether this function was called with : or .
bool calledWithSelf = expected.size() == context.callSite->args.size;
// unify the prefix one argument at a time
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{
TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i];
Location location = context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location;
// use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
if (!result.isSubtype)
{
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
}
return true;
}
} }
static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size) static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)

View file

@ -3,7 +3,7 @@
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
namespace Luau namespace Luau
{ {
@ -51,7 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
bool visit(TypeId, const TypeFunctionInstanceType&) override bool visit(TypeId, const TypeFunctionInstanceType&) override
{ {
return FFlag::LuauEagerGeneralization2 && traverseIntoTypeFunctions; return FFlag::LuauEagerGeneralization3 && traverseIntoTypeFunctions;
} }
}; };
@ -104,7 +104,7 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{ {
rci.traverse(fchc->argsPack); rci.traverse(fchc->argsPack);
} }
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization2) else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization3)
{ {
rci.traverseIntoTypeFunctions = false; rci.traverseIntoTypeFunctions = false;
rci.traverse(fcc->fn); rci.traverse(fcc->fn);
@ -118,12 +118,12 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
else if (auto hpc = get<HasPropConstraint>(*this)) else if (auto hpc = get<HasPropConstraint>(*this))
{ {
rci.traverse(hpc->resultType); rci.traverse(hpc->resultType);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
rci.traverse(hpc->subjectType); rci.traverse(hpc->subjectType);
} }
else if (auto hic = get<HasIndexerConstraint>(*this)) else if (auto hic = get<HasIndexerConstraint>(*this))
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
rci.traverse(hic->subjectType); rci.traverse(hic->subjectType);
rci.traverse(hic->resultType); rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `indexType`. // `HasIndexerConstraint` should not mutate `indexType`.

View file

@ -34,22 +34,21 @@
LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation) LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine) LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases)
LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment)
namespace Luau namespace Luau
{ {
@ -257,7 +256,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
rootScope->location = block->location; rootScope->location = block->location;
module->astScopes[block] = NotNull{scope.get()}; module->astScopes[block] = NotNull{scope.get()};
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); DEPRECATED_interiorTypes.emplace_back();
@ -293,7 +292,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
} }
); );
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -312,7 +311,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
} }
); );
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -338,7 +337,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
asMutable(ty)->ty.emplace<BoundType>(domainTy); asMutable(ty)->ty.emplace<BoundType>(domainTy);
} }
if (FFlag::LuauSimplifyOutOfLine) if (FFlag::LuauSimplifyOutOfLine2)
{ {
for (TypeId ty : unionsToSimplify) for (TypeId ty : unionsToSimplify)
addConstraint(scope, block->location, SimplifyConstraint{ty}); addConstraint(scope, block->location, SimplifyConstraint{ty});
@ -350,13 +349,13 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
// We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes // We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes
prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block); prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block);
// Pre // Pre
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); DEPRECATED_interiorTypes.emplace_back();
visitBlockWithoutChildScope(resumeScope, block); visitBlockWithoutChildScope(resumeScope, block);
// Post // Post
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -386,12 +385,12 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft); interiorFreeTypes.back().types.push_back(ft);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
freeTypes.insert(ft); freeTypes.insert(ft);
return ft; return ft;
@ -408,7 +407,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po
{ {
FreeTypePack f{scope.get(), polarity}; FreeTypePack f{scope.get(), polarity};
TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); TypePackId result = arena->addTypePack(TypePackVar{std::move(f)});
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.back().typePacks.push_back(result); interiorFreeTypes.back().typePacks.push_back(result);
return result; return result;
} }
@ -719,8 +718,6 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
if (std::optional<TypeId> defTy = lookup(scope, location, def)) if (std::optional<TypeId> defTy = lookup(scope, location, def))
{ {
TypeId ty = *defTy; TypeId ty = *defTy;
if (!FFlag::LuauWeakNilRefinementType && partition.shouldAppendNilType)
ty = arena->addType(UnionType{{ty, builtinTypes->nilType}});
// Intersect ty with every discriminant type. If either type is not // Intersect ty with every discriminant type. If either type is not
// sufficiently solved, we queue the intersection up via an // sufficiently solved, we queue the intersection up via an
// IntersectConstraint. // IntersectConstraint.
@ -767,7 +764,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
if (kind != RefinementsOpKind::None) if (kind != RefinementsOpKind::None)
ty = flushConstraints(kind, ty, discriminants); ty = flushConstraints(kind, ty, discriminants);
if (FFlag::LuauWeakNilRefinementType && partition.shouldAppendNilType) if (partition.shouldAppendNilType)
ty = createTypeFunctionInstance(builtinTypeFunctions().weakoptionalFunc, {ty}, {}, scope, location); ty = createTypeFunctionInstance(builtinTypeFunctions().weakoptionalFunc, {ty}, {}, scope, location);
scope->rvalueRefinements[def] = ty; scope->rvalueRefinements[def] = ty;
@ -1420,7 +1417,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location}; sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location};
bool sigFullyDefined = FFlag::LuauEagerGeneralization2 ? false : !hasFreeType(sig.signature); bool sigFullyDefined = FFlag::LuauEagerGeneralization3 ? false : !hasFreeType(sig.signature);
if (sigFullyDefined) if (sigFullyDefined)
emplaceType<BoundType>(asMutable(functionType), sig.signature); emplaceType<BoundType>(asMutable(functionType), sig.signature);
@ -1480,7 +1477,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
Checkpoint start = checkpoint(this); Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
bool sigFullyDefined = FFlag::LuauEagerGeneralization2 ? false : !hasFreeType(sig.signature); bool sigFullyDefined = FFlag::LuauEagerGeneralization3 ? false : !hasFreeType(sig.signature);
DefId def = dfg->getDef(function->name); DefId def = dfg->getDef(function->name);
@ -1669,11 +1666,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
TypeId resultTy = checkAstExprBinary(scope, assign->location, assign->op, assign->var, assign->value, std::nullopt).ty; TypeId resultTy = checkAstExprBinary(scope, assign->location, assign->op, assign->var, assign->value, std::nullopt).ty;
module->astCompoundAssignResultTypes[assign] = resultTy; module->astCompoundAssignResultTypes[assign] = resultTy;
TypeId lhsType = check(scope, assign->var).ty; if (!FFlag::LuauSkipLvalueForCompoundAssignment)
visitLValue(scope, assign->var, lhsType); {
TypeId lhsType = check(scope, assign->var).ty;
visitLValue(scope, assign->var, lhsType);
follow(lhsType); follow(lhsType);
follow(resultTy); follow(resultTy);
}
return ControlFlow::None; return ControlFlow::None;
} }
@ -1794,7 +1794,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
// Place this function as a child of the non-type function scope // Place this function as a child of the non-type function scope
scope->children.push_back(NotNull{sig.signatureScope.get()}); scope->children.push_back(NotNull{sig.signatureScope.get()});
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -1812,7 +1812,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
} }
); );
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -1821,7 +1821,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -2491,7 +2491,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
return Inference{builtinTypes->stringType}; return Inference{builtinTypes->stringType};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2532,7 +2532,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
return Inference{builtinTypes->booleanType}; return Inference{builtinTypes->booleanType};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2691,7 +2691,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint startCheckpoint = checkpoint(this); Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -2709,7 +2709,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
} }
); );
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -2849,16 +2849,14 @@ Inference ConstraintGenerator::checkAstExprBinary(
} }
case AstExprBinary::Op::CompareLt: case AstExprBinary::Op::CompareLt:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions) addConstraint(scope, location, EqualityConstraint{leftType, rightType});
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location); TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareGe: case AstExprBinary::Op::CompareGe:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions) addConstraint(scope, location, EqualityConstraint{leftType, rightType});
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance( TypeId resultType = createTypeFunctionInstance(
builtinTypeFunctions().ltFunc, builtinTypeFunctions().ltFunc,
@ -2871,16 +2869,14 @@ Inference ConstraintGenerator::checkAstExprBinary(
} }
case AstExprBinary::Op::CompareLe: case AstExprBinary::Op::CompareLe:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions) addConstraint(scope, location, EqualityConstraint{leftType, rightType});
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location); TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareGt: case AstExprBinary::Op::CompareGt:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions) addConstraint(scope, location, EqualityConstraint{leftType, rightType});
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance( TypeId resultType = createTypeFunctionInstance(
builtinTypeFunctions().leFunc, builtinTypeFunctions().leFunc,
@ -3217,7 +3213,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit))
largeTableDepth++; largeTableDepth++;
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
interiorFreeTypes.back().types.push_back(ty); interiorFreeTypes.back().types.push_back(ty);
else else
DEPRECATED_interiorTypes.back().push_back(ty); DEPRECATED_interiorTypes.back().push_back(ty);
@ -3270,7 +3266,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
{ {
LUAU_ASSERT(!indexValueLowerBound.empty()); LUAU_ASSERT(!indexValueLowerBound.empty());
if (FFlag::LuauSimplifyOutOfLine) if (FFlag::LuauSimplifyOutOfLine2)
{ {
TypeId indexKey = nullptr; TypeId indexKey = nullptr;
TypeId indexValue = nullptr; TypeId indexValue = nullptr;
@ -3478,7 +3474,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
LUAU_ASSERT(nullptr != varargPack); LUAU_ASSERT(nullptr != varargPack);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// Some of the types in argTypes will eventually be generics, and some // Some of the types in argTypes will eventually be generics, and some
// will not. The ones that are not generic will be pruned when // will not. The ones that are not generic will be pruned when
@ -3543,7 +3539,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
if (expectedType && get<FreeType>(*expectedType)) if (expectedType && get<FreeType>(*expectedType))
bindFreeType(*expectedType, actualFunctionType); bindFreeType(*expectedType, actualFunctionType);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
scopeToFunction[signatureScope.get()] = actualFunctionType; scopeToFunction[signatureScope.get()] = actualFunctionType;
return { return {
@ -4064,7 +4060,7 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location,
if (get<NeverType>(follow(rhs))) if (get<NeverType>(follow(rhs)))
return lhs; return lhs;
if (FFlag::LuauSimplifyOutOfLine) if (FFlag::LuauSimplifyOutOfLine2)
{ {
TypeId result = simplifyUnion(scope, location, lhs, rhs); TypeId result = simplifyUnion(scope, location, lhs, rhs);
if (is<UnionType>(follow(result))) if (is<UnionType>(follow(result)))
@ -4080,7 +4076,7 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location,
TypeId ConstraintGenerator::makeUnion(std::vector<TypeId> options) TypeId ConstraintGenerator::makeUnion(std::vector<TypeId> options)
{ {
LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine); LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine2);
TypeId result = arena->addType(UnionType{std::move(options)}); TypeId result = arena->addType(UnionType{std::move(options)});
unionsToSimplify.push_back(result); unionsToSimplify.push_back(result);
return result; return result;
@ -4304,7 +4300,7 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
scope->bindings[symbol] = Binding{tys.front(), location}; scope->bindings[symbol] = Binding{tys.front(), location};
else else
{ {
if (FFlag::LuauSimplifyOutOfLine) if (FFlag::LuauSimplifyOutOfLine2)
{ {
TypeId ty = makeUnion(std::move(tys)); TypeId ty = makeUnion(std::move(tys));
scope->bindings[symbol] = Binding{ty, location}; scope->bindings[symbol] = Binding{ty, location};
@ -4352,7 +4348,7 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
el = result[0]; el = result[0];
else else
{ {
if (FFlag::LuauSimplifyOutOfLine) if (FFlag::LuauSimplifyOutOfLine2)
el = makeUnion(std::move(result)); el = makeUnion(std::move(result));
else else
el = module->internalTypes.addType(UnionType{std::move(result)}); el = module->internalTypes.addType(UnionType{std::move(result)});

View file

@ -32,17 +32,15 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult) LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
namespace Luau namespace Luau
{ {
@ -418,7 +416,7 @@ void ConstraintSolver::run()
} }
// Free types that have no constraints at all can be generalized right away. // Free types that have no constraints at all can be generalized right away.
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
for (TypeId ty : constraintSet.freeTypes) for (TypeId ty : constraintSet.freeTypes)
{ {
@ -479,7 +477,7 @@ void ConstraintSolver::run()
// expansion types, etc, so we need to follow it. // expansion types, etc, so we need to follow it.
ty = follow(ty); ty = follow(ty);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (seen.contains(ty)) if (seen.contains(ty))
continue; continue;
@ -498,7 +496,7 @@ void ConstraintSolver::run()
if (refCount <= 1) if (refCount <= 1)
unblock(ty, Location{}); unblock(ty, Location{});
if (FFlag::LuauEagerGeneralization2 && refCount == 0) if (FFlag::LuauEagerGeneralization3 && refCount == 0)
generalizeOneType(ty); generalizeOneType(ty);
} }
} }
@ -676,7 +674,7 @@ void ConstraintSolver::initFreeTypeTracking()
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1; refCount += 1;
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr); auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
it->second.insert(c.get()); it->second.insert(c.get());
@ -689,13 +687,6 @@ void ConstraintSolver::initFreeTypeTracking()
block(dep, c); block(dep, c);
} }
} }
// Also check flag integrity while we're here
if (FFlag::LuauEagerGeneralization2)
{
LUAU_ASSERT(FFlag::LuauSubtypeGenericsAndNegations);
LUAU_ASSERT(FFlag::LuauNoMoreInjectiveTypeFunctions);
}
} }
void ConstraintSolver::generalizeOneType(TypeId ty) void ConstraintSolver::generalizeOneType(TypeId ty)
@ -739,7 +730,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity? ); // FIXME? Is this the right polarity?
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
trackInteriorFreeType(constraint->scope, ty); trackInteriorFreeType(constraint->scope, ty);
return; return;
@ -900,7 +891,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
{ {
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
ty = follow(ty); ty = follow(ty);
if (auto freeTy = get<FreeType>(ty)) if (auto freeTy = get<FreeType>(ty))
@ -909,7 +900,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
params.foundOutsideFunctions = true; params.foundOutsideFunctions = true;
params.useCount = 1; params.useCount = 1;
params.polarity = freeTy->polarity; params.polarity = freeTy->polarity;
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params); GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params);
if (res.resourceLimitsExceeded) if (res.resourceLimitsExceeded)
reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this. reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this.
@ -922,7 +912,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (constraint->scope->interiorFreeTypePacks) if (constraint->scope->interiorFreeTypePacks)
{ {
@ -942,7 +932,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (c.noGenerics) if (c.noGenerics)
{ {
@ -1387,7 +1377,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
TypePackId argsPack = follow(c.argsPack); TypePackId argsPack = follow(c.argsPack);
TypePackId result = follow(c.result); TypePackId result = follow(c.result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (isBlocked(fn)) if (isBlocked(fn))
return block(c.fn, constraint); return block(c.fn, constraint);
@ -1527,7 +1517,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
for (TypeId freeTy : u2.newFreshTypes) for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy); trackInteriorFreeType(constraint->scope, freeTy);
@ -1612,23 +1602,14 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
// This can potentially contain free types if the return type of // This can potentially contain free types if the return type of
// `inferredTy` is never unified elsewhere. // `inferredTy` is never unified elsewhere.
if (FFlag::LuauTrackInferredFunctionTypeFromCall) trackInteriorFreeType(constraint->scope, inferredTy);
trackInteriorFreeType(constraint->scope, inferredTy);
unblock(c.result, constraint->location); unblock(c.result, constraint->location);
return true; return true;
} }
static AstExpr* unwrapGroup(AstExpr* expr) struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor
{
while (auto group = expr->as<AstExprGroup>())
expr = group->expr;
return expr;
}
struct ContainsGenerics : public TypeOnceVisitor
{ {
DenseHashSet<const void*> generics{nullptr}; DenseHashSet<const void*> generics{nullptr};
@ -1665,6 +1646,93 @@ struct ContainsGenerics : public TypeOnceVisitor
} }
}; };
namespace
{
struct ReferentialReplacer : Substitution
{
NotNull<DenseHashMap<TypeId, TypeId>> replacements;
NotNull<DenseHashMap<TypePackId, TypePackId>> replacementPacks;
ReferentialReplacer(
NotNull<TypeArena> arena,
NotNull<DenseHashMap<TypeId, TypeId>> replacements,
NotNull<DenseHashMap<TypePackId, TypePackId>> replacementPacks
)
: Substitution(TxnLog::empty(), arena)
, replacements(std::move(replacements))
, replacementPacks(std::move(replacementPacks))
{
}
bool isDirty(TypeId ty) override
{
return replacements->find(ty) != nullptr;
}
bool isDirty(TypePackId tp) override
{
return replacementPacks->find(tp) != nullptr;
}
TypeId clean(TypeId ty) override
{
TypeId res = (*replacements)[ty];
LUAU_ASSERT(res);
dontTraverseInto(res);
return res;
}
TypePackId clean(TypePackId tp) override
{
TypePackId res = (*replacementPacks)[tp];
LUAU_ASSERT(res);
dontTraverseInto(res);
return res;
}
};
struct ContainsGenerics : public TypeOnceVisitor
{
NotNull<DenseHashSet<const void*>> generics;
explicit ContainsGenerics(NotNull<DenseHashSet<const void*>> generics)
: generics{generics}
{
}
bool found = false;
bool visit(TypeId ty) override
{
return !found;
}
bool visit(TypeId ty, const GenericType&) override
{
found |= generics->contains(ty);
return true;
}
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{
return !found;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
found |= generics->contains(tp);
return !found;
}
static bool hasGeneric(TypeId ty, NotNull<DenseHashSet<const void*>> generics)
{
ContainsGenerics cg{generics};
cg.traverse(ty);
return cg.found;
}
};
} // namespace
bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint) bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
{ {
TypeId fn = follow(c.fn); TypeId fn = follow(c.fn);
@ -1707,92 +1775,175 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
DenseHashMap<TypeId, TypeId> replacements{nullptr}; DenseHashMap<TypeId, TypeId> replacements{nullptr};
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr}; DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
ContainsGenerics containsGenerics; if (FFlag::LuauAvoidGenericsLeakingDuringFunctionCallCheck)
for (auto generic : ftv->generics)
{ {
replacements[generic] = builtinTypes->unknownType;
containsGenerics.generics.insert(generic);
}
for (auto genericPack : ftv->genericPacks) DenseHashSet<const void*> genericTypesAndPacks{nullptr};
{
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
containsGenerics.generics.insert(genericPack);
}
const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first; Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
const std::vector<TypeId> argPackHead = flatten(argsPack).first;
// If this is a self call, the types will have more elements than the AST call. for (auto generic : ftv->generics)
// We don't attempt to perform bidirectional inference on the self type.
const size_t typeOffset = c.callSite->self ? 1 : 0;
for (size_t i = 0; i < c.callSite->args.size && i + typeOffset < expectedArgs.size() && i + typeOffset < argPackHead.size(); ++i)
{
const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
const TypeId actualArgTy = follow(argPackHead[i + typeOffset]);
AstExpr* expr = unwrapGroup(c.callSite->args.data[i]);
(*c.astExpectedTypes)[expr] = expectedArgTy;
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
// Generic types are skipped over entirely, for now.
if (containsGenerics.hasGeneric(expectedArgTy))
{ {
if (!FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 || !lambdaTy || !lambdaTy->argTypes) // We may see non-generic types here, for example when evaluating a
continue; // recursive function call.
if (auto gty = get<GenericType>(follow(generic)))
const TypePack* argTp = get<TypePack>(follow(lambdaTy->argTypes));
if (!argTp || !argTp->tail)
continue;
if (const VariadicTypePack* argTpTail = get<VariadicTypePack>(follow(argTp->tail));
argTpTail && argTpTail->hidden && argTpTail->ty == builtinTypes->anyType)
{ {
// Strip variadic any replacements[generic] = gty->polarity == Polarity::Negative ? builtinTypes->neverType : builtinTypes->unknownType;
const TypePackId anyLessArgTp = arena->addTypePack(TypePack{argTp->head}); genericTypesAndPacks.insert(generic);
const TypeId newFuncTypeId = arena->addType(FunctionType{anyLessArgTp, lambdaTy->retTypes});
FunctionType* newFunc = getMutable<FunctionType>(newFuncTypeId);
newFunc->argNames = lambdaTy->argNames;
(*c.astTypes)[expr] = newFuncTypeId;
} }
continue;
} }
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy); for (auto genericPack : ftv->genericPacks)
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
if (expectedLambdaTy && lambdaTy && lambdaExpr)
{ {
const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first; replacementPacks[genericPack] = builtinTypes->unknownTypePack;
const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first; genericTypesAndPacks.insert(genericPack);
}
for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j) const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
const std::vector<TypeId> argPackHead = flatten(argsPack).first;
// If this is a self call, the types will have more elements than the AST call.
// We don't attempt to perform bidirectional inference on the self type.
const size_t typeOffset = c.callSite->self ? 1 : 0;
for (size_t i = 0; i < c.callSite->args.size && i + typeOffset < expectedArgs.size() && i + typeOffset < argPackHead.size(); ++i)
{
const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
const TypeId actualArgTy = follow(argPackHead[i + typeOffset]);
AstExpr* expr = unwrapGroup(c.callSite->args.data[i]);
(*c.astExpectedTypes)[expr] = expectedArgTy;
const auto lambdaTy = get<FunctionType>(actualArgTy);
const auto expectedLambdaTy = get<FunctionType>(expectedArgTy);
const auto lambdaExpr = expr->as<AstExprFunction>();
if (expectedLambdaTy && lambdaTy && lambdaExpr)
{ {
if (!lambdaExpr->args.data[j]->annotation && get<FreeType>(follow(lambdaArgTys[j]))) if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
continue;
const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first;
const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first;
for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j)
{ {
shiftReferences(lambdaArgTys[j], expectedLambdaArgTys[j]); if (!lambdaExpr->args.data[j]->annotation && get<FreeType>(follow(lambdaArgTys[j])))
bind(constraint, lambdaArgTys[j], expectedLambdaArgTys[j]); {
shiftReferences(lambdaArgTys[j], expectedLambdaArgTys[j]);
bind(constraint, lambdaArgTys[j], expectedLambdaArgTys[j]);
}
} }
} }
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantNil>())
{
ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}};
if (auto res = replacer.substitute(expectedArgTy))
u2.unify(actualArgTy, *res);
else
u2.unify(actualArgTy, expectedArgTy);
}
else if (expr->is<AstExprTable>() && !ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
{
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
std::vector<TypeId> toBlock;
(void)matchLiteralType(
c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock
);
LUAU_ASSERT(toBlock.empty());
}
} }
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() || }
expr->is<AstExprConstantNil>()) else
{
ContainsGenerics_DEPRECATED containsGenerics;
for (auto generic : ftv->generics)
{ {
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; replacements[generic] = builtinTypes->unknownType;
u2.unify(actualArgTy, expectedArgTy); containsGenerics.generics.insert(generic);
} }
else if (expr->is<AstExprTable>())
for (auto genericPack : ftv->genericPacks)
{ {
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; replacementPacks[genericPack] = builtinTypes->unknownTypePack;
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; containsGenerics.generics.insert(genericPack);
std::vector<TypeId> toBlock; }
(void)matchLiteralType(
c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
); const std::vector<TypeId> argPackHead = flatten(argsPack).first;
LUAU_ASSERT(toBlock.empty());
// If this is a self call, the types will have more elements than the AST call.
// We don't attempt to perform bidirectional inference on the self type.
const size_t typeOffset = c.callSite->self ? 1 : 0;
for (size_t i = 0; i < c.callSite->args.size && i + typeOffset < expectedArgs.size() && i + typeOffset < argPackHead.size(); ++i)
{
const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
const TypeId actualArgTy = follow(argPackHead[i + typeOffset]);
AstExpr* expr = unwrapGroup(c.callSite->args.data[i]);
(*c.astExpectedTypes)[expr] = expectedArgTy;
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
// Generic types are skipped over entirely, for now.
if (containsGenerics.hasGeneric(expectedArgTy))
{
if (!FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 || !lambdaTy || !lambdaTy->argTypes)
continue;
const TypePack* argTp = get<TypePack>(follow(lambdaTy->argTypes));
if (!argTp || !argTp->tail)
continue;
if (const VariadicTypePack* argTpTail = get<VariadicTypePack>(follow(argTp->tail));
argTpTail && argTpTail->hidden && argTpTail->ty == builtinTypes->anyType)
{
// Strip variadic any
const TypePackId anyLessArgTp = arena->addTypePack(TypePack{argTp->head});
const TypeId newFuncTypeId = arena->addType(FunctionType{anyLessArgTp, lambdaTy->retTypes});
FunctionType* newFunc = getMutable<FunctionType>(newFuncTypeId);
newFunc->argNames = lambdaTy->argNames;
(*c.astTypes)[expr] = newFuncTypeId;
}
continue;
}
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
if (expectedLambdaTy && lambdaTy && lambdaExpr)
{
const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first;
const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first;
for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j)
{
if (!lambdaExpr->args.data[j]->annotation && get<FreeType>(follow(lambdaArgTys[j])))
{
shiftReferences(lambdaArgTys[j], expectedLambdaArgTys[j]);
bind(constraint, lambdaArgTys[j], expectedLambdaArgTys[j]);
}
}
}
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantNil>())
{
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
u2.unify(actualArgTy, expectedArgTy);
}
else if (expr->is<AstExprTable>())
{
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
std::vector<TypeId> toBlock;
(void)matchLiteralType(
c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock
);
LUAU_ASSERT(toBlock.empty());
}
} }
} }
@ -1864,16 +2015,8 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
LUAU_ASSERT(get<BlockedType>(resultType)); LUAU_ASSERT(get<BlockedType>(resultType));
LUAU_ASSERT(canMutate(resultType, constraint)); LUAU_ASSERT(canMutate(resultType, constraint));
if (FFlag::LuauHasPropProperBlock) if (isBlocked(subjectType))
{ return block(subjectType, constraint);
if (isBlocked(subjectType))
return block(subjectType, constraint);
}
else
{
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
return block(subjectType, constraint);
}
if (const TableType* subjectTable = getTableType(subjectType)) if (const TableType* subjectTable = getTableType(subjectType))
{ {
@ -1941,7 +2084,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
TypeId upperBound = TypeId upperBound =
arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed}); arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed});
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound)); TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound));
@ -1972,7 +2115,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed}; FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult); emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
trackInteriorFreeType(constraint->scope, resultType); trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType}; tt->indexer = TableIndexer{indexType, resultType};
@ -2161,7 +2304,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
{ {
auto lhsFreeUpperBound = follow(lhsFree->upperBound); auto lhsFreeUpperBound = follow(lhsFree->upperBound);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue); const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
if (!blocked.empty()) if (!blocked.empty())
@ -2341,10 +2484,21 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
if (auto lhsFree = getMutable<FreeType>(lhsType)) if (auto lhsFree = getMutable<FreeType>(lhsType))
{ {
if (auto lhsTable = getMutable<TableType>(lhsFree->upperBound)) if (FFlag::LuauMissingFollowInAssignIndexConstraint)
{ {
if (auto res = tableStuff(lhsTable)) if (auto lhsTable = getMutable<TableType>(follow(lhsFree->upperBound)))
return *res; {
if (auto res = tableStuff(lhsTable))
return *res;
}
}
else
{
if (auto lhsTable = getMutable<TableType>(lhsFree->upperBound))
{
if (auto res = tableStuff(lhsTable))
return *res;
}
} }
TypeId newUpperBound = TypeId newUpperBound =
@ -2603,8 +2757,8 @@ bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Co
struct FindAllUnionMembers : TypeOnceVisitor struct FindAllUnionMembers : TypeOnceVisitor
{ {
DenseHashSet<TypeId> recordedTys{nullptr}; TypeIds recordedTys;
DenseHashSet<TypeId> blockedTys{nullptr}; TypeIds blockedTys;
FindAllUnionMembers() FindAllUnionMembers()
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor(/* skipBoundTypes */ true)
@ -3062,7 +3216,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
{ {
const TypeId upperBound = follow(ft->upperBound); const TypeId upperBound = follow(ft->upperBound);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound)) if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
{ {
@ -3530,7 +3684,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
// Any constraint that might have mutated source may now mutate target // Any constraint that might have mutated source may now mutate target
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
auto it = mutatedFreeTypeToConstraint.find(source); auto it = mutatedFreeTypeToConstraint.find(source);
if (it != mutatedFreeTypeToConstraint.end()) if (it != mutatedFreeTypeToConstraint.end())

View file

@ -13,7 +13,6 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
@ -1069,11 +1068,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
{ {
visitExpr(c->func); visitExpr(c->func);
if (FFlag::LuauPreprocessTypestatedArgument) for (AstExpr* arg : c->args)
{ visitExpr(arg);
for (AstExpr* arg : c->args)
visitExpr(arg);
}
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin())) if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{ {
@ -1105,12 +1101,6 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def); visitLValue(firstArg, def);
} }
if (!FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
// We treat function calls as "subscripted" as they could potentially // We treat function calls as "subscripted" as they could potentially
// return a subscripted value, consider: // return a subscripted value, consider:
// //

View file

@ -1,967 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Differ.h"
#include "Luau/Common.h"
#include "Luau/Error.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypePack.h"
#include "Luau/Unifiable.h"
#include <optional>
#include <string>
#include <unordered_set>
#include <vector>
namespace Luau
{
std::string DiffPathNode::toString() const
{
switch (kind)
{
case DiffPathNode::Kind::TableProperty:
{
if (!tableProperty.has_value())
throw InternalCompilerError{"DiffPathNode has kind TableProperty but tableProperty is nullopt"};
return *tableProperty;
break;
}
case DiffPathNode::Kind::FunctionArgument:
{
if (!index.has_value())
return "Arg[Variadic]";
// Add 1 because Lua is 1-indexed
return "Arg[" + std::to_string(*index + 1) + "]";
}
case DiffPathNode::Kind::FunctionReturn:
{
if (!index.has_value())
return "Ret[Variadic]";
// Add 1 because Lua is 1-indexed
return "Ret[" + std::to_string(*index + 1) + "]";
}
case DiffPathNode::Kind::Negation:
{
return "Negation";
}
default:
{
throw InternalCompilerError{"DiffPathNode::toString is not exhaustive"};
}
}
}
DiffPathNode DiffPathNode::constructWithTableProperty(Name tableProperty)
{
return DiffPathNode{DiffPathNode::Kind::TableProperty, tableProperty, std::nullopt};
}
DiffPathNode DiffPathNode::constructWithKindAndIndex(Kind kind, size_t index)
{
return DiffPathNode{kind, std::nullopt, index};
}
DiffPathNode DiffPathNode::constructWithKind(Kind kind)
{
return DiffPathNode{kind, std::nullopt, std::nullopt};
}
DiffPathNodeLeaf DiffPathNodeLeaf::detailsNormal(TypeId ty)
{
return DiffPathNodeLeaf{ty, std::nullopt, std::nullopt, false, std::nullopt};
}
DiffPathNodeLeaf DiffPathNodeLeaf::detailsTableProperty(TypeId ty, Name tableProperty)
{
return DiffPathNodeLeaf{ty, tableProperty, std::nullopt, false, std::nullopt};
}
DiffPathNodeLeaf DiffPathNodeLeaf::detailsUnionIndex(TypeId ty, size_t index)
{
return DiffPathNodeLeaf{ty, std::nullopt, std::nullopt, false, index};
}
DiffPathNodeLeaf DiffPathNodeLeaf::detailsLength(int minLength, bool isVariadic)
{
return DiffPathNodeLeaf{std::nullopt, std::nullopt, minLength, isVariadic, std::nullopt};
}
DiffPathNodeLeaf DiffPathNodeLeaf::nullopts()
{
return DiffPathNodeLeaf{std::nullopt, std::nullopt, std::nullopt, false, std::nullopt};
}
std::string DiffPath::toString(bool prependDot) const
{
std::string pathStr;
bool isFirstInForLoop = !prependDot;
for (auto node = path.rbegin(); node != path.rend(); node++)
{
if (isFirstInForLoop)
{
isFirstInForLoop = false;
}
else
{
pathStr += ".";
}
pathStr += node->toString();
}
return pathStr;
}
std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const
{
std::string conditionalNewline = multiLine ? "\n" : " ";
std::string conditionalIndent = multiLine ? " " : "";
std::string pathStr{rootName + diffPath.toString(true)};
switch (kind)
{
case DiffError::Kind::Normal:
{
checkNonMissingPropertyLeavesHaveNulloptTableProperty();
return pathStr + conditionalNewline + "has type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
}
case DiffError::Kind::MissingTableProperty:
{
if (leaf.ty.has_value())
{
if (!leaf.tableProperty.has_value())
throw InternalCompilerError{"leaf.tableProperty is nullopt"};
return pathStr + "." + *leaf.tableProperty + conditionalNewline + "has type" + conditionalNewline + conditionalIndent +
Luau::toString(*leaf.ty);
}
else if (otherLeaf.ty.has_value())
{
if (!otherLeaf.tableProperty.has_value())
throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"};
return pathStr + conditionalNewline + "is missing the property" + conditionalNewline + conditionalIndent + *otherLeaf.tableProperty;
}
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
}
case DiffError::Kind::MissingUnionMember:
{
// TODO: do normal case
if (leaf.ty.has_value())
{
if (!leaf.unionIndex.has_value())
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
return pathStr + conditionalNewline + "is a union containing type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
}
else if (otherLeaf.ty.has_value())
{
return pathStr + conditionalNewline + "is a union missing type" + conditionalNewline + conditionalIndent + Luau::toString(*otherLeaf.ty);
}
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
}
case DiffError::Kind::MissingIntersectionMember:
{
// TODO: better message for intersections
// An intersection of just functions is always an "overloaded function"
// An intersection of just tables is always a "joined table"
if (leaf.ty.has_value())
{
if (!leaf.unionIndex.has_value())
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
return pathStr + conditionalNewline + "is an intersection containing type" + conditionalNewline + conditionalIndent +
Luau::toString(*leaf.ty);
}
else if (otherLeaf.ty.has_value())
{
return pathStr + conditionalNewline + "is an intersection missing type" + conditionalNewline + conditionalIndent +
Luau::toString(*otherLeaf.ty);
}
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
}
case DiffError::Kind::LengthMismatchInFnArgs:
{
if (!leaf.minLength.has_value())
throw InternalCompilerError{"leaf.minLength is nullopt"};
return pathStr + conditionalNewline + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
}
case DiffError::Kind::LengthMismatchInFnRets:
{
if (!leaf.minLength.has_value())
throw InternalCompilerError{"leaf.minLength is nullopt"};
return pathStr + conditionalNewline + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
}
default:
{
throw InternalCompilerError{"DiffPath::toStringALeaf is not exhaustive"};
}
}
}
void DiffError::checkNonMissingPropertyLeavesHaveNulloptTableProperty() const
{
if (left.tableProperty.has_value() || right.tableProperty.has_value())
throw InternalCompilerError{"Non-MissingProperty DiffError should have nullopt tableProperty in both leaves"};
}
std::string getDevFixFriendlyName(const std::optional<std::string>& maybeSymbol, TypeId ty)
{
if (maybeSymbol.has_value())
return *maybeSymbol;
if (auto table = get<TableType>(ty))
{
if (table->name.has_value())
return *table->name;
else if (table->syntheticName.has_value())
return *table->syntheticName;
}
if (auto metatable = get<MetatableType>(ty))
{
if (metatable->syntheticName.has_value())
{
return *metatable->syntheticName;
}
}
return "<unlabeled-symbol>";
}
std::string DifferEnvironment::getDevFixFriendlyNameLeft() const
{
return getDevFixFriendlyName(externalSymbolLeft, rootLeft);
}
std::string DifferEnvironment::getDevFixFriendlyNameRight() const
{
return getDevFixFriendlyName(externalSymbolRight, rootRight);
}
std::string DiffError::toString(bool multiLine) const
{
std::string conditionalNewline = multiLine ? "\n" : " ";
std::string conditionalIndent = multiLine ? " " : "";
switch (kind)
{
case DiffError::Kind::IncompatibleGeneric:
{
std::string diffPathStr{diffPath.toString(true)};
return "DiffError: these two types are not equal because the left generic at" + conditionalNewline + conditionalIndent + leftRootName +
diffPathStr + conditionalNewline + "cannot be the same type parameter as the right generic at" + conditionalNewline +
conditionalIndent + rightRootName + diffPathStr;
}
default:
{
return "DiffError: these two types are not equal because the left type at" + conditionalNewline + conditionalIndent +
toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + "while the right type at" + conditionalNewline +
conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine);
}
}
}
void DiffError::checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right)
{
if (!left.ty.has_value() || !right.ty.has_value())
{
// TODO: think about whether this should be always thrown!
// For example, Kind::Primitive doesn't make too much sense to have a TypeId
// throw InternalCompilerError{"Left and Right fields are leaf nodes and must have a TypeId"};
}
}
void DifferResult::wrapDiffPath(DiffPathNode node)
{
if (!diffError.has_value())
{
throw InternalCompilerError{"Cannot wrap diffPath because there is no diffError"};
}
diffError->diffPath.path.push_back(node);
}
static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffMetatable(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right);
struct FindSeteqCounterexampleResult
{
// nullopt if no counterexample found
std::optional<size_t> mismatchIdx;
// true if counterexample is in the left, false if cex is in the right
bool inLeft;
};
static FindSeteqCounterexampleResult findSeteqCounterexample(
DifferEnvironment& env,
const std::vector<TypeId>& left,
const std::vector<TypeId>& right
);
static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId right);
/**
* The last argument gives context info on which complex type contained the TypePack.
*/
static DifferResult diffTpi(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, TypePackId left, TypePackId right);
static DifferResult diffCanonicalTpShape(
DifferEnvironment& env,
DiffError::Kind possibleNonNormalErrorKind,
const std::pair<std::vector<TypeId>, std::optional<TypePackId>>& left,
const std::pair<std::vector<TypeId>, std::optional<TypePackId>>& right
);
static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, TypePackId left, TypePackId right);
static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypePackId right);
static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
{
const TableType* leftTable = get<TableType>(left);
const TableType* rightTable = get<TableType>(right);
LUAU_ASSERT(leftTable);
LUAU_ASSERT(rightTable);
for (auto const& [field, value] : leftTable->props)
{
if (rightTable->props.find(field) == rightTable->props.end())
{
// left has a field the right doesn't
return DifferResult{DiffError{
DiffError::Kind::MissingTableProperty,
DiffPathNodeLeaf::detailsTableProperty(value.type(), field),
DiffPathNodeLeaf::nullopts(),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
}
for (auto const& [field, value] : rightTable->props)
{
if (leftTable->props.find(field) == leftTable->props.end())
{
// right has a field the left doesn't
return DifferResult{DiffError{
DiffError::Kind::MissingTableProperty,
DiffPathNodeLeaf::nullopts(),
DiffPathNodeLeaf::detailsTableProperty(value.type(), field),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight()
}};
}
}
// left and right have the same set of keys
for (auto const& [field, leftValue] : leftTable->props)
{
auto const& rightValue = rightTable->props.at(field);
DifferResult differResult = diffUsingEnv(env, leftValue.type(), rightValue.type());
if (differResult.diffError.has_value())
{
differResult.wrapDiffPath(DiffPathNode::constructWithTableProperty(field));
return differResult;
}
}
return DifferResult{};
}
static DifferResult diffMetatable(DifferEnvironment& env, TypeId left, TypeId right)
{
const MetatableType* leftMetatable = get<MetatableType>(left);
const MetatableType* rightMetatable = get<MetatableType>(right);
LUAU_ASSERT(leftMetatable);
LUAU_ASSERT(rightMetatable);
DifferResult diffRes = diffUsingEnv(env, leftMetatable->table, rightMetatable->table);
if (diffRes.diffError.has_value())
{
return diffRes;
}
diffRes = diffUsingEnv(env, leftMetatable->metatable, rightMetatable->metatable);
if (diffRes.diffError.has_value())
{
diffRes.wrapDiffPath(DiffPathNode::constructWithTableProperty("__metatable"));
return diffRes;
}
return DifferResult{};
}
static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId right)
{
const PrimitiveType* leftPrimitive = get<PrimitiveType>(left);
const PrimitiveType* rightPrimitive = get<PrimitiveType>(right);
LUAU_ASSERT(leftPrimitive);
LUAU_ASSERT(rightPrimitive);
if (leftPrimitive->type != rightPrimitive->type)
{
return DifferResult{DiffError{
DiffError::Kind::Normal,
DiffPathNodeLeaf::detailsNormal(left),
DiffPathNodeLeaf::detailsNormal(right),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
return DifferResult{};
}
static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId right)
{
const SingletonType* leftSingleton = get<SingletonType>(left);
const SingletonType* rightSingleton = get<SingletonType>(right);
LUAU_ASSERT(leftSingleton);
LUAU_ASSERT(rightSingleton);
if (*leftSingleton != *rightSingleton)
{
return DifferResult{DiffError{
DiffError::Kind::Normal,
DiffPathNodeLeaf::detailsNormal(left),
DiffPathNodeLeaf::detailsNormal(right),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
return DifferResult{};
}
static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right)
{
const FunctionType* leftFunction = get<FunctionType>(left);
const FunctionType* rightFunction = get<FunctionType>(right);
LUAU_ASSERT(leftFunction);
LUAU_ASSERT(rightFunction);
DifferResult differResult = diffTpi(env, DiffError::Kind::LengthMismatchInFnArgs, leftFunction->argTypes, rightFunction->argTypes);
if (differResult.diffError.has_value())
return differResult;
return diffTpi(env, DiffError::Kind::LengthMismatchInFnRets, leftFunction->retTypes, rightFunction->retTypes);
}
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right)
{
LUAU_ASSERT(get<GenericType>(left));
LUAU_ASSERT(get<GenericType>(right));
// Try to pair up the generics
bool isLeftFree = !env.genericMatchedPairs.contains(left);
bool isRightFree = !env.genericMatchedPairs.contains(right);
if (isLeftFree && isRightFree)
{
env.genericMatchedPairs[left] = right;
env.genericMatchedPairs[right] = left;
return DifferResult{};
}
else if (isLeftFree || isRightFree)
{
return DifferResult{DiffError{
DiffError::Kind::IncompatibleGeneric,
DiffPathNodeLeaf::nullopts(),
DiffPathNodeLeaf::nullopts(),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
// Both generics are already paired up
if (*env.genericMatchedPairs.find(left) == right)
return DifferResult{};
return DifferResult{DiffError{
DiffError::Kind::IncompatibleGeneric,
DiffPathNodeLeaf::nullopts(),
DiffPathNodeLeaf::nullopts(),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right)
{
const NegationType* leftNegation = get<NegationType>(left);
const NegationType* rightNegation = get<NegationType>(right);
LUAU_ASSERT(leftNegation);
LUAU_ASSERT(rightNegation);
DifferResult differResult = diffUsingEnv(env, leftNegation->ty, rightNegation->ty);
if (!differResult.diffError.has_value())
return DifferResult{};
differResult.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::Negation));
return differResult;
}
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right)
{
const ExternType* leftExternType = get<ExternType>(left);
const ExternType* rightExternType = get<ExternType>(right);
LUAU_ASSERT(leftExternType);
LUAU_ASSERT(rightExternType);
if (leftExternType == rightExternType)
{
return DifferResult{};
}
return DifferResult{DiffError{
DiffError::Kind::Normal,
DiffPathNodeLeaf::detailsNormal(left),
DiffPathNodeLeaf::detailsNormal(right),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
static FindSeteqCounterexampleResult findSeteqCounterexample(
DifferEnvironment& env,
const std::vector<TypeId>& left,
const std::vector<TypeId>& right
)
{
std::unordered_set<size_t> unmatchedRightIdxes;
for (size_t i = 0; i < right.size(); i++)
unmatchedRightIdxes.insert(i);
for (size_t leftIdx = 0; leftIdx < left.size(); leftIdx++)
{
bool leftIdxIsMatched = false;
auto unmatchedRightIdxIt = unmatchedRightIdxes.begin();
while (unmatchedRightIdxIt != unmatchedRightIdxes.end())
{
DifferResult differResult = diffUsingEnv(env, left[leftIdx], right[*unmatchedRightIdxIt]);
if (differResult.diffError.has_value())
{
unmatchedRightIdxIt++;
continue;
}
// unmatchedRightIdxIt is matched with current leftIdx
env.recordProvenEqual(left[leftIdx], right[*unmatchedRightIdxIt]);
leftIdxIsMatched = true;
unmatchedRightIdxIt = unmatchedRightIdxes.erase(unmatchedRightIdxIt);
}
if (!leftIdxIsMatched)
{
return FindSeteqCounterexampleResult{leftIdx, true};
}
}
if (unmatchedRightIdxes.empty())
return FindSeteqCounterexampleResult{std::nullopt, false};
return FindSeteqCounterexampleResult{*unmatchedRightIdxes.begin(), false};
}
static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right)
{
const UnionType* leftUnion = get<UnionType>(left);
const UnionType* rightUnion = get<UnionType>(right);
LUAU_ASSERT(leftUnion);
LUAU_ASSERT(rightUnion);
FindSeteqCounterexampleResult findSeteqCexResult = findSeteqCounterexample(env, leftUnion->options, rightUnion->options);
if (findSeteqCexResult.mismatchIdx.has_value())
{
if (findSeteqCexResult.inLeft)
return DifferResult{DiffError{
DiffError::Kind::MissingUnionMember,
DiffPathNodeLeaf::detailsUnionIndex(leftUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
DiffPathNodeLeaf::nullopts(),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
else
return DifferResult{DiffError{
DiffError::Kind::MissingUnionMember,
DiffPathNodeLeaf::nullopts(),
DiffPathNodeLeaf::detailsUnionIndex(rightUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
// TODO: somehow detect mismatch index, likely using heuristics
return DifferResult{};
}
static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId right)
{
const IntersectionType* leftIntersection = get<IntersectionType>(left);
const IntersectionType* rightIntersection = get<IntersectionType>(right);
LUAU_ASSERT(leftIntersection);
LUAU_ASSERT(rightIntersection);
FindSeteqCounterexampleResult findSeteqCexResult = findSeteqCounterexample(env, leftIntersection->parts, rightIntersection->parts);
if (findSeteqCexResult.mismatchIdx.has_value())
{
if (findSeteqCexResult.inLeft)
return DifferResult{DiffError{
DiffError::Kind::MissingIntersectionMember,
DiffPathNodeLeaf::detailsUnionIndex(leftIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
DiffPathNodeLeaf::nullopts(),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
else
return DifferResult{DiffError{
DiffError::Kind::MissingIntersectionMember,
DiffPathNodeLeaf::nullopts(),
DiffPathNodeLeaf::detailsUnionIndex(rightIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
// TODO: somehow detect mismatch index, likely using heuristics
return DifferResult{};
}
static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId right)
{
left = follow(left);
right = follow(right);
if (left->ty.index() != right->ty.index())
{
return DifferResult{DiffError{
DiffError::Kind::Normal,
DiffPathNodeLeaf::detailsNormal(left),
DiffPathNodeLeaf::detailsNormal(right),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
// Both left and right are the same variant
// Check cycles & caches
if (env.isAssumedEqual(left, right) || env.isProvenEqual(left, right))
return DifferResult{};
if (isSimple(left))
{
if (auto lp = get<PrimitiveType>(left))
return diffPrimitive(env, left, right);
else if (auto ls = get<SingletonType>(left))
{
return diffSingleton(env, left, right);
}
else if (auto la = get<AnyType>(left))
{
// Both left and right must be Any if either is Any for them to be equal!
return DifferResult{};
}
else if (auto lu = get<UnknownType>(left))
{
return DifferResult{};
}
else if (auto ln = get<NeverType>(left))
{
return DifferResult{};
}
else if (auto ln = get<NegationType>(left))
{
return diffNegation(env, left, right);
}
else if (auto lc = get<ExternType>(left))
{
return diffExternType(env, left, right);
}
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
}
// Both left and right are the same non-Simple
// Non-simple types must record visits in the DifferEnvironment
env.pushVisiting(left, right);
if (auto lt = get<TableType>(left))
{
DifferResult diffRes = diffTable(env, left, right);
if (!diffRes.diffError.has_value())
{
env.recordProvenEqual(left, right);
}
env.popVisiting();
return diffRes;
}
if (auto lm = get<MetatableType>(left))
{
env.popVisiting();
return diffMetatable(env, left, right);
}
if (auto lf = get<FunctionType>(left))
{
DifferResult diffRes = diffFunction(env, left, right);
if (!diffRes.diffError.has_value())
{
env.recordProvenEqual(left, right);
}
env.popVisiting();
return diffRes;
}
if (auto lg = get<GenericType>(left))
{
DifferResult diffRes = diffGeneric(env, left, right);
if (!diffRes.diffError.has_value())
{
env.recordProvenEqual(left, right);
}
env.popVisiting();
return diffRes;
}
if (auto lu = get<UnionType>(left))
{
DifferResult diffRes = diffUnion(env, left, right);
if (!diffRes.diffError.has_value())
{
env.recordProvenEqual(left, right);
}
env.popVisiting();
return diffRes;
}
if (auto li = get<IntersectionType>(left))
{
DifferResult diffRes = diffIntersection(env, left, right);
if (!diffRes.diffError.has_value())
{
env.recordProvenEqual(left, right);
}
env.popVisiting();
return diffRes;
}
if (auto le = get<ErrorType>(left))
{
// TODO: return debug-friendly result state
env.popVisiting();
return DifferResult{};
}
throw InternalCompilerError{"Unimplemented non-simple TypeId variant for diffing"};
}
static DifferResult diffTpi(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, TypePackId left, TypePackId right)
{
left = follow(left);
right = follow(right);
// Canonicalize
std::pair<std::vector<TypeId>, std::optional<TypePackId>> leftFlatTpi = flatten(left);
std::pair<std::vector<TypeId>, std::optional<TypePackId>> rightFlatTpi = flatten(right);
// Check for shape equality
DifferResult diffResult = diffCanonicalTpShape(env, possibleNonNormalErrorKind, leftFlatTpi, rightFlatTpi);
if (diffResult.diffError.has_value())
{
return diffResult;
}
// Left and Right have the same shape
for (size_t i = 0; i < leftFlatTpi.first.size(); i++)
{
DifferResult differResult = diffUsingEnv(env, leftFlatTpi.first[i], rightFlatTpi.first[i]);
if (!differResult.diffError.has_value())
continue;
switch (possibleNonNormalErrorKind)
{
case DiffError::Kind::LengthMismatchInFnArgs:
{
differResult.wrapDiffPath(DiffPathNode::constructWithKindAndIndex(DiffPathNode::Kind::FunctionArgument, i));
return differResult;
}
case DiffError::Kind::LengthMismatchInFnRets:
{
differResult.wrapDiffPath(DiffPathNode::constructWithKindAndIndex(DiffPathNode::Kind::FunctionReturn, i));
return differResult;
}
default:
{
throw InternalCompilerError{"Unhandled Tpi diffing case with same shape"};
}
}
}
if (!leftFlatTpi.second.has_value())
return DifferResult{};
return diffHandleFlattenedTail(env, possibleNonNormalErrorKind, *leftFlatTpi.second, *rightFlatTpi.second);
}
static DifferResult diffCanonicalTpShape(
DifferEnvironment& env,
DiffError::Kind possibleNonNormalErrorKind,
const std::pair<std::vector<TypeId>, std::optional<TypePackId>>& left,
const std::pair<std::vector<TypeId>, std::optional<TypePackId>>& right
)
{
if (left.first.size() == right.first.size() && left.second.has_value() == right.second.has_value())
return DifferResult{};
return DifferResult{DiffError{
possibleNonNormalErrorKind,
DiffPathNodeLeaf::detailsLength(int(left.first.size()), left.second.has_value()),
DiffPathNodeLeaf::detailsLength(int(right.first.size()), right.second.has_value()),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, TypePackId left, TypePackId right)
{
left = follow(left);
right = follow(right);
if (left->ty.index() != right->ty.index())
{
return DifferResult{DiffError{
DiffError::Kind::Normal,
DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->first),
DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->second),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
// Both left and right are the same variant
if (auto lv = get<VariadicTypePack>(left))
{
auto rv = get<VariadicTypePack>(right);
DifferResult differResult = diffUsingEnv(env, lv->ty, rv->ty);
if (!differResult.diffError.has_value())
return DifferResult{};
switch (possibleNonNormalErrorKind)
{
case DiffError::Kind::LengthMismatchInFnArgs:
{
differResult.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::FunctionArgument));
return differResult;
}
case DiffError::Kind::LengthMismatchInFnRets:
{
differResult.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::FunctionReturn));
return differResult;
}
default:
{
throw InternalCompilerError{"Unhandled flattened tail case for VariadicTypePack"};
}
}
}
if (auto lg = get<GenericTypePack>(left))
{
DifferResult diffRes = diffGenericTp(env, left, right);
if (!diffRes.diffError.has_value())
return DifferResult{};
switch (possibleNonNormalErrorKind)
{
case DiffError::Kind::LengthMismatchInFnArgs:
{
diffRes.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::FunctionArgument));
return diffRes;
}
case DiffError::Kind::LengthMismatchInFnRets:
{
diffRes.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::FunctionReturn));
return diffRes;
}
default:
{
throw InternalCompilerError{"Unhandled flattened tail case for GenericTypePack"};
}
}
}
throw InternalCompilerError{"Unhandled tail type pack variant for flattened tails"};
}
static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypePackId right)
{
LUAU_ASSERT(get<GenericTypePack>(left));
LUAU_ASSERT(get<GenericTypePack>(right));
// Try to pair up the generics
bool isLeftFree = !env.genericTpMatchedPairs.contains(left);
bool isRightFree = !env.genericTpMatchedPairs.contains(right);
if (isLeftFree && isRightFree)
{
env.genericTpMatchedPairs[left] = right;
env.genericTpMatchedPairs[right] = left;
return DifferResult{};
}
else if (isLeftFree || isRightFree)
{
return DifferResult{DiffError{
DiffError::Kind::IncompatibleGeneric,
DiffPathNodeLeaf::nullopts(),
DiffPathNodeLeaf::nullopts(),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
// Both generics are already paired up
if (*env.genericTpMatchedPairs.find(left) == right)
return DifferResult{};
return DifferResult{DiffError{
DiffError::Kind::IncompatibleGeneric,
DiffPathNodeLeaf::nullopts(),
DiffPathNodeLeaf::nullopts(),
env.getDevFixFriendlyNameLeft(),
env.getDevFixFriendlyNameRight(),
}};
}
bool DifferEnvironment::isProvenEqual(TypeId left, TypeId right) const
{
return provenEqual.find({left, right}) != provenEqual.end();
}
bool DifferEnvironment::isAssumedEqual(TypeId left, TypeId right) const
{
return visiting.find({left, right}) != visiting.end();
}
void DifferEnvironment::recordProvenEqual(TypeId left, TypeId right)
{
provenEqual.insert({left, right});
provenEqual.insert({right, left});
}
void DifferEnvironment::pushVisiting(TypeId left, TypeId right)
{
LUAU_ASSERT(visiting.find({left, right}) == visiting.end());
LUAU_ASSERT(visiting.find({right, left}) == visiting.end());
visitingStack.push_back({left, right});
visiting.insert({left, right});
visiting.insert({right, left});
}
void DifferEnvironment::popVisiting()
{
auto tyPair = visitingStack.back();
visiting.erase({tyPair.first, tyPair.second});
visiting.erase({tyPair.second, tyPair.first});
visitingStack.pop_back();
}
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment::visitingBegin() const
{
return visitingStack.crbegin();
}
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment::visitingEnd() const
{
return visitingStack.crend();
}
DifferResult diff(TypeId ty1, TypeId ty2)
{
DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt};
return diffUsingEnv(differEnv, ty1, ty2);
}
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2)
{
DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2};
return diffUsingEnv(differEnv, ty1, ty2);
}
bool isSimple(TypeId ty)
{
ty = follow(ty);
// TODO: think about GenericType, etc.
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ExternType>(ty) ||
get<UnknownType>(ty) || get<NeverType>(ty);
}
} // namespace Luau

View file

@ -18,7 +18,7 @@
#include <unordered_set> #include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive) LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
@ -663,7 +663,7 @@ struct ErrorConverter
} }
// binary operators // binary operators
const auto binaryOps = FFlag::LuauEagerGeneralization2 ? kBinaryOps : DEPRECATED_kBinaryOps; const auto binaryOps = FFlag::LuauEagerGeneralization3 ? kBinaryOps : DEPRECATED_kBinaryOps;
if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end()) if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end())
{ {
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
@ -718,7 +718,7 @@ struct ErrorConverter
"'"; "'";
} }
if ((FFlag::LuauEagerGeneralization2 ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name)) if ((FFlag::LuauEagerGeneralization3 ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name))
{ {
return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" + return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
@ -852,6 +852,25 @@ struct ErrorConverter
{ {
return "Unexpected array-like table item: the indexer key type of this table is not `number`."; return "Unexpected array-like table item: the indexer key type of this table is not `number`.";
} }
std::string operator()(const CannotCheckDynamicStringFormatCalls& e) const
{
return "We cannot statically check the type of `string.format` when called with a format string that is not statically known.\n"
"If you'd like to use an unchecked `string.format` call, you can cast the format string to `any` using `:: any`.";
}
std::string operator()(const GenericTypeCountMismatch& e) const
{
return "Different number of generic type parameters: subtype had " + std::to_string(e.subTyGenericCount) + ", supertype had " +
std::to_string(e.superTyGenericCount) + ".";
}
std::string operator()(const GenericTypePackCountMismatch& e) const
{
return "Different number of generic type pack parameters: subtype had " + std::to_string(e.subTyGenericPackCount) + ", supertype had " +
std::to_string(e.superTyGenericPackCount) + ".";
}
}; };
struct InvalidNameChecker struct InvalidNameChecker
@ -1240,6 +1259,16 @@ bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
return *rhsType == *rhs.rhsType && reason == rhs.reason; return *rhsType == *rhs.rhsType && reason == rhs.reason;
} }
bool GenericTypeCountMismatch::operator==(const GenericTypeCountMismatch& rhs) const
{
return subTyGenericCount == rhs.subTyGenericCount && superTyGenericCount == rhs.superTyGenericCount;
}
bool GenericTypePackCountMismatch::operator==(const GenericTypePackCountMismatch& rhs) const
{
return subTyGenericPackCount == rhs.subTyGenericPackCount && superTyGenericPackCount == rhs.superTyGenericPackCount;
}
std::string toString(const TypeError& error) std::string toString(const TypeError& error)
{ {
return toString(error, TypeErrorToStringOptions{}); return toString(error, TypeErrorToStringOptions{});
@ -1451,6 +1480,15 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
else if constexpr (std::is_same_v<T, ReservedIdentifier>) else if constexpr (std::is_same_v<T, ReservedIdentifier>)
{ {
} }
else if constexpr (std::is_same_v<T, CannotCheckDynamicStringFormatCalls>)
{
}
else if constexpr (std::is_same_v<T, GenericTypeCountMismatch>)
{
}
else if constexpr (std::is_same_v<T, GenericTypePackCountMismatch>)
{
}
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -40,7 +40,7 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
@ -1444,7 +1444,7 @@ ModulePtr check(
// is set, and another when it is unset. // is set, and another when it is unset.
std::optional<ConstraintSolver> cs; std::optional<ConstraintSolver> cs;
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
ConstraintSet constraintSet = cg.run(sourceModule.root); ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors); result->errors = std::move(constraintSet.errors);

View file

@ -10,13 +10,14 @@
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeIds.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Substitution.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationCannotMutateAcrossModules)
namespace Luau namespace Luau
{ {
@ -469,7 +470,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (!subsumes(scope, ft.scope)) if (!subsumes(scope, ft.scope))
return true; return true;
@ -520,7 +521,7 @@ struct FreeTypeSearcher : TypeVisitor
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
unsealedTables.insert(ty); unsealedTables.insert(ty);
else else
{ {
@ -593,7 +594,7 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer) if (tt.indexer)
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// {[K]: V} is equivalent to three functions: get, set, and iterate // {[K]: V} is equivalent to three functions: get, set, and iterate
// //
@ -651,7 +652,7 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope)) if (!subsumes(scope, ftp.scope))
return true; return true;
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
GeneralizationParams<TypePackId>& params = typePacks[tp]; GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount; ++params.useCount;
@ -1127,129 +1128,77 @@ struct TypeCacher : TypeOnceVisitor
} }
}; };
struct RemoveType : Substitution // NOLINT namespace
{
struct TypeRemover
{ {
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeArena> arena;
TypeId needle; TypeId needle;
DenseHashSet<TypeId> seen{nullptr};
RemoveType(NotNull<BuiltinTypes> builtinTypes, TypeArena* arena, TypeId needle) void process(TypeId item)
: Substitution(arena)
, builtinTypes(builtinTypes)
, needle(needle)
{ {
} item = follow(item);
bool ignoreChildren(TypeId ty) override // If we've already visited this item, or it's outside our arena, then
{ // do not try to mutate it.
if (get<UnionType>(ty) || get<IntersectionType>(ty)) if (seen.contains(item) || item->owningArena != arena || item->persistent)
return false; return;
else seen.insert(item);
return true;
}
bool isDirty(TypeId ty) override if (auto ut = getMutable<UnionType>(item))
{
// A union or intersection is dirty if it contains the needle or if it has any duplicate members.
if (auto ut = get<UnionType>(ty))
{ {
DenseHashSet<TypeId> distinctParts{nullptr}; TypeIds newOptions;
size_t count = 0; for (TypeId option : ut->options)
for (TypeId part : ut)
{ {
++count; process(option);
if (part == needle) option = follow(option);
return true; if (option != needle && !is<NeverType>(option) && option != item)
distinctParts.insert(follow(part)); newOptions.insert(option);
}
if (ut->options.size() != newOptions.size())
{
if (newOptions.empty())
emplaceType<BoundType>(asMutable(item), builtinTypes->neverType);
else if (newOptions.size() == 1)
emplaceType<BoundType>(asMutable(item), *newOptions.begin());
else
emplaceType<BoundType>(asMutable(item), arena->addType(UnionType{newOptions.take()}));
} }
return distinctParts.size() != count;
} }
else if (auto it = get<IntersectionType>(ty)) else if (auto it = getMutable<IntersectionType>(item))
{ {
DenseHashSet<TypeId> distinctParts{nullptr}; TypeIds newParts;
size_t count = 0; for (TypeId part : it->parts)
for (TypeId part : it)
{ {
++count; process(part);
if (part == needle) part = follow(part);
return true; if (part != needle && !is<UnknownType>(part) && part != item)
distinctParts.insert(follow(part)); newParts.insert(part);
}
if (it->parts.size() != newParts.size())
{
if (newParts.empty())
emplaceType<BoundType>(asMutable(item), builtinTypes->unknownType);
else if (newParts.size() == 1)
emplaceType<BoundType>(asMutable(item), *newParts.begin());
else
emplaceType<BoundType>(asMutable(item), arena->addType(IntersectionType{newParts.take()}));
} }
return distinctParts.size() != count;
} }
return false;
} }
bool isDirty(TypePackId tp) override
{
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ut = get<UnionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : ut)
{
if (ty != needle && !is<NeverType>(ty))
newParts.insert(ty);
}
if (newParts.empty())
return builtinTypes->neverType;
else if (newParts.size() == 1)
{
TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
return onlyType;
}
else
return arena->addType(UnionType{newParts.takeVector()});
}
else if (auto it = get<IntersectionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : it)
{
if (ty != needle && !is<UnknownType>(ty))
newParts.insert(ty);
}
if (newParts.empty())
return builtinTypes->unknownType;
else if (newParts.size() == 1)
{
TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
return onlyType;
}
else
return arena->addType(IntersectionType{newParts.takeVector()});
}
else
return ty;
}
TypePackId clean(TypePackId tp) override
{
return tp;
}
}; };
/** void removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
*
* @param haystack Either the upper or lower bound of a free type.
* @param needle The type to be removed.
*/
[[nodiscard]]
static std::optional<TypeId> removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
{ {
RemoveType rt{builtinTypes, arena, needle}; TypeRemover tr{builtinTypes, arena, needle};
return rt.substitute(haystack); tr.process(haystack);
}
} }
GeneralizationResult<TypeId> generalizeType( GeneralizationResult<TypeId> generalizeType(
@ -1274,7 +1223,7 @@ GeneralizationResult<TypeId> generalizeType(
if (!hasLowerBound && !hasUpperBound) if (!hasLowerBound && !hasUpperBound)
{ {
if (!isWithinFunction || (!FFlag::LuauEagerGeneralization2 && (params.polarity != Polarity::Mixed && params.useCount == 1))) if (!isWithinFunction || (!FFlag::LuauEagerGeneralization3 && (params.polarity != Polarity::Mixed && params.useCount == 1)))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else else
{ {
@ -1294,19 +1243,11 @@ GeneralizationResult<TypeId> generalizeType(
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == freeTy) if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == freeTy)
lowerFree->upperBound = builtinTypes->unknownType; lowerFree->upperBound = builtinTypes->unknownType;
else else
{ removeType(arena, builtinTypes, lb, freeTy);
std::optional<TypeId> removed = removeType(arena, builtinTypes, lb, freeTy);
if (removed)
lb = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
ft->lowerBound = lb;
}
if (follow(lb) != freeTy) if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb); emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization2 && params.useCount == 1)) else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization3 && params.useCount == 1))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else else
{ {
@ -1321,15 +1262,7 @@ GeneralizationResult<TypeId> generalizeType(
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == freeTy) if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == freeTy)
upperFree->lowerBound = builtinTypes->neverType; upperFree->lowerBound = builtinTypes->neverType;
else else
{ removeType(arena, builtinTypes, ub, freeTy);
// If the free type appears within its own upper bound, cull that cycle.
std::optional<TypeId> removed = removeType(arena, builtinTypes, ub, freeTy);
if (removed)
ub = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
ft->upperBound = ub;
}
if (follow(ub) != freeTy) if (follow(ub) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), ub); emplaceType<BoundType>(asMutable(freeTy), ub);
@ -1339,17 +1272,14 @@ GeneralizationResult<TypeId> generalizeType(
// //
// A <: 'b < C // A <: 'b < C
// //
// We can approximately generalize this to the intersection of it's // We can approximately generalize this to the intersection of its
// bounds, taking care to avoid constructing a degenerate // bounds, taking care to avoid constructing a degenerate
// union or intersection by clipping the free type from the upper // union or intersection by clipping the free type from the upper
// and lower bounds, then also cleaning the resulting intersection. // and lower bounds, then also cleaning the resulting intersection.
std::optional<TypeId> removedLb = removeType(arena, builtinTypes, ft->lowerBound, freeTy); removeType(arena, builtinTypes, ft->lowerBound, freeTy);
if (!removedLb) TypeId cleanedTy = arena->addType(IntersectionType{{ft->lowerBound, ub}});
return {std::nullopt, false, true}; removeType(arena, builtinTypes, cleanedTy, freeTy);
std::optional<TypeId> cleanedTy = removeType(arena, builtinTypes, arena->addType(IntersectionType{{*removedLb, ub}}), freeTy); emplaceType<BoundType>(asMutable(freeTy), cleanedTy);
if (!cleanedTy)
return {std::nullopt, false, true};
emplaceType<BoundType>(asMutable(freeTy), *cleanedTy);
} }
else else
{ {
@ -1423,7 +1353,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes}; FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty); fts.traverse(ty);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
FunctionType* functionTy = getMutable<FunctionType>(ty); FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t) auto pushGeneric = [&](TypeId t)
@ -1521,6 +1451,12 @@ struct GenericCounter : TypeVisitor
Polarity polarity = Polarity::None; Polarity polarity = Polarity::None;
}; };
// This traversal does need to walk into types multiple times because we
// care about generics that are only refererd to once. If a type is present
// more than once, however, we don't care exactly how many times, so we also
// track counts in our "seen set."
DenseHashMap<TypeId, size_t> seenCounts{nullptr};
NotNull<DenseHashSet<TypeId>> cachedTypes; NotNull<DenseHashSet<TypeId>> cachedTypes;
DenseHashMap<TypeId, CounterState> generics{nullptr}; DenseHashMap<TypeId, CounterState> generics{nullptr};
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr}; DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
@ -1537,6 +1473,12 @@ struct GenericCounter : TypeVisitor
if (ty->persistent) if (ty->persistent)
return false; return false;
size_t& seenCount = seenCounts[ty];
if (seenCount > 1)
return false;
++seenCount;
polarity = invert(polarity); polarity = invert(polarity);
traverse(ft.argTypes); traverse(ft.argTypes);
polarity = invert(polarity); polarity = invert(polarity);
@ -1550,6 +1492,11 @@ struct GenericCounter : TypeVisitor
if (ty->persistent) if (ty->persistent)
return false; return false;
size_t& seenCount = seenCounts[ty];
if (seenCount > 1)
return false;
++seenCount;
const Polarity previous = polarity; const Polarity previous = polarity;
for (const auto& [_name, prop] : tt.props) for (const auto& [_name, prop] : tt.props)
@ -1650,7 +1597,7 @@ void pruneUnnecessaryGenerics(
TypeId ty TypeId ty
) )
{ {
if (!FFlag::LuauEagerGeneralization2) if (!FFlag::LuauEagerGeneralization3)
return; return;
ty = follow(ty); ty = follow(ty);
@ -1696,7 +1643,11 @@ void pruneUnnecessaryGenerics(
for (const auto& [generic, state] : counter.generics) for (const auto& [generic, state] : counter.generics)
{ {
if (state.count == 1 && state.polarity != Polarity::Mixed) if (state.count == 1 && state.polarity != Polarity::Mixed)
{
if (FFlag::LuauGeneralizationCannotMutateAcrossModules && arena.get() != generic->owningArena)
continue;
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
}
} }
// Remove duplicates and types that aren't actually generics. // Remove duplicates and types that aren't actually generics.

View file

@ -5,7 +5,7 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
namespace Luau namespace Luau
{ {
@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor
template<typename TID> template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty) static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{ {
if (!FFlag::LuauEagerGeneralization2) if (!FFlag::LuauEagerGeneralization3)
return; return;
InferPolarity infer{arena, scope}; InferPolarity infer{arena, scope};

View file

@ -1,8 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/IostreamHelpers.h" #include "Luau/IostreamHelpers.h"
#include "Luau/Error.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TypePath.h" #include "Luau/TypePath.h"
#include <type_traits>
namespace Luau namespace Luau
{ {
@ -250,6 +253,18 @@ static void errorToString(std::ostream& stream, const T& err)
} }
else if constexpr (std::is_same_v<T, UnexpectedArrayLikeTableItem>) else if constexpr (std::is_same_v<T, UnexpectedArrayLikeTableItem>)
stream << "UnexpectedArrayLikeTableItem {}"; stream << "UnexpectedArrayLikeTableItem {}";
else if constexpr (std::is_same_v<T, CannotCheckDynamicStringFormatCalls>)
stream << "CannotCheckDynamicStringFormatCalls {}";
else if constexpr (std::is_same_v<T, GenericTypeCountMismatch>)
{
stream << "GenericTypeCountMismatch { subTyGenericCount = " << err.subTyGenericCount << ", superTyGenericCount = " << err.superTyGenericCount
<< " }";
}
else if constexpr (std::is_same_v<T, GenericTypePackCountMismatch>)
{
stream << "GenericTypePackCountMismatch { subTyGenericPackCount = " << err.subTyGenericPackCount
<< ", superTyGenericPackCount = " << err.superTyGenericPackCount << " }";
}
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -21,6 +21,8 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
namespace Luau namespace Luau
{ {
@ -2897,6 +2899,24 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
if (FFlag::LuauNormalizationReorderFreeTypeIntersect)
{
for (auto& [tyvar, inter] : there.tyvars)
{
int index = tyvarIndex(tyvar);
if (ignoreSmallerTyvars < index)
{
auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique<NormalizedType>(NormalizedType{builtinTypes}));
if (fresh)
{
NormalizationResult res = unionNormals(*found->second, here, index);
if (res != NormalizationResult::True)
return res;
}
}
}
}
here.booleans = intersectionOfBools(here.booleans, there.booleans); here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectExternTypes(here.externTypes, there.externTypes); intersectExternTypes(here.externTypes, there.externTypes);
@ -2909,20 +2929,24 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
intersectFunctions(here.functions, there.functions); intersectFunctions(here.functions, there.functions);
intersectTables(here.tables, there.tables); intersectTables(here.tables, there.tables);
for (auto& [tyvar, inter] : there.tyvars) if (!FFlag::LuauNormalizationReorderFreeTypeIntersect)
{ {
int index = tyvarIndex(tyvar); for (auto& [tyvar, inter] : there.tyvars)
if (ignoreSmallerTyvars < index)
{ {
auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique<NormalizedType>(NormalizedType{builtinTypes})); int index = tyvarIndex(tyvar);
if (fresh) if (ignoreSmallerTyvars < index)
{ {
NormalizationResult res = unionNormals(*found->second, here, index); auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique<NormalizedType>(NormalizedType{builtinTypes}));
if (res != NormalizationResult::True) if (fresh)
return res; {
NormalizationResult res = unionNormals(*found->second, here, index);
if (res != NormalizationResult::True)
return res;
}
} }
} }
} }
for (auto it = here.tyvars.begin(); it != here.tyvars.end();) for (auto it = here.tyvars.begin(); it != here.tyvars.end();)
{ {
TypeId tyvar = it->first; TypeId tyvar = it->first;
@ -3016,10 +3040,22 @@ NormalizationResult Normalizer::intersectNormalWithTy(
} }
else if (get<TableType>(there) || get<MetatableType>(there)) else if (get<TableType>(there) || get<MetatableType>(there))
{ {
TypeIds tables = std::move(here.tables); if (FFlag::LuauSolverV2 && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes)
clearNormal(here); {
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes); NormalizedExternType externTypes = std::move(here.externTypes);
here.tables = std::move(tables); TypeIds tables = std::move(here.tables);
clearNormal(here);
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables);
here.externTypes = std::move(externTypes);
}
else
{
TypeIds tables = std::move(here.tables);
clearNormal(here);
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables);
}
} }
else if (get<ExternType>(there)) else if (get<ExternType>(there))
{ {

View file

@ -16,8 +16,6 @@
LUAU_FASTINT(LuauTypeReductionRecursionLimit) LUAU_FASTINT(LuauTypeReductionRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8) LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType) LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
namespace Luau namespace Luau
@ -771,12 +769,9 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
newParts.insert(simplified); newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption) // Initial combination size check could not predict nested union iteration
{ if (newParts.size() > maxSize)
// Initial combination size check could not predict nested union iteration return arena->addType(IntersectionType{{left, right}});
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
} }
if (!changed) if (!changed)
@ -818,12 +813,9 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
newParts.insert(simplified); newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption) // Initial combination size check could not predict nested union iteration
{ if (newParts.size() > maxSize)
// Initial combination size check could not predict nested union iteration return arena->addType(IntersectionType{{left, right}});
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
} }
} }
@ -1310,24 +1302,21 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
return std::nullopt; return std::nullopt;
} }
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect) if (isTruthyType(left))
{ if (auto res = basicIntersectWithTruthy(right))
if (isTruthyType(left)) return res;
if (auto res = basicIntersectWithTruthy(right))
return res;
if (isTruthyType(right)) if (isTruthyType(right))
if (auto res = basicIntersectWithTruthy(left)) if (auto res = basicIntersectWithTruthy(left))
return res; return res;
if (isFalsyType(left)) if (isFalsyType(left))
if (auto res = basicIntersectWithFalsy(right)) if (auto res = basicIntersectWithFalsy(right))
return res; return res;
if (isFalsyType(right)) if (isFalsyType(right))
if (auto res = basicIntersectWithFalsy(left)) if (auto res = basicIntersectWithFalsy(left))
return res; return res;
}
Relation relation = relate(left, right); Relation relation = relate(left, right);
if (left == right || Relation::Coincident == relation) if (left == right || Relation::Coincident == relation)

View file

@ -8,7 +8,6 @@
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/ToString.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
@ -19,9 +18,9 @@
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauEagerGeneralization3)
namespace Luau namespace Luau
{ {
@ -669,27 +668,26 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = {false}; result = {false};
else if (get<ErrorType>(subTy)) else if (get<ErrorType>(subTy))
result = {true}; result = {true};
else if (auto subGeneric = get<GenericType>(subTy); FFlag::LuauSubtypeGenericsAndNegations && subGeneric && variance == Variance::Covariant) else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
{ {
bool ok = bindGeneric(env, subTy, superTy); bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok; result.isSubtype = ok;
result.isCacheable = false; result.isCacheable = false;
} }
else if (auto superGeneric = get<GenericType>(superTy); else if (auto superGeneric = get<GenericType>(superTy); superGeneric && variance == Variance::Contravariant)
FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant)
{ {
bool ok = bindGeneric(env, subTy, superTy); bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok; result.isSubtype = ok;
result.isCacheable = false; result.isCacheable = false;
} }
else if (auto pair = get2<FreeType, FreeType>(subTy, superTy); FFlag::LuauEagerGeneralization2 && pair) else if (auto pair = get2<FreeType, FreeType>(subTy, superTy); FFlag::LuauEagerGeneralization3 && pair)
{ {
// Any two free types are potentially subtypes of one another because // Any two free types are potentially subtypes of one another because
// both of them could be narrowed to never. // both of them could be narrowed to never.
result = {true}; result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
} }
else if (auto superFree = get<FreeType>(superTy); superFree && FFlag::LuauEagerGeneralization2) else if (auto superFree = get<FreeType>(superTy); superFree && FFlag::LuauEagerGeneralization3)
{ {
// Given SubTy <: (LB <: SuperTy <: UB) // Given SubTy <: (LB <: SuperTy <: UB)
// //
@ -704,7 +702,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (result.isSubtype) if (result.isSubtype)
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
} }
else if (auto subFree = get<FreeType>(subTy); subFree && FFlag::LuauEagerGeneralization2) else if (auto subFree = get<FreeType>(subTy); subFree && FFlag::LuauEagerGeneralization3)
{ {
// Given (LB <: SubTy <: UB) <: SuperTy // Given (LB <: SubTy <: UB) <: SuperTy
// //
@ -762,19 +760,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope);
} }
else if (auto subGeneric = get<GenericType>(subTy); !FFlag::LuauSubtypeGenericsAndNegations && subGeneric && variance == Variance::Covariant)
{
bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok;
result.isCacheable = false;
}
else if (auto superGeneric = get<GenericType>(superTy);
!FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant)
{
bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok;
result.isCacheable = false;
}
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
@ -1452,7 +1437,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{ {
SubtypingResult result{true}; SubtypingResult result{true};
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer) if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer)
{ {
@ -1508,7 +1493,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{ {
if (subTable->indexer) if (subTable->indexer)
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope)); result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope));
else if (FFlag::LuauEagerGeneralization2 && subTable->state != TableState::Sealed) else if (FFlag::LuauEagerGeneralization3 && subTable->state != TableState::Sealed)
{ {
// As above, we assume that {| |} <: {T} because the unsealed table // As above, we assume that {| |} <: {T} because the unsealed table
// on the left will eventually gain the necessary indexer. // on the left will eventually gain the necessary indexer.
@ -1620,6 +1605,21 @@ SubtypingResult Subtyping::isCovariantWith(
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns)); result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns));
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
{
if (*subFunction->argTypes == *superFunction->argTypes && *subFunction->retTypes == *superFunction->retTypes)
{
if (superFunction->generics.size() != subFunction->generics.size())
result.andAlso({false}).withError(
TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}}
);
if (superFunction->genericPacks.size() != subFunction->genericPacks.size())
result.andAlso({false}).withError(
TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}}
);
}
}
return result; return result;
} }

View file

@ -13,7 +13,6 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
namespace Luau namespace Luau
@ -297,9 +296,6 @@ TypeId matchLiteralType(
} }
else if (item.kind == AstExprTable::Item::List) else if (item.kind == AstExprTable::Item::List)
{ {
if (!FFlag::LuauBidirectionalInferenceElideAssert)
LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer) if (expectedTableTy->indexer)
{ {
const TypeId* propTy = astTypes->find(item.value); const TypeId* propTy = astTypes->find(item.value);

View file

@ -22,7 +22,6 @@
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
/* /*
* Enables increasing levels of verbosity for Luau type names when stringifying. * Enables increasing levels of verbosity for Luau type names when stringifying.
@ -959,34 +958,21 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit) resultsLength += state.result.name.length();
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit) lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit) if (lengthLimitHit)
break; break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit) if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
{ std::sort(results.begin(), results.end());
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
if (optional && results.size() > 1) if (optional && results.size() > 1)
state.emit("("); state.emit("(");
@ -1049,34 +1035,21 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit) resultsLength += state.result.name.length();
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit) lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit) if (lengthLimitHit)
break; break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit) if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
{ std::sort(results.begin(), results.end());
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
bool first = true; bool first = true;
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty); bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);

View file

@ -23,10 +23,13 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
namespace Luau namespace Luau
{ {
@ -831,7 +834,25 @@ bool areEqual(SeenSet& seen, const TableType& lhs, const TableType& rhs)
if (l->first != r->first) if (l->first != r->first)
return false; return false;
if (!areEqual(seen, *l->second.type(), *r->second.type())) if (FFlag::LuauSolverV2 && FFlag::LuauSubtypingCheckFunctionGenericCounts)
{
if (l->second.readTy && r->second.readTy)
{
if (!areEqual(seen, **l->second.readTy, **r->second.readTy))
return false;
}
else if (l->second.readTy || r->second.readTy)
return false;
if (l->second.writeTy && r->second.writeTy)
{
if (!areEqual(seen, **l->second.writeTy, **r->second.writeTy))
return false;
}
else if (l->second.writeTy || r->second.writeTy)
return false;
}
else if (!areEqual(seen, *l->second.type(), *r->second.type()))
return false; return false;
++l; ++l;
++r; ++r;

View file

@ -30,7 +30,6 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
@ -38,6 +37,7 @@ LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
namespace Luau namespace Luau
{ {
@ -1409,14 +1409,18 @@ void TypeChecker2::visit(AstExprConstantBool* expr)
const TypeId inferredType = lookupType(expr); const TypeId inferredType = lookupType(expr);
NotNull<Scope> scope{findInnermostScope(expr->location)}; NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (FFlag::LuauReportSubtypingErrors) if (FFlag::LuauReportSubtypingErrors)
{ {
if (!isErrorSuppressing(expr->location, inferredType)) if (!isErrorSuppressing(expr->location, inferredType))
{ {
if (!r.isSubtype) if (!r.isSubtype)
reportError(TypeMismatch{inferredType, bestType}, expr->location); reportError(TypeMismatch{inferredType, bestType}, expr->location);
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
{
for (auto& e : r.errors)
e.location = expr->location;
}
reportErrors(r.errors); reportErrors(r.errors);
} }
} }
@ -1447,14 +1451,18 @@ void TypeChecker2::visit(AstExprConstantString* expr)
const TypeId inferredType = lookupType(expr); const TypeId inferredType = lookupType(expr);
NotNull<Scope> scope{findInnermostScope(expr->location)}; NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (FFlag::LuauReportSubtypingErrors) if (FFlag::LuauReportSubtypingErrors)
{ {
if (!isErrorSuppressing(expr->location, inferredType)) if (!isErrorSuppressing(expr->location, inferredType))
{ {
if (!r.isSubtype) if (!r.isSubtype)
reportError(TypeMismatch{inferredType, bestType}, expr->location); reportError(TypeMismatch{inferredType, bestType}, expr->location);
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
{
for (auto& e : r.errors)
e.location = expr->location;
}
reportErrors(r.errors); reportErrors(r.errors);
} }
} }
@ -1533,7 +1541,12 @@ void TypeChecker2::visitCall(AstExprCall* call)
if (FFlag::LuauReportSubtypingErrors) if (FFlag::LuauReportSubtypingErrors)
{ {
if (!isErrorSuppressing(call->location, *selectedOverloadTy)) if (!isErrorSuppressing(call->location, *selectedOverloadTy))
reportErrors(std::move(result.errors)); if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
{
for (auto& e : result.errors)
e.location = call->location;
}
reportErrors(std::move(result.errors));
} }
if (result.normalizationTooComplex) if (result.normalizationTooComplex)
@ -2359,18 +2372,9 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
return builtinTypes->numberType; return builtinTypes->numberType;
case AstExprBinary::Op::Concat: case AstExprBinary::Op::Concat:
{ {
if (FFlag::LuauTypeCheckerAcceptNumberConcats) const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
{ testIsSubtype(leftType, numberOrString, expr->left->location);
const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}}); testIsSubtype(rightType, numberOrString, expr->right->location);
testIsSubtype(leftType, numberOrString, expr->left->location);
testIsSubtype(rightType, numberOrString, expr->right->location);
}
else
{
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
}
return builtinTypes->stringType; return builtinTypes->stringType;
} }
case AstExprBinary::Op::CompareGe: case AstExprBinary::Op::CompareGe:
@ -3113,7 +3117,12 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location
if (FFlag::LuauReportSubtypingErrors) if (FFlag::LuauReportSubtypingErrors)
{ {
if (!isErrorSuppressing(location, subTy)) if (!isErrorSuppressing(location, subTy))
reportErrors(std::move(r.errors)); if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
{
for (auto& e : r.errors)
e.location = location;
}
reportErrors(std::move(r.errors));
} }
if (r.normalizationTooComplex) if (r.normalizationTooComplex)
@ -3133,7 +3142,12 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location
if (FFlag::LuauReportSubtypingErrors) if (FFlag::LuauReportSubtypingErrors)
{ {
if (!isErrorSuppressing(location, subTy)) if (!isErrorSuppressing(location, subTy))
reportErrors(std::move(r.errors)); if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
{
for (auto& e : r.errors)
e.location = location;
}
reportErrors(std::move(r.errors));
} }
if (r.normalizationTooComplex) if (r.normalizationTooComplex)

View file

@ -48,16 +48,14 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
namespace Luau namespace Luau
{ {
@ -285,7 +283,7 @@ struct TypeFunctionReducer
} }
else if (is<GenericType>(ty)) else if (is<GenericType>(ty))
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
return SkipTestResult::Generic; return SkipTestResult::Generic;
else else
return SkipTestResult::Irreducible; return SkipTestResult::Irreducible;
@ -307,7 +305,7 @@ struct TypeFunctionReducer
} }
else if (is<GenericTypePack>(ty)) else if (is<GenericTypePack>(ty))
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
return SkipTestResult::Generic; return SkipTestResult::Generic;
else else
return SkipTestResult::Irreducible; return SkipTestResult::Irreducible;
@ -1261,7 +1259,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (isPending(operandTy, ctx->solver)) if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
operandTy = follow(operandTy); operandTy = follow(operandTy);
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy); std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
@ -1858,7 +1856,7 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
return {rhsTy, Reduction::MaybeOk, {}, {}}; return {rhsTy, Reduction::MaybeOk, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not // check to see if both operand types are resolved enough, and wait to reduce if not
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy)) if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
@ -1905,7 +1903,7 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (lhsTy == instance || rhsTy == instance) if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy)) if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
@ -1938,16 +1936,6 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType); emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType);
else if (rhsFree && isNumber(lhsTy)) else if (rhsFree && isNumber(lhsTy))
emplaceType<BoundType>(asMutable(rhsTy), ctx->builtins->numberType); emplaceType<BoundType>(asMutable(rhsTy), ctx->builtins->numberType);
else if (!FFlag::LuauNoMoreInjectiveTypeFunctions && lhsFree && ctx->normalizer->isInhabited(rhsTy) != NormalizationResult::False)
{
auto c1 = ctx->pushConstraint(EqualityConstraint{lhsTy, rhsTy});
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
}
else if (!FFlag::LuauNoMoreInjectiveTypeFunctions && rhsFree && ctx->normalizer->isInhabited(lhsTy) != NormalizationResult::False)
{
auto c1 = ctx->pushConstraint(EqualityConstraint{rhsTy, lhsTy});
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
}
} }
// The above might have caused the operand types to be rebound, we need to follow them again // The above might have caused the operand types to be rebound, we need to follow them again
@ -2279,7 +2267,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
for (size_t i = 1; i < typeParams.size(); i++) for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(i))); discriminantTypes.push_back(follow(typeParams.at(i)));
const bool targetIsPending = FFlag::LuauEagerGeneralization2 ? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy) const bool targetIsPending = FFlag::LuauEagerGeneralization3 ? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy)
: isPending(targetTy, ctx->solver); : isPending(targetTy, ctx->solver);
// check to see if both operand types are resolved enough, and wait to reduce if not // check to see if both operand types are resolved enough, and wait to reduce if not
@ -2294,16 +2282,13 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
} }
} }
if (FFlag::LuauRefineWaitForBlockedTypesInTarget) // If we have a blocked type in the target, we *could* potentially
{ // refine it, but more likely we end up with some type explosion in
// If we have a blocked type in the target, we *could* potentially // normalization.
// refine it, but more likely we end up with some type explosion in FindRefinementBlockers frb;
// normalization. frb.traverse(targetTy);
FindRefinementBlockers frb; if (!frb.found.empty())
frb.traverse(targetTy); return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
if (!frb.found.empty())
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
}
// Refine a target type and a discriminant one at a time. // Refine a target type and a discriminant one at a time.
// Returns result : TypeId, toBlockOn : vector<TypeId> // Returns result : TypeId, toBlockOn : vector<TypeId>
@ -2353,59 +2338,43 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
} }
} }
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect) // If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
// We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively
// NOTE: It would be nice to be able to do a simple intersection for something like:
//
// { a: A, b: B, ... } & { x: X }
//
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{ {
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type. if (FFlag::LuauEagerGeneralization3)
// We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively
// NOTE: It would be nice to be able to do a simple intersection for something like:
//
// { a: A, b: B, ... } & { x: X }
//
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{ {
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); // Simplification considers free and generic types to be
if (FFlag::LuauEagerGeneralization2) // 'blocking', but that's not suitable for refine<>.
//
// If we are only blocked on those types, we consider
// the simplification a success and reduce.
if (std::all_of(
begin(result.blockedTypes),
end(result.blockedTypes),
[](auto&& v)
{
return is<FreeType, GenericType>(follow(v));
}
))
{ {
// Simplification considers free and generic types to be return {result.result, {}};
// 'blocking', but that's not suitable for refine<>.
//
// If we are only blocked on those types, we consider
// the simplification a success and reduce.
if (std::all_of(
begin(result.blockedTypes),
end(result.blockedTypes),
[](auto&& v)
{
return is<FreeType, GenericType>(follow(v));
}
))
{
return {result.result, {}};
}
else
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
} }
else else
{ return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
if (!result.blockedTypes.empty())
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
}
return {result.result, {}};
} }
} else
else
{
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
if (get<TableType>(target))
{ {
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (!result.blockedTypes.empty()) if (!result.blockedTypes.empty())
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
return {result.result, {}};
} }
return {result.result, {}};
} }
@ -3333,7 +3302,7 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
{ {
targetTy = follow(targetTy); targetTy = follow(targetTy);
std::optional<TypeId> metatable = std::nullopt; std::optional<TypeId> result = std::nullopt;
bool erroneous = true; bool erroneous = true;
if (auto table = get<TableType>(targetTy)) if (auto table = get<TableType>(targetTy))
@ -3341,19 +3310,19 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
if (auto mt = get<MetatableType>(targetTy)) if (auto mt = get<MetatableType>(targetTy))
{ {
metatable = mt->metatable; result = mt->metatable;
erroneous = false; erroneous = false;
} }
if (auto clazz = get<ExternType>(targetTy)) if (auto clazz = get<ExternType>(targetTy))
{ {
metatable = clazz->metatable; result = clazz->metatable;
erroneous = false; erroneous = false;
} }
if (auto primitive = get<PrimitiveType>(targetTy)) if (auto primitive = get<PrimitiveType>(targetTy))
{ {
metatable = primitive->metatable; result = primitive->metatable;
erroneous = false; erroneous = false;
} }
@ -3362,11 +3331,18 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
if (get<StringSingleton>(singleton)) if (get<StringSingleton>(singleton))
{ {
auto primitiveString = get<PrimitiveType>(ctx->builtins->stringType); auto primitiveString = get<PrimitiveType>(ctx->builtins->stringType);
metatable = primitiveString->metatable; result = primitiveString->metatable;
} }
erroneous = false; erroneous = false;
} }
if (FFlag::LuauUpdateGetMetatableTypeSignature && get<AnyType>(targetTy))
{
// getmetatable<any> ~ any
result = targetTy;
erroneous = false;
}
if (erroneous) if (erroneous)
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3379,8 +3355,8 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
if (metatableMetamethod) if (metatableMetamethod)
return {metatableMetamethod, Reduction::MaybeOk, {}, {}}; return {metatableMetamethod, Reduction::MaybeOk, {}, {}};
if (metatable) if (result)
return {metatable, Reduction::MaybeOk, {}, {}}; return {result, Reduction::MaybeOk, {}, {}};
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}}; return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
} }
@ -3428,16 +3404,34 @@ TypeFunctionReductionResult<TypeId> getmetatableTypeFunction(
std::vector<TypeId> parts{}; std::vector<TypeId> parts{};
parts.reserve(it->parts.size()); parts.reserve(it->parts.size());
bool erroredWithUnknown = false;
for (auto part : it->parts) for (auto part : it->parts)
{ {
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(part, location, ctx); TypeFunctionReductionResult<TypeId> result = getmetatableHelper(part, location, ctx);
if (!result.result) if (!result.result)
return result; {
// Don't immediately error if part is unknown
if (FFlag::LuauUpdateGetMetatableTypeSignature && get<UnknownType>(follow(part)))
{
erroredWithUnknown = true;
continue;
}
else
return result;
}
parts.push_back(*result.result); parts.push_back(*result.result);
} }
// If all parts are unknown, return erroneous reduction
if (FFlag::LuauUpdateGetMetatableTypeSignature && erroredWithUnknown && parts.empty())
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (FFlag::LuauUpdateGetMetatableTypeSignature && parts.size() == 1)
return {parts.front(), Reduction::MaybeOk, {}, {}};
return {ctx->arena->addType(IntersectionType{std::move(parts)}), Reduction::MaybeOk, {}, {}}; return {ctx->arena->addType(IntersectionType{std::move(parts)}), Reduction::MaybeOk, {}, {}};
} }
@ -3495,7 +3489,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
, ltFunc{"lt", ltTypeFunction} , ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction} , leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqTypeFunction} , eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization2} , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization3}
, singletonFunc{"singleton", singletonTypeFunction} , singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction} , unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction} , intersectFunc{"intersect", intersectTypeFunction}

View file

@ -150,4 +150,11 @@ bool TypeIds::operator==(const TypeIds& there) const
return true; return true;
} }
std::vector<TypeId> TypeIds::take()
{
hash = 0;
types.clear();
return std::move(order);
}
} }

View file

@ -12,7 +12,7 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs) LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs)
namespace Luau namespace Luau
@ -306,7 +306,7 @@ TypePack extendTypePack(
TypePack newPack; TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail); trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -588,7 +588,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{ {
LUAU_ASSERT(tp); LUAU_ASSERT(tp);
if (!FFlag::LuauEagerGeneralization2) if (!FFlag::LuauEagerGeneralization3)
return; return;
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
@ -695,5 +695,14 @@ bool isRecord(const AstExprTable::Item& item)
return false; return false;
} }
AstExpr* unwrapGroup(AstExpr* expr)
{
while (auto group = expr->as<AstExprGroup>())
expr = group->expr;
return expr;
}
} // namespace Luau } // namespace Luau

View file

@ -19,7 +19,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
namespace Luau namespace Luau
{ {
@ -329,9 +329,9 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
for (TypePackId genericPack : subFn->genericPacks) for (TypePackId genericPack : subFn->genericPacks)
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
genericPack = follow(genericPack); genericPack = follow(genericPack);
// TODO: Clip this follow() with LuauEagerGeneralization2 // TODO: Clip this follow() with LuauEagerGeneralization2
@ -465,7 +465,7 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
{ {
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType); result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType); result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// FIXME: We can probably do something more efficient here. // FIXME: We can probably do something more efficient here.
result &= unify(superTable->indexer->indexType, subTable->indexer->indexType); result &= unify(superTable->indexer->indexType, subTable->indexer->indexType);

View file

@ -12,10 +12,11 @@ inline bool isAnalysisFlagExperimental(const char* flag)
// or critical bugs that are found after the code has been submitted. This list is intended _only_ for flags that affect // or critical bugs that are found after the code has been submitted. This list is intended _only_ for flags that affect
// Luau's type checking. Flags that may change runtime behavior (e.g.: parser or VM flags) are not appropriate for this list. // Luau's type checking. Flags that may change runtime behavior (e.g.: parser or VM flags) are not appropriate for this list.
static const char* const kList[] = { static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny2", // takes telemetry data for usage of any types "StudioReportLuauAny2", // takes telemetry data for usage of any types
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly "LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
"LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly
"LuauSolverV2", "LuauSolverV2",
// makes sure we always have at least one entry // makes sure we always have at least one entry
nullptr, nullptr,

View file

@ -185,7 +185,6 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/DataFlowGraph.h Analysis/include/Luau/DataFlowGraph.h
Analysis/include/Luau/DcrLogger.h Analysis/include/Luau/DcrLogger.h
Analysis/include/Luau/Def.h Analysis/include/Luau/Def.h
Analysis/include/Luau/Differ.h
Analysis/include/Luau/Documentation.h Analysis/include/Luau/Documentation.h
Analysis/include/Luau/Error.h Analysis/include/Luau/Error.h
Analysis/include/Luau/EqSatSimplification.h Analysis/include/Luau/EqSatSimplification.h
@ -265,7 +264,6 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/DataFlowGraph.cpp Analysis/src/DataFlowGraph.cpp
Analysis/src/DcrLogger.cpp Analysis/src/DcrLogger.cpp
Analysis/src/Def.cpp Analysis/src/Def.cpp
Analysis/src/Differ.cpp
Analysis/src/EmbeddedBuiltinDefinitions.cpp Analysis/src/EmbeddedBuiltinDefinitions.cpp
Analysis/src/Error.cpp Analysis/src/Error.cpp
Analysis/src/EqSatSimplification.cpp Analysis/src/EqSatSimplification.cpp
@ -464,9 +462,6 @@ if(TARGET Luau.UnitTest)
tests/CostModel.test.cpp tests/CostModel.test.cpp
tests/DataFlowGraph.test.cpp tests/DataFlowGraph.test.cpp
tests/DenseHash.test.cpp tests/DenseHash.test.cpp
tests/DiffAsserts.cpp
tests/DiffAsserts.h
tests/Differ.test.cpp
tests/EqSat.language.test.cpp tests/EqSat.language.test.cpp
tests/EqSat.propositional.test.cpp tests/EqSat.propositional.test.cpp
tests/EqSat.slice.test.cpp tests/EqSat.slice.test.cpp

View file

@ -14,6 +14,8 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauGcAgainstOom, false)
/* /*
* Luau uses an incremental non-generational non-moving mark&sweep garbage collector. * Luau uses an incremental non-generational non-moving mark&sweep garbage collector.
* *
@ -446,6 +448,21 @@ static void shrinkstack(lua_State* L)
condhardstacktests(luaD_reallocstack(L, s_used, 0)); condhardstacktests(luaD_reallocstack(L, s_used, 0));
} }
static void shrinkstackprotected(lua_State* L)
{
struct CallContext
{
static void run(lua_State* L, void* ud)
{
shrinkstack(L);
}
} ctx = {};
// the resize call can fail on exception, in which case we will continue with original size
int status = luaD_rawrunprotected(L, &CallContext::run, &ctx);
LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM);
}
/* /*
** traverse one gray object, turning it to black. ** traverse one gray object, turning it to black.
** Returns `quantity' traversed. ** Returns `quantity' traversed.
@ -497,7 +514,12 @@ static size_t propagatemark(global_State* g)
// we could shrink stack at any time but we opt to do it during initial mark to do that just once per cycle // we could shrink stack at any time but we opt to do it during initial mark to do that just once per cycle
if (g->gcstate == GCSpropagate) if (g->gcstate == GCSpropagate)
shrinkstack(th); {
if (DFFlag::LuauGcAgainstOom)
shrinkstackprotected(th);
else
shrinkstack(th);
}
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
} }
@ -545,6 +567,26 @@ static int isobjcleared(GCObject* o)
#define iscleared(o) (iscollectable(o) && isobjcleared(gcvalue(o))) #define iscleared(o) (iscollectable(o) && isobjcleared(gcvalue(o)))
static void tableresizeprotected(lua_State* L, LuaTable* t, int nhsize)
{
struct CallContext
{
LuaTable* t;
int nhsize;
static void run(lua_State* L, void* ud)
{
CallContext* ctx = (CallContext*)ud;
luaH_resizehash(L, ctx->t, ctx->nhsize);
}
} ctx = {t, nhsize};
// the resize call can fail on exception, in which case we will continue with original size
int status = luaD_rawrunprotected(L, &CallContext::run, &ctx);
LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM);
}
/* /*
** clear collected entries from weaktables ** clear collected entries from weaktables
*/ */
@ -592,7 +634,12 @@ static size_t cleartable(lua_State* L, GCObject* l)
{ {
// shrink at 37.5% occupancy // shrink at 37.5% occupancy
if (activevalues < sizenode(h) * 3 / 8) if (activevalues < sizenode(h) * 3 / 8)
luaH_resizehash(L, h, activevalues); {
if (DFFlag::LuauGcAgainstOom)
tableresizeprotected(L, h, activevalues);
else
luaH_resizehash(L, h, activevalues);
}
} }
} }
@ -635,12 +682,36 @@ static void freeobj(lua_State* L, GCObject* o, lua_Page* page)
} }
} }
static void stringresizeprotected(lua_State* L, int newsize)
{
struct CallContext
{
int newsize;
static void run(lua_State* L, void* ud)
{
CallContext* ctx = (CallContext*)ud;
luaS_resize(L, ctx->newsize);
}
} ctx = {newsize};
// the resize call can fail on exception, in which case we will continue with original size
int status = luaD_rawrunprotected(L, &CallContext::run, &ctx);
LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM);
}
static void shrinkbuffers(lua_State* L) static void shrinkbuffers(lua_State* L)
{ {
global_State* g = L->global; global_State* g = L->global;
// check size of string hash // check size of string hash
if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2) if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2)
luaS_resize(L, g->strt.size / 2); // table is too big {
if (DFFlag::LuauGcAgainstOom)
stringresizeprotected(L, g->strt.size / 2); // table is too big
else
luaS_resize(L, g->strt.size / 2); // table is too big
}
} }
static void shrinkbuffersfull(lua_State* L) static void shrinkbuffersfull(lua_State* L)
@ -651,7 +722,12 @@ static void shrinkbuffersfull(lua_State* L)
while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2) while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2)
hashsize /= 2; hashsize /= 2;
if (hashsize != g->strt.size) if (hashsize != g->strt.size)
luaS_resize(L, hashsize); // table is too big {
if (DFFlag::LuauGcAgainstOom)
stringresizeprotected(L, hashsize); // table is too big
else
luaS_resize(L, hashsize); // table is too big
}
} }
static bool deletegco(void* context, lua_Page* page, GCObject* gco) static bool deletegco(void* context, lua_Page* page, GCObject* gco)

View file

@ -8,8 +8,6 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStringFormatFixC, false)
// macro to `unsign' a character // macro to `unsign' a character
#define uchar(c) ((unsigned char)(c)) #define uchar(c) ((unsigned char)(c))
@ -1001,17 +999,9 @@ static int str_format(lua_State* L)
{ {
case 'c': case 'c':
{ {
if (DFFlag::LuauStringFormatFixC) int count = snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
{ luaL_addlstring(&b, buff, count);
int count = snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg)); continue; // skip the 'luaL_addlstring' at the end
luaL_addlstring(&b, buff, count);
continue; // skip the 'luaL_addlstring' at the end
}
else
{
snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
break;
}
} }
case 'd': case 'd':
case 'i': case 'i':

View file

@ -19,7 +19,7 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauExpectedTypeVisitor) LUAU_FASTFLAG(LuauExpectedTypeVisitor)
using namespace Luau; using namespace Luau;
@ -4468,7 +4468,7 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
auto ac = autocomplete('1'); auto ac = autocomplete('1');
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2) if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization3)
{ {
// This `if` statement is because `LuauEagerGeneralization2` // This `if` statement is because `LuauEagerGeneralization2`
// sets some flags // sets some flags

View file

@ -36,11 +36,11 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
LUAU_FASTFLAG(LuauYieldableContinuations) LUAU_FASTFLAG(LuauYieldableContinuations)
LUAU_FASTFLAG(LuauCurrentLineBounds) LUAU_FASTFLAG(LuauCurrentLineBounds)
LUAU_FASTFLAG(LuauLoadNoOomThrow) LUAU_FASTFLAG(LuauLoadNoOomThrow)
LUAU_FASTFLAG(LuauHeapNameDetails) LUAU_FASTFLAG(LuauHeapNameDetails)
LUAU_DYNAMIC_FASTFLAG(LuauGcAgainstOom)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -713,8 +713,6 @@ TEST_CASE("Clear")
TEST_CASE("Strings") TEST_CASE("Strings")
{ {
ScopedFastFlag luauStringFormatFixC{DFFlag::LuauStringFormatFixC, true};
runConformance("strings.luau"); runConformance("strings.luau");
} }
@ -768,9 +766,48 @@ TEST_CASE("Attrib")
runConformance("attrib.luau"); runConformance("attrib.luau");
} }
static bool blockableReallocAllowed = true;
static void* blockableRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
{
if (nsize == 0)
{
free(ptr);
return nullptr;
}
else
{
if (!blockableReallocAllowed)
return nullptr;
return realloc(ptr, nsize);
}
}
TEST_CASE("GC") TEST_CASE("GC")
{ {
runConformance("gc.luau"); ScopedFastFlag luauGcAgainstOom{DFFlag::LuauGcAgainstOom, true};
runConformance(
"gc.luau",
[](lua_State* L)
{
lua_pushcclosurek(
L,
[](lua_State* L)
{
blockableReallocAllowed = !luaL_checkboolean(L, 1);
return 0;
},
"setblockallocations",
0,
nullptr
);
lua_setglobal(L, "setblockallocations");
},
nullptr,
lua_newstate(blockableRealloc, nullptr)
);
} }
TEST_CASE("Bitwise") TEST_CASE("Bitwise")

View file

@ -1,31 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "DiffAsserts.h"
#include <string>
namespace Luau
{
std::string toString(const DifferResult& result)
{
if (result.diffError)
return result.diffError->toString();
else
return "<no diff>";
}
template<>
std::string diff<TypeId, TypeId>(TypeId l, TypeId r)
{
return toString(diff(l, r));
}
template<>
std::string diff<const Type&, const Type&>(const Type& l, const Type& r)
{
return toString(diff(&l, &r));
}
} // namespace Luau

View file

@ -1,46 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Differ.h"
#include "Luau/TypeFwd.h"
#include "doctest.h"
#include <string>
#include <type_traits>
namespace Luau
{
std::string toString(const DifferResult& result);
template<typename L, typename R>
std::string diff(L, R)
{
return "<undiffable>";
}
template<>
std::string diff<TypeId, TypeId>(TypeId l, TypeId r);
template<>
std::string diff<const Type&, const Type&>(const Type& l, const Type& r);
} // namespace Luau
// Note: the do-while blocks in the macros below is to scope the INFO block to
// only that assertion.
#define CHECK_EQ_DIFF(l, r) \
do \
{ \
INFO("Left and right values were not equal: ", diff(l, r)); \
CHECK_EQ(l, r); \
} while (false);
#define REQUIRE_EQ_DIFF(l, r) \
do \
{ \
INFO("Left and right values were not equal: ", diff(l, r)); \
REQUIRE_EQ(l, r); \
} while (false);

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,6 @@
#pragma once #pragma once
#include "Luau/Config.h" #include "Luau/Config.h"
#include "Luau/Differ.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/FileResolver.h" #include "Luau/FileResolver.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
@ -30,6 +29,7 @@ LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauTypeFunOptional) LUAU_FASTFLAG(LuauTypeFunOptional)
LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests}; #define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
@ -150,6 +150,7 @@ struct Fixture
// In that case, flag can be forced to 'true' using the example below: // In that case, flag can be forced to 'true' using the example below:
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true}; // ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};
ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true}; ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true};
ScopedFastFlag sff_LuauUpdateGetMetatableTypeSignature{FFlag::LuauUpdateGetMetatableTypeSignature, true};
// Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it. // Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it.
// This is useful for tracking down violations of Luau's memory model. // This is useful for tracking down violations of Luau's memory model.
@ -234,84 +235,6 @@ const E* findError(const CheckResult& result)
return nullptr; return nullptr;
} }
template<typename BaseFixture>
struct DifferFixtureGeneric : BaseFixture
{
std::string normalizeWhitespace(std::string msg)
{
std::string normalizedMsg = "";
bool wasWhitespace = true;
for (char c : msg)
{
bool isWhitespace = c == ' ' || c == '\n';
if (wasWhitespace && isWhitespace)
continue;
normalizedMsg += isWhitespace ? ' ' : c;
wasWhitespace = isWhitespace;
}
if (wasWhitespace)
normalizedMsg.pop_back();
return normalizedMsg;
}
void compareNe(TypeId left, TypeId right, const std::string& expectedMessage, bool multiLine)
{
compareNe(left, std::nullopt, right, std::nullopt, expectedMessage, multiLine);
}
void compareNe(
TypeId left,
std::optional<std::string> symbolLeft,
TypeId right,
std::optional<std::string> symbolRight,
const std::string& expectedMessage,
bool multiLine
)
{
DifferResult diffRes = diffWithSymbols(left, right, symbolLeft, symbolRight);
REQUIRE_MESSAGE(diffRes.diffError.has_value(), "Differ did not report type error, even though types are unequal");
std::string diffMessage = diffRes.diffError->toString(multiLine);
CHECK_EQ(expectedMessage, diffMessage);
}
void compareTypesNe(
const std::string& leftSymbol,
const std::string& rightSymbol,
const std::string& expectedMessage,
bool forwardSymbol = false,
bool multiLine = false
)
{
if (forwardSymbol)
{
compareNe(
BaseFixture::requireType(leftSymbol), leftSymbol, BaseFixture::requireType(rightSymbol), rightSymbol, expectedMessage, multiLine
);
}
else
{
compareNe(
BaseFixture::requireType(leftSymbol), std::nullopt, BaseFixture::requireType(rightSymbol), std::nullopt, expectedMessage, multiLine
);
}
}
void compareEq(TypeId left, TypeId right)
{
DifferResult diffRes = diff(left, right);
CHECK(!diffRes.diffError);
if (diffRes.diffError)
INFO(diffRes.diffError->toString());
}
void compareTypesEq(const std::string& leftSymbol, const std::string& rightSymbol)
{
compareEq(BaseFixture::requireType(leftSymbol), BaseFixture::requireType(rightSymbol));
}
};
using DifferFixture = DifferFixtureGeneric<Fixture>;
using DifferFixtureWithBuiltins = DifferFixtureGeneric<BuiltinsFixture>;
} // namespace Luau } // namespace Luau
#define LUAU_REQUIRE_ERRORS(result) \ #define LUAU_REQUIRE_ERRORS(result) \

View file

@ -3406,7 +3406,6 @@ local foo = 8)");
CHECK(*pos == Position{2, 0}); CHECK(*pos == Position{2, 0});
} }
#if 0
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalReturn_assert") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalReturn_assert")
{ {
const std::string source = R"()"; const std::string source = R"()";
@ -3452,7 +3451,6 @@ return target(bar)";
} }
); );
} }
#endif
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_finished_defining") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_finished_defining")
{ {

View file

@ -15,9 +15,9 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
TEST_SUITE_BEGIN("Generalization"); TEST_SUITE_BEGIN("Generalization");
@ -227,7 +227,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a")
TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})") TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})")
{ {
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
TableType tt; TableType tt;
tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType}; tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType};
@ -261,7 +261,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?")
TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()")
{ {
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
auto [aTy, aFree] = freshType(); auto [aTy, aFree] = freshType();
auto [bTy, bFree] = freshType(); auto [bTy, bFree] = freshType();
@ -342,7 +342,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type") TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type")
{ {
ScopedFastFlag sffs[] = {{FFlag::DebugLuauForbidInternalTypes, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}}; ScopedFastFlag _{FFlag::DebugLuauForbidInternalTypes, true};
// This test case should just not assert // This test case should just not assert
CheckResult result = check(R"( CheckResult result = check(R"(
@ -374,4 +374,93 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type")
)"); )");
} }
TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauAvoidGenericsLeakingDuringFunctionCallCheck, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local func: <T>(T, (T) -> ()) -> () = nil :: any
func({}, function(obj)
local _ = obj
end)
)"));
// `unknown` is correct here
// - The lambda given can be generalized to `(unknown) -> ()`
// - We can substitute the `T` in `func` for either `{}` or `unknown` and
// still have a well typed program.
// We *probably* can do a better job bidirectionally inferring the types.
CHECK_EQ("unknown", toString(requireTypeAtPosition(Position{3, 23})));
}
TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback_2")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauAvoidGenericsLeakingDuringFunctionCallCheck, true},
};
// FIXME: CLI-156389: this is clearly wrong, but also predates this PR.
LUAU_REQUIRE_NO_ERRORS(check(R"(
local func: <T>(T, (T) -> ()) -> () = nil :: any
local foobar: (number) -> () = nil :: any
func({}, function(obj)
foobar(obj)
end)
)"));
}
TEST_CASE_FIXTURE(Fixture, "generic_argument_with_singleton_oss_1808")
{
ScopedFastFlag _{FFlag::LuauAvoidGenericsLeakingDuringFunctionCallCheck, true};
// All we care about here is that this has no errors, and we correctly
// infer that the `false` literal should be typed as `false`.
LUAU_REQUIRE_NO_ERRORS(check(R"(
local function test<T>(value: false | (T) -> T)
return value
end
test(false)
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "avoid_cross_module_mutation_in_bidirectional_inference")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauAvoidGenericsLeakingDuringFunctionCallCheck, true},
{FFlag::LuauEagerGeneralization3, true},
};
fileResolver.source["Module/ListFns"] = R"(
local mod = {}
function mod.findWhere(list, predicate): number?
for i = 1, #list do
if predicate(list[i], i) then
return i
end
end
return nil
end
return mod
)";
fileResolver.source["Module/B"] = R"(
local funs = require(script.Parent.ListFns)
local accessories = funs.findWhere(getList(), function(accessory)
return accessory.AccessoryType ~= accessoryTypeEnum
end)
return {}
)";
CheckResult result = frontend.check("Module/ListFns");
auto modListFns = frontend.moduleResolver.getModule("Module/ListFns");
freeze(modListFns->interfaceTypes);
freeze(modListFns->internalTypes);
LUAU_REQUIRE_NO_ERRORS(result);
CheckResult result2 = frontend.check("Module/B");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -8,13 +8,13 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauEagerGeneralization2); LUAU_FASTFLAG(LuauEagerGeneralization3);
TEST_SUITE_BEGIN("InferPolarity"); TEST_SUITE_BEGIN("InferPolarity");
TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }") TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }")
{ {
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
TypeArena arena; TypeArena arena;
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack); ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack);

View file

@ -10,7 +10,7 @@
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LintRedundantNativeAttribute); LUAU_FASTFLAG(LintRedundantNativeAttribute);
LUAU_FASTFLAG(LuauDeprecatedAttribute); LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauEagerGeneralization2); LUAU_FASTFLAG(LuauEagerGeneralization3);
using namespace Luau; using namespace Luau;
@ -1941,9 +1941,6 @@ print(foo:bar(2.0))
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations") TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
{ {
// FIXME: For now this flag causes a stack overflow on Windows.
ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false};
LintResult result = lint(R"( LintResult result = lint(R"(
local t = {} local t = {}
local tt = {} local tt = {}

View file

@ -14,14 +14,9 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
using namespace Luau; using namespace Luau;
@ -1071,6 +1066,26 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
CHECK("'a & (false?)" == toString(result)); CHECK("'a & (false?)" == toString(result));
} }
TEST_CASE_FIXTURE(NormalizeFixture, "free_type_intersection_ordering")
{
ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, // Affects stringification of free types.
{FFlag::LuauNormalizationReorderFreeTypeIntersect, true},
};
TypeId freeTy = arena.freshType(builtinTypes, &globalScope);
TypeId orderA = arena.addType(IntersectionType{{freeTy, builtinTypes->stringType}});
auto normA = normalizer.normalize(orderA);
REQUIRE(normA);
CHECK_EQ("'a & string", toString(normalizer.typeFromNormal(*normA)));
TypeId orderB = arena.addType(IntersectionType{{builtinTypes->stringType, freeTy}});
auto normB = normalizer.normalize(orderB);
REQUIRE(normB);
// Prior to LuauNormalizationReorderFreeTypeIntersect this became `never` :skull:
CHECK_EQ("'a & string", toString(normalizer.typeFromNormal(*normB)));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow") TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow")
{ {
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
@ -1200,13 +1215,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauRefineWaitForBlockedTypesInTarget, true}, {FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauSimplifyOutOfLine, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::LuauEagerGeneralization2, true}
}; };
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};

View file

@ -16,8 +16,7 @@
#include <initializer_list> #include <initializer_list>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization2)
using namespace Luau; using namespace Luau;
@ -1619,8 +1618,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings")
TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation") TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
{ {
ScopedFastFlag sff{FFlag::LuauSubtypeGenericsAndNegations, true};
// <A, B>(x: A, y: B) -> (A & ~(false?)) | B // <A, B>(x: A, y: B) -> (A & ~(false?)) | B
// (~(false?), ~(false?)) -> (~(false?) & ~(false?)) | ~(false?) // (~(false?), ~(false?)) -> (~(false?) & ~(false?)) | ~(false?)
@ -1644,7 +1641,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes")
{ {
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
TypeId argTy = arena.freshType(builtinTypes, moduleScope.get()); TypeId argTy = arena.freshType(builtinTypes, moduleScope.get());
FreeType* freeArg = getMutable<FreeType>(argTy); FreeType* freeArg = getMutable<FreeType>(argTy);

View file

@ -14,9 +14,8 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs) LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs)
@ -159,7 +158,7 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions") TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
{ {
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
@ -1652,7 +1651,8 @@ type foo<T> = { a: add<T, T>, b : add<T, T> }
TEST_CASE_FIXTURE(BuiltinsFixture, "len_typefun_on_metatable") TEST_CASE_FIXTURE(BuiltinsFixture, "len_typefun_on_metatable")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
local t = setmetatable({}, { __mode = "v" }) local t = setmetatable({}, { __mode = "v" })
@ -1669,7 +1669,6 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "has_prop_on_irreducible_type_function") TEST_CASE_FIXTURE(BuiltinsFixture, "has_prop_on_irreducible_type_function")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauHasPropProperBlock{FFlag::LuauHasPropProperBlock, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local test = "a" + "b" local test = "a" + "b"
@ -1725,7 +1724,7 @@ struct TFFixture
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
const ScopedFastFlag sff[1] = { const ScopedFastFlag sff[1] = {
{FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauEagerGeneralization3, true},
}; };
BuiltinTypeFunctions builtinTypeFunctions; BuiltinTypeFunctions builtinTypeFunctions;

View file

@ -9,7 +9,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
@ -409,7 +409,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works_on_unions")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -458,7 +460,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -649,7 +653,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -892,7 +898,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_complex_cyclic_serialization_works")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type function errors_if_string(arg) type function errors_if_string(arg)
@ -913,7 +920,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_call_metamethod") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_call_metamethod")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type function hello(arg) type function hello(arg)
@ -1098,7 +1106,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type function foo(x) type function foo(x)
@ -1112,7 +1121,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1164,7 +1175,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1211,7 +1224,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recovery_no_upvalues")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_follow") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_follow")
{ {
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type t0 = any type t0 = any
@ -1226,7 +1240,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_follow")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1251,7 +1267,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer")
TEST_CASE_FIXTURE(BuiltinsFixture, "no_type_methods_on_types") TEST_CASE_FIXTURE(BuiltinsFixture, "no_type_methods_on_types")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type function test(x) type function test(x)
@ -1377,7 +1394,8 @@ local a: foo<> = "a"
TEST_CASE_FIXTURE(BuiltinsFixture, "implicit_export") TEST_CASE_FIXTURE(BuiltinsFixture, "implicit_export")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
type function concat(a: type, b: type) type function concat(a: type, b: type)
@ -1430,7 +1448,8 @@ local a = test()
TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export") TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
export type function concat(a: type, b: type) export type function concat(a: type, b: type)
@ -1478,7 +1497,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error")
TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_error") TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_error")
{ {
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type function t0(a) type function t0(a)
@ -1665,7 +1685,8 @@ local function ok(idx: pass<test>): <T>(T, T) -> (T) return idx end
TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_3") TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_3")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type function pass() type function pass()
@ -1968,7 +1989,10 @@ local function ok(idx: pass<test>): (number, ...string) -> (string, ...number) r
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::DebugLuauEqSatSimplification, true}}; if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sffs[] = {{FFlag::DebugLuauEqSatSimplification, true}};
CheckResult _ = check(R"( CheckResult _ = check(R"(
type function t0(a) type function t0(a)
@ -1988,7 +2012,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// FIXME: CLI-151985 // FIXME: CLI-151985
// This test breaks because we can't see that eq<type?, b> is already fully reduced. // This test breaks because we can't see that eq<type?, b> is already fully reduced.
@ -2011,7 +2035,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// FIXME: CLI-151985 // FIXME: CLI-151985
// This test breaks because we can't see that eq<type?, b> is already fully reduced. // This test breaks because we can't see that eq<type?, b> is already fully reduced.

View file

@ -11,8 +11,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauStringFormatImprovements)
TEST_SUITE_BEGIN("BuiltinTests"); TEST_SUITE_BEGIN("BuiltinTests");
@ -459,9 +460,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2) if (FFlag::LuauSolverV2)
CHECK("{ [number]: string | string | string, n: number }" == toString(requireType("t")));
else if (FFlag::LuauSolverV2)
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t"))); CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));
else else
CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t"))); CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t")));
@ -1665,4 +1664,57 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any_2")
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
ScopedFastFlag sff{FFlag::LuauStringFormatImprovements, true};
CheckResult result = check(R"(
local fmt = "Hello, %s!" :: any
local x = "world" :: any
print(string.format(fmt, x))
print(string.format(fmt, "hello"))
print(string.format(fmt, 5)) -- unchecked because the format string is `any`!
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_singleton_types")
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
ScopedFastFlag sff{FFlag::LuauStringFormatImprovements, true};
CheckResult result = check(R"(
local fmt: "Hello, %s!" = "Hello, %s!"
print(string.format(fmt, "hello"))
print(string.format(fmt, 5)) -- should still produce an error since the expected type is `string`!
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(tm->wantedType, builtinTypes->stringType);
CHECK_EQ(tm->givenType, builtinTypes->numberType);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "better_string_format_error_when_format_string_is_dynamic")
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
ScopedFastFlag sff{FFlag::LuauStringFormatImprovements, true};
CheckResult result = check(R"(
local fmt: string = "Hello, %s!"
print(string.format(fmt, "hello"))
print(string.format(fmt :: any, "hello")) -- no error
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
"We cannot statically check the type of `string.format` when called with a format string that is not statically known.\n"
"If you'd like to use an unchecked `string.format` call, you can cast the format string to `any` using `:: any`.",
toString(result.errors[0])
);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -10,7 +10,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
TEST_SUITE_BEGIN("DefinitionTests"); TEST_SUITE_BEGIN("DefinitionTests");
@ -558,7 +558,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauSimplifyOutOfLine, true}, {FFlag::LuauSimplifyOutOfLine2, true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -22,14 +22,13 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauFormatUseLastPosition)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
@ -1432,6 +1431,10 @@ g12({x=1}, {x=2}, function(x, y) return {x=x.x + y.x} end)
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_lib_function_function_argument") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_lib_function_function_argument")
{ {
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a = {{x=4}, {x=7}, {x=1}} local a = {{x=4}, {x=7}, {x=1}}
table.sort(a, function(x, y) return x.x < y.x end) table.sort(a, function(x, y) return x.x < y.x end)
@ -1683,7 +1686,7 @@ t.f = function(x)
end end
)"); )");
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 // FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(3, result); LUAU_CHECK_ERROR_COUNT(3, result);
@ -1768,7 +1771,7 @@ t.f = function(x)
end end
)"); )");
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 // FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(2, result); LUAU_CHECK_ERROR_COUNT(2, result);
@ -1945,8 +1948,6 @@ TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_unknown")
TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_call_site") TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_call_site")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}};
CheckResult result = check(R"( CheckResult result = check(R"(
local t = {} local t = {}
@ -1967,7 +1968,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_
CHECK_EQ("<a>(a) -> a", toString(requireType("f"))); CHECK_EQ("<a>(a) -> a", toString(requireType("f")));
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
{ {
LUAU_CHECK_NO_ERRORS(result); LUAU_CHECK_NO_ERRORS(result);
CHECK("<a>({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g"))); CHECK("<a>({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g")));
@ -2910,9 +2911,8 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun")
TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSimplifyOutOfLine, true}, {FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, {FFlag::LuauTableLiteralSubtypeSpecificCheck, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -2932,7 +2932,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
{ {
// The new solver should ideally be able to do better here, but this is no worse than the old solver. // The new solver should ideally be able to do better here, but this is no worse than the old solver.
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
auto tm1 = get<TypeMismatch>(result.errors[0]); auto tm1 = get<TypeMismatch>(result.errors[0]);
@ -3159,7 +3159,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func")
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack") TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack")
{ {
ScopedFastFlag _{FFlag::LuauFormatUseLastPosition, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local function foo(): (string, string, string) local function foo(): (string, string, string)
return "", "", "" return "", "", ""
@ -3170,7 +3169,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack")
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic") TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic")
{ {
ScopedFastFlag _{FFlag::LuauFormatUseLastPosition, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local foo : () -> (...string) = (nil :: any) local foo : () -> (...string) = (nil :: any)
print(string.format("%s %s %s", foo())) print(string.format("%s %s %s", foo()))

View file

@ -15,6 +15,10 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauEagerGeneralization3)
using namespace Luau; using namespace Luau;
@ -780,7 +784,7 @@ TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names_old_solver")
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_types") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_types")
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauSubtypingCheckFunctionGenericCounts, true}};
CheckResult result = check(R"( CheckResult result = check(R"(
type C = () -> () type C = () -> ()
@ -790,14 +794,64 @@ local c: C
local d: D = c local d: D = c
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const auto genericMismatch = get<GenericTypeCountMismatch>(result.errors[0]);
CHECK(genericMismatch);
CHECK_EQ(genericMismatch->subTyGenericCount, 1);
CHECK_EQ(genericMismatch->superTyGenericCount, 0);
CHECK_EQ(toString(result.errors[0]), R"(Type '() -> ()' could not be converted into '<T>() -> ()'; different number of generic type parameters)"); auto mismatch = get<TypeMismatch>(result.errors[1]);
CHECK(mismatch);
CHECK_EQ(toString(mismatch->givenType), "() -> ()");
CHECK_EQ(toString(mismatch->wantedType), "<T>() -> ()");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto mismatch = get<TypeMismatch>(result.errors[0]);
CHECK(mismatch);
CHECK_EQ(mismatch->reason, "different number of generic type parameters");
}
}
TEST_CASE_FIXTURE(Fixture, "generic_function_mismatch_with_argument")
{
ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauSubtypingCheckFunctionGenericCounts, true}};
CheckResult result = check(R"(
type C = (number) -> ()
type D = <T>(number) -> ()
local c: C
local d: D = c
)");
if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const auto genericMismatch = get<GenericTypeCountMismatch>(result.errors[0]);
CHECK(genericMismatch);
CHECK_EQ(genericMismatch->subTyGenericCount, 1);
CHECK_EQ(genericMismatch->superTyGenericCount, 0);
auto mismatch = get<TypeMismatch>(result.errors[1]);
CHECK(mismatch);
CHECK_EQ(toString(mismatch->givenType), "(number) -> ()");
CHECK_EQ(toString(mismatch->wantedType), "<T>(number) -> ()");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto mismatch = get<TypeMismatch>(result.errors[0]);
CHECK(mismatch);
CHECK_EQ(mismatch->reason, "different number of generic type parameters");
}
} }
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_pack") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_pack")
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauSubtypingCheckFunctionGenericCounts, true}};
CheckResult result = check(R"( CheckResult result = check(R"(
type C = () -> () type C = () -> ()
@ -807,12 +861,26 @@ local c: C
local d: D = c local d: D = c
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const auto genericMismatch = get<GenericTypePackCountMismatch>(result.errors[0]);
CHECK(genericMismatch);
CHECK_EQ(genericMismatch->subTyGenericPackCount, 1);
CHECK_EQ(genericMismatch->superTyGenericPackCount, 0);
CHECK_EQ( auto mismatch = get<TypeMismatch>(result.errors[1]);
toString(result.errors[0]), CHECK(mismatch);
R"(Type '() -> ()' could not be converted into '<T...>() -> ()'; different number of generic type pack parameters)" CHECK_EQ(toString(mismatch->givenType), "() -> ()");
); CHECK_EQ(toString(mismatch->wantedType), "<T...>() -> ()");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto mismatch = get<TypeMismatch>(result.errors[0]);
CHECK(mismatch);
CHECK_EQ(mismatch->reason, "different number of generic type pack parameters");
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_functions_dont_cache_type_parameters") TEST_CASE_FIXTURE(BuiltinsFixture, "generic_functions_dont_cache_type_parameters")
@ -966,7 +1034,7 @@ wrapper(test)
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
{ {
ScopedFastFlag sff{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}; DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number, b: string) function test2(a: number, b: string)
@ -980,17 +1048,7 @@ wrapper(test2, 1, "", 3)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2) CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)");
{
const CountMismatch* cm = get<CountMismatch>(result.errors[0]);
REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]);
// TODO: CLI-152070 fix to expect 3
CHECK_EQ(cm->expected, 1);
CHECK_EQ(cm->actual, 4);
CHECK_EQ(cm->context, CountMismatch::Arg);
}
else
CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)");
} }
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_just_right") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_just_right")
@ -1364,19 +1422,13 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded"
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_lib_function_function_argument")
{
CheckResult result = check(R"(
local a = {{x=4}, {x=7}, {x=1}}
table.sort(a, function(x, y) return x.x < y.x end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
{ {
ScopedFastFlag _[] = {
{FFlag::LuauReportSubtypingErrors, true},
{FFlag::LuauSubtypingCheckFunctionGenericCounts, true},
{FFlag::LuauEagerGeneralization3, true},
};
CheckResult result; CheckResult result;
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -1392,7 +1444,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("<a>(a, a, (a, a) -> a) -> a", toString(requireType("sum")));
CHECK_EQ("<a>(a, a, (a, a) -> a) -> a", toString(requireTypeAtPosition({7, 29})));
} }
else else
{ {
@ -1406,9 +1459,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
local b = sumrec(sum) -- ok local b = sumrec(sum) -- ok
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
)"); )");
LUAU_REQUIRE_NO_ERRORS(result);
} }
LUAU_REQUIRE_NO_ERRORS(result);
} }

View file

@ -16,7 +16,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
@ -188,7 +188,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements"
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauAddCallConstraintForIterableFunctions, true}, {FFlag::LuauAddCallConstraintForIterableFunctions, true},
{FFlag::LuauSimplifyOutOfLine, true}, {FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauDfgAllowUpdatesInLoops, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true},
}; };
@ -1407,8 +1407,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1413")
TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_error_in_body") TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_error_in_body")
{ {
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDfgAllowUpdatesInLoops, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true},
}; };
@ -1499,8 +1501,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_unconditionally_fires_error")
TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish") TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish")
{ {
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
{FFlag::LuauDfgAllowUpdatesInLoops, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true},
}; };

View file

@ -13,8 +13,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
using namespace Luau; using namespace Luau;
@ -746,14 +745,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "spooky_blocked_type_laundered_by_bound_type"
local _ = require(game.A); local _ = require(game.A);
)"); )");
if (FFlag::LuauAddCallConstraintForIterableFunctions && !FFlag::LuauOptimizeFalsyAndTruthyIntersect) LUAU_REQUIRE_NO_ERRORS(result);
{
LUAU_REQUIRE_ERROR_COUNT(3, result);
}
else
{
LUAU_REQUIRE_NO_ERRORS(result);
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics") TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics")
@ -785,7 +777,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23}))); CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23})));
} }

View file

@ -17,7 +17,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
TEST_SUITE_BEGIN("TypeInferOperators"); TEST_SUITE_BEGIN("TypeInferOperators");
@ -29,7 +29,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// FIXME: Regression // FIXME: Regression
CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
@ -51,7 +51,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// FIXME: Regression. // FIXME: Regression.
CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
@ -72,7 +72,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
// FIXME: Regression // FIXME: Regression
CHECK("(string & ~(false?)) | string" == toString(requireType("s"))); CHECK("(string & ~(false?)) | string" == toString(requireType("s")));
@ -634,7 +634,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
local a = -foo local a = -foo
)"); )");
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);

View file

@ -1,14 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AstQuery.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Scope.h"
#include "Luau/TypeInfer.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
#include "Fixture.h" #include "Fixture.h"
#include "DiffAsserts.h"
#include "doctest.h" #include "doctest.h"
@ -32,7 +27,7 @@ TEST_CASE_FIXTURE(Fixture, "string_length")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ_DIFF(builtinTypes->numberType, requireType("t")); CHECK_EQ(builtinTypes->numberType, requireType("t"));
} }
TEST_CASE_FIXTURE(Fixture, "string_index") TEST_CASE_FIXTURE(Fixture, "string_index")

View file

@ -10,13 +10,13 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauSimplificationTableExternType) LUAU_FASTFLAG(LuauSimplificationTableExternType)
LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive) LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAG(LuauAvoidDoubleNegation) LUAU_FASTFLAG(LuauAvoidDoubleNegation)
using namespace Luau; using namespace Luau;
@ -690,8 +690,6 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil") TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
{ {
ScopedFastFlag _{FFlag::LuauWeakNilRefinementType, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local t: {string} = {"hello"} local t: {string} = {"hello"}
@ -748,10 +746,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_narrow_to_vector")
TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true") TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true")
{ {
ScopedFastFlag sffs[] = {
{FFlag::LuauWeakNilRefinementType, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local t = {"hello"} local t = {"hello"}
local v = t[2] local v = t[2]
@ -770,7 +764,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
{ {
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
@ -1655,7 +1649,37 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_sh
local pos = part1.Position local pos = part1.Position
)"); )");
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 && !FFlag::LuauNormalizationIntersectTablesPreservesExternTypes)
{
// CLI-142467: this is a major regression that we need to address.
CHECK_EQ("never", toString(requireTypeAtPosition({3, 15})));
CHECK_EQ("any", toString(requireTypeAtPosition({6, 29})));
}
else
{
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("WeldConstraint", toString(requireTypeAtPosition({3, 15})));
CHECK_EQ("Vector3", toString(requireTypeAtPosition({6, 29})));
}
}
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_non_existent_properties_should_not_refine_extern_types_to_never")
{
CheckResult result = check(R"(
local weld: WeldConstraint = nil :: any
assert(weld.Part8)
print(weld) -- hover type should become `never`
assert(weld.Part8.Name == "RootPart")
local part8 = assert(weld.Part8)
local pos = part8.Position
)");
LUAU_REQUIRE_ERRORS(result);
CHECK_EQ(toString(result.errors[0]), "Key 'Part8' not found in class 'WeldConstraint'");
if (FFlag::LuauSolverV2 && !FFlag::LuauNormalizationIntersectTablesPreservesExternTypes)
{ {
// CLI-142467: this is a major regression that we need to address. // CLI-142467: this is a major regression that we need to address.
CHECK_EQ("never", toString(requireTypeAtPosition({3, 15}))); CHECK_EQ("never", toString(requireTypeAtPosition({3, 15})));
@ -1664,7 +1688,10 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_sh
else else
{ {
CHECK_EQ("WeldConstraint", toString(requireTypeAtPosition({3, 15}))); CHECK_EQ("WeldConstraint", toString(requireTypeAtPosition({3, 15})));
CHECK_EQ("Vector3", toString(requireTypeAtPosition({6, 29}))); if (FFlag::LuauSolverV2)
CHECK_EQ("any", toString(requireTypeAtPosition({6, 29})));
else
CHECK_EQ("*error-type*", toString(requireTypeAtPosition({6, 29})));
} }
} }
@ -2223,6 +2250,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction"
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant") TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant")
{ {
// FIXME CLI-141364: An underlying bug in normalization means the type of
// `isIndexKey` is platform dependent.
CheckResult result = check(R"( CheckResult result = check(R"(
local function isIndexKey(k, contiguousLength: number) local function isIndexKey(k, contiguousLength: number)
return type(k) == "number" return type(k) == "number"
@ -2231,7 +2260,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_
and math.floor(k) == k -- no float keys and math.floor(k) == k -- no float keys
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
@ -2505,7 +2533,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauWeakNilRefinementType, true},
{FFlag::DebugLuauEqSatSimplification, true}, {FFlag::DebugLuauEqSatSimplification, true},
}; };
@ -2517,7 +2544,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
end end
)")); )"));
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
// FIXME CLI-114134. We need to simplify types more consistently.
CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24}))); CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24})));
else else
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24})));
@ -2617,8 +2645,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1528_method_calls_are_not_nillable")
TEST_CASE_FIXTURE(Fixture, "oss_1687_equality_shouldnt_leak_nil") TEST_CASE_FIXTURE(Fixture, "oss_1687_equality_shouldnt_leak_nil")
{ {
ScopedFastFlag _{FFlag::LuauWeakNilRefinementType, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict --!strict
function returns_two(): number function returns_two(): number
@ -2639,7 +2665,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1687_equality_shouldnt_leak_nil")
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauWeakNilRefinementType, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
type Part = { type Part = {
@ -2740,4 +2766,37 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1835")
CHECK(get<OptionalValueAccess>(result.errors[0])); CHECK(get<OptionalValueAccess>(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "limit_complexity_of_arithmetic_type_functions" * doctest::timeout(0.5))
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
local Hermite = {}
function Hermite:__init(p0, p1, m0, m1)
self[1] = {
p0.x;
p0.y;
p0.z;
}
self[2] = {
m0.x;
m0.y;
m0.z;
}
self[3] = {
3*(p1.x - p0.x) - 2*m0.x - m1.x;
3*(p1.y - p0.y) - 2*m0.y - m1.y;
3*(p1.z - p0.z) - 2*m0.z - m1.z;
}
end
return Hermite
)");
// We do not care what the errors are, only that this type checks in a
// reasonable amount of time.
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -21,20 +21,16 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTFLAG(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAG(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -702,7 +698,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2) if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization3)
CHECK("<a>({a}) -> ()" == toString(requireType("swap"))); CHECK("<a>({a}) -> ()" == toString(requireType("swap")));
else if (FFlag::LuauSolverV2) else if (FFlag::LuauSolverV2)
CHECK("({unknown}) -> ()" == toString(requireType("swap"))); CHECK("({unknown}) -> ()" == toString(requireType("swap")));
@ -762,7 +758,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2")
TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table") TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table")
{ {
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local t = {"one", "two", "three"} local t = {"one", "two", "three"}
@ -2062,7 +2058,7 @@ TEST_CASE_FIXTURE(Fixture, "explicit_nil_indexer")
TEST_CASE_FIXTURE(Fixture, "ok_to_provide_a_subtype_during_construction") TEST_CASE_FIXTURE(Fixture, "ok_to_provide_a_subtype_during_construction")
{ {
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: string | number = 1 local a: string | number = 1
@ -2379,7 +2375,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
local c : string = t.m("hi") local c : string = t.m("hi")
)"); )");
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 // FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(2, result); LUAU_CHECK_ERROR_COUNT(2, result);
@ -3176,9 +3172,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_quantify_table_that_belongs_to_outer_sc
CHECK_EQ(follow(newRet->metatable), follow(requireType("Counter"))); CHECK_EQ(follow(newRet->metatable), follow(requireType("Counter")));
} }
// TODO: CLI-39624
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_tables_at_scope_level") TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_tables_at_scope_level")
{ {
ScopedFastFlag sff1{FFlag::LuauSimplifyOutOfLine2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local Option = {} local Option = {}
@ -3749,9 +3746,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauReportSubtypingErrors, true},
{FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauEagerGeneralization3, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -4644,7 +4639,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
return; return;
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true} {FFlag::LuauEagerGeneralization3, true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -4684,8 +4679,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers
TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array") TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
{ {
ScopedFastFlag _{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function foo(x, y) function foo(x, y)
if x then if x then
@ -4696,7 +4689,7 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
end end
)"); )");
if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization2) if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization3)
{ {
LUAU_CHECK_ERROR_COUNT(1, result); LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_ERROR(result, NotATable); LUAU_CHECK_ERROR(result, NotATable);
@ -4744,7 +4737,7 @@ TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
// FIXME CLI-114134. We need to simplify types more consistently. // FIXME CLI-114134. We need to simplify types more consistently.
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f"))); CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f")));
else else
CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f"))); CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f")));
@ -5038,7 +5031,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table") TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table")
{ {
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local test = if true then { "meow", "woof" } else { 4, 81 } local test = if true then { "meow", "woof" } else { 4, 81 }
@ -5639,8 +5632,6 @@ TEST_CASE_FIXTURE(Fixture, "stop_refining_new_table_indices_for_non_primitive_ta
TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again") TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again")
{ {
ScopedFastFlag _{FFlag::LuauBidirectionalInferenceElideAssert, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(_: { [string]: {unknown}} ) end function f(_: { [string]: {unknown}} ) end
f( f(

View file

@ -24,22 +24,16 @@ LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauStringPartLengthLimit)
LUAU_FASTFLAG(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauAvoidDoubleNegation) LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult) LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
LUAU_FASTFLAG(LuauSkipLvalueForCompoundAssignment)
LUAU_FASTFLAG(LuauMissingFollowInAssignIndexConstraint)
using namespace Luau; using namespace Luau;
@ -447,7 +441,7 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
#endif #endif
ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100}; ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100};
ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100}; ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100};
ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false}; ScopedFastFlag _{FFlag::LuauEagerGeneralization3, false};
CheckResult result = check(R"(("foo"))" + rep(":lower()", limit)); CheckResult result = check(R"(("foo"))" + rep(":lower()", limit));
@ -1865,16 +1859,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_ret")
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_param") TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_param")
{ {
ScopedFastFlag _{FFlag::LuauSolverV2, true};
auto result = check(R"( auto result = check(R"(
local function check(x): any local function check(x): any
return getmetatable(x) return getmetatable(x)
end end
)"); )");
// CLI-144695: We're leaking the `MT` generic here. if (FFlag::LuauSolverV2)
CHECK_EQ("({ @metatable MT, {+ +} }) -> any", toString(requireType("check"))); CHECK_EQ("(unknown) -> any", toString(requireType("check")));
else
CHECK_EQ("({ @metatable any, {+ +} }) -> any", toString(requireType("check")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_pack_check_missing_follow") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_pack_check_missing_follow")
@ -1929,7 +1923,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_derived_unsound_loops")
TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union") TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union")
{ {
ScopedFastFlag _{FFlag::LuauSolverV2, true}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
ScopedFastFlag fixNumberConcats{FFlag::LuauTypeCheckerAcceptNumberConcats, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local function concat_stuff(x: string, y : string | number) local function concat_stuff(x: string, y : string | number)
@ -1940,10 +1933,7 @@ TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_local_before_declaration_ice") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_local_before_declaration_ice")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauPreprocessTypestatedArgument, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local _ local _
@ -2034,11 +2024,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauEagerGeneralization3, true},
{FFlag::LuauEagerGeneralization2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
}; };
auto result = check(R"( auto result = check(R"(
@ -2073,10 +2059,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauEagerGeneralization3, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -2109,13 +2092,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauHasPropProperBlock, true},
{FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauEagerGeneralization2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauStringPartLengthLimit, true},
{FFlag::LuauSimplificationRecheckAssumption, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
}; };
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(
@ -2154,7 +2131,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::tim
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauAvoidDoubleNegation, true}, {FFlag::LuauAvoidDoubleNegation, true},
}; };
// We don't care about errors, only that we don't OOM during typechecking. // We don't care about errors, only that we don't OOM during typechecking.
@ -2207,7 +2183,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union")
TEST_CASE_FIXTURE(Fixture, "fuzzer_simplify_table_indexer" * doctest::timeout(0.5)) TEST_CASE_FIXTURE(Fixture, "fuzzer_simplify_table_indexer" * doctest::timeout(0.5))
{ {
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(
_[_] += true _[_] += true
@ -2290,4 +2266,152 @@ end
)")); )"));
} }
TEST_CASE_FIXTURE(Fixture, "self_bound_due_to_compound_assign")
{
ScopedFastFlag _{FFlag::LuauSkipLvalueForCompoundAssignment, true};
loadDefinition(R"(
declare class Camera
CameraType: string
CFrame: number
end
)");
CheckResult result = check(R"(
--!strict
function MT_UPDATE(CAMERA: Camera, Enum: any, totalOffsets: number, focusToCFrame: number, magnitude: number)
if CAMERA.CameraType ~= Enum.CameraType.Custom then
return
end
local goalCFrame = (CAMERA.CFrame) * totalOffsets
if goalCFrame ~= CAMERA.CFrame then
goalCFrame -= (focusToCFrame * magnitude) -- Offset the goalCFrame the raycast direction based on the cutoff distance.
end
end
return {}
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "config_reader_example")
{
// If this flag isn't set, then we do not do the requisite setup when the
// test suite starts, which will cause an assert if we try to eagerly
// generalize _after_ the test is set up. Additionally, this code block
// crashes under the new solver without flags.
if (!FFlag::LuauEagerGeneralization3)
return;
fileResolver.source["game/ConfigReader"] = R"(
--!strict
local ConfigReader = {}
ConfigReader.Defaults = {}
local Defaults = ConfigReader.Defaults
local Config = ConfigReader.Defaults
function ConfigReader:read(config_name: string)
if Config[config_name] ~= nil then
return Config[config_name]
elseif Defaults[config_name] ~= nil then
return Defaults[config_name]
else
error(config_name .. " must be defined in Config")
end
end
function ConfigReader:getFullConfigWithDefaults()
local config = {}
for key, val in pairs(ConfigReader.Defaults) do
config[key] = val
end
for key, val in pairs(Config) do
config[key] = val
end
return config
end
return ConfigReader
)";
fileResolver.source["game/Util"] = R"(
--!strict
local ConfigReader = require(script.Parent.ConfigReader)
local _ = ConfigReader:read("foobar")()
)";
LUAU_REQUIRE_ERRORS(frontend.check("game/Util"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "is_safe_integer_example")
{
if (!FFlag::LuauEagerGeneralization3)
return;
fileResolver.source["game/isInteger"] = R"(
--!strict
return function(value)
return type(value) == "number" and value ~= math.huge and value == math.floor(value)
end
)";
fileResolver.source["game/MAX_SAFE_INTEGER"] = R"(
--!strict
return 42
)";
fileResolver.source["game/Util"] = R"(
--!strict
local isInteger = require(script.Parent.isInteger)
local MAX_SAFE_INTEGER = require(script.Parent.MAX_SAFE_INTEGER)
return function(value)
return isInteger(value) and math.abs(value) <= MAX_SAFE_INTEGER
end
)";
LUAU_REQUIRE_NO_ERRORS(frontend.check("game/Util"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free")
{
LUAU_REQUIRE_ERRORS(check(R"(
_ = if l0.n0.n0 then {n4(...,setmetatable(setmetatable(_),_)),_ == _,} elseif _.ceil._ then _ elseif _ then not _
)"));
LUAU_REQUIRE_ERRORS(check(R"(
do
_ = if _[_] then {[_(``)]="y",} elseif _ then _ elseif _[_] then "" elseif _ then _ elseif _[_] then {} elseif _[_] then false else ""
end
)"));
LUAU_REQUIRE_ERRORS(check(R"(
local l249 = require(module0)
_,_ = {[`{_}`]=_,[_._G._]=(_)(),[_["" + _]._G]={_=_,_=_,[_._G[_]._]=_G,},},_,(_)()
)"));
}
TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_assign_index_constraint")
{
ScopedFastFlag _{FFlag::LuauMissingFollowInAssignIndexConstraint, true};
LUAU_REQUIRE_ERRORS(check(R"(
_._G = nil
for _ in ... do
break
end
for _ in function<t0,t0,t0>(l0)
_,_._,l0 = l0,_,_._
local _ = l0,{[_]=_,}
_[{nil=_,}](_)
end,{[_]=_,} do
end
_ -= _
)"));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -12,9 +12,8 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauFixEmptyTypePackStringification) LUAU_FASTFLAG(LuauFixEmptyTypePackStringification)
@ -100,7 +99,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
CHECK_EQ("<a, b..., c...>((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply"))); CHECK_EQ("<a, b..., c...>((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply")));
else else
CHECK_EQ("<a, b..., c...>((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply"))); CHECK_EQ("<a, b..., c...>((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply")));
@ -1064,7 +1063,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks")
TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2") TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}}; ScopedFastFlag _{FFlag::LuauReportSubtypingErrors, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any) function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any)

View file

@ -4,16 +4,12 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
using namespace Luau; using namespace Luau;
@ -418,9 +414,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauReportSubtypingErrors, true},
{FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauEagerGeneralization3, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -553,7 +547,6 @@ TEST_CASE_FIXTURE(Fixture, "typestate_unknown_global")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalized_type_variables_are_bad" * doctest::timeout(0.5)) TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalized_type_variables_are_bad" * doctest::timeout(0.5))
{ {
ScopedFastFlag _{FFlag::LuauRefineWaitForBlockedTypesInTarget, true};
// We do not care about the errors here, only that this finishes typing // We do not care about the errors here, only that this finishes typing
// in a sensible amount of time. // in a sensible amount of time.
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(

View file

@ -10,7 +10,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauEagerGeneralization3)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
TEST_SUITE_BEGIN("UnionTypes"); TEST_SUITE_BEGIN("UnionTypes");
@ -895,7 +895,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization2) if (FFlag::LuauEagerGeneralization3)
CHECK_EQ( CHECK_EQ(
"<a>(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) "<a>(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))
); );

View file

@ -7,7 +7,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions);
TEST_SUITE_BEGIN("TypeInferUnknownNever"); TEST_SUITE_BEGIN("TypeInferUnknownNever");
@ -336,13 +335,11 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauNoMoreInjectiveTypeFunctions) if (FFlag::LuauSolverV2)
{ {
// FIXME: CLI-152325 // FIXME: CLI-152325
CHECK_EQ("(nil, nil & ~nil) -> boolean", toString(requireType("ord"))); CHECK_EQ("(nil, nil & ~nil) -> boolean", toString(requireType("ord")));
} }
else if (FFlag::LuauSolverV2)
CHECK_EQ("(nil, unknown) -> boolean", toString(requireType("ord")));
else else
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("ord"))); CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("ord")));
} }

View file

@ -186,6 +186,49 @@ for i=1,lim do a[{}] = i end
collectgarbage() collectgarbage()
assert(next(a) == nil) assert(next(a) == nil)
-- shrinking a table should be robust against an OOM
do
local count = 100000
local extra = 1000
a = {}
setmetatable(a, {__mode = 'ks'})
-- items to be collected
for i = 1,count do a[{}] = i end
-- items that will still be reachable
for i = 1,extra do a[tostring(i)] = {} end
setblockallocations(true)
collectgarbage()
setblockallocations(false)
for k,v in a do assert(type(k) == "string") end
end
-- shrinking strings should be robust against an OOM
do
local count = 100000
a = {}
for i = 1,count do a[tostring(count)] = i end
table.clear(a);
setblockallocations(true)
collectgarbage()
setblockallocations(false)
end
-- shrinking thread stacks should be robust against an OOM
do
function recurse(n, ...) return n <= 1 and (1 + #{...}) or recurse(n-1, table.unpack(table.create(4000, 1))) + 1 end
recurse(100)
setblockallocations(true)
collectgarbage()
setblockallocations(false)
end
-- testing userdata -- testing userdata
collectgarbage("stop") -- stop collection collectgarbage("stop") -- stop collection
local u = newproxy(true) local u = newproxy(true)