mirror of
https://github.com/luau-lang/luau.git
synced 2025-08-26 11:27:08 +01:00
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:
parent
7a27c38147
commit
68cdcc4a3a
61 changed files with 1801 additions and 4003 deletions
|
@ -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
|
|
@ -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<
|
||||
TypeMismatch,
|
||||
UnknownSymbol,
|
||||
|
@ -521,7 +547,10 @@ using TypeErrorData = Variant<
|
|||
ExplicitFunctionAnnotationRecommended,
|
||||
UserDefinedTypeFunctionError,
|
||||
ReservedIdentifier,
|
||||
UnexpectedArrayLikeTableItem>;
|
||||
UnexpectedArrayLikeTableItem,
|
||||
CannotCheckDynamicStringFormatCalls,
|
||||
GenericTypeCountMismatch,
|
||||
GenericTypePackCountMismatch>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
|
|
|
@ -20,7 +20,6 @@ struct SourceCode
|
|||
None,
|
||||
Module,
|
||||
Script,
|
||||
Local_DEPRECATED
|
||||
};
|
||||
|
||||
std::string source;
|
||||
|
|
|
@ -62,6 +62,12 @@ public:
|
|||
bool operator==(const TypeIds& there) const;
|
||||
size_t getHash() 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
|
||||
|
|
|
@ -318,4 +318,7 @@ std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, Type
|
|||
*/
|
||||
bool isRecord(const AstExprTable::Item& item);
|
||||
|
||||
// Unwraps any grouping expressions iteratively.
|
||||
AstExpr* unwrapGroup(AstExpr* expr);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "Luau/TypeUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
|
@ -29,11 +30,12 @@
|
|||
*/
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatImprovements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -311,7 +313,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
TypeArena& arena = globals.globalTypes;
|
||||
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
||||
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauEagerGeneralization2
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
globalScope = globals.globalScope.get();
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
@ -378,12 +380,22 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
|
||||
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
|
||||
|
||||
// getmetatable : <MT>({ @metatable MT, {+ +} }) -> MT
|
||||
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||
|
||||
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)
|
||||
{
|
||||
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
|
||||
|
||||
if (FFlag::LuauUpdateSetMetatableTypeSignature)
|
||||
|
@ -633,110 +645,251 @@ bool MagicFormat::infer(const MagicFunctionCallContext& context)
|
|||
{
|
||||
TypeArena* arena = context.solver->arena;
|
||||
|
||||
AstExprConstantString* fmt = nullptr;
|
||||
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
|
||||
if (FFlag::LuauStringFormatImprovements)
|
||||
{
|
||||
if (auto group = index->expr->as<AstExprGroup>())
|
||||
fmt = group->expr->as<AstExprConstantString>();
|
||||
else
|
||||
fmt = index->expr->as<AstExprConstantString>();
|
||||
auto iter = begin(context.arguments);
|
||||
|
||||
// we'll suppress any errors for `string.format` if the format string is error suppressing.
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
AstExprConstantString* fmt = nullptr;
|
||||
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
|
||||
if (FFlag::LuauStringFormatImprovements)
|
||||
{
|
||||
if (auto group = index->expr->as<AstExprGroup>())
|
||||
fmt = group->expr->as<AstExprConstantString>();
|
||||
else
|
||||
fmt = index->expr->as<AstExprConstantString>();
|
||||
}
|
||||
auto iter = begin(context.arguments);
|
||||
|
||||
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 =
|
||||
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)
|
||||
if (iter == end(context.arguments))
|
||||
{
|
||||
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);
|
||||
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!reasonings.suppressed)
|
||||
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
|
||||
// we'll suppress any errors for `string.format` if the format string is error suppressing.
|
||||
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)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "Luau/Constraint.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -51,7 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
|
|||
|
||||
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);
|
||||
}
|
||||
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization2)
|
||||
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
rci.traverseIntoTypeFunctions = false;
|
||||
rci.traverse(fcc->fn);
|
||||
|
@ -118,12 +118,12 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
|||
else if (auto hpc = get<HasPropConstraint>(*this))
|
||||
{
|
||||
rci.traverse(hpc->resultType);
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
rci.traverse(hpc->subjectType);
|
||||
}
|
||||
else if (auto hic = get<HasIndexerConstraint>(*this))
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
rci.traverse(hic->subjectType);
|
||||
rci.traverse(hic->resultType);
|
||||
// `HasIndexerConstraint` should not mutate `indexType`.
|
||||
|
|
|
@ -34,22 +34,21 @@
|
|||
LUAU_FASTINT(LuauCheckRecursionLimit)
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables)
|
||||
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -257,7 +256,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
rootScope->location = block->location;
|
||||
module->astScopes[block] = NotNull{scope.get()};
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.emplace_back();
|
||||
else
|
||||
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->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
|
||||
|
@ -312,7 +311,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
}
|
||||
);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.pop_back();
|
||||
else
|
||||
DEPRECATED_interiorTypes.pop_back();
|
||||
|
@ -338,7 +337,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
asMutable(ty)->ty.emplace<BoundType>(domainTy);
|
||||
}
|
||||
|
||||
if (FFlag::LuauSimplifyOutOfLine)
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
{
|
||||
for (TypeId ty : unionsToSimplify)
|
||||
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
|
||||
prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block);
|
||||
// Pre
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.emplace_back();
|
||||
else
|
||||
DEPRECATED_interiorTypes.emplace_back();
|
||||
visitBlockWithoutChildScope(resumeScope, block);
|
||||
// Post
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.pop_back();
|
||||
else
|
||||
DEPRECATED_interiorTypes.pop_back();
|
||||
|
@ -386,12 +385,12 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
|
|||
|
||||
TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
|
||||
interiorFreeTypes.back().types.push_back(ft);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
freeTypes.insert(ft);
|
||||
|
||||
return ft;
|
||||
|
@ -408,7 +407,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po
|
|||
{
|
||||
FreeTypePack f{scope.get(), polarity};
|
||||
TypePackId result = arena->addTypePack(TypePackVar{std::move(f)});
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.back().typePacks.push_back(result);
|
||||
return result;
|
||||
}
|
||||
|
@ -719,8 +718,6 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
|||
if (std::optional<TypeId> defTy = lookup(scope, location, def))
|
||||
{
|
||||
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
|
||||
// sufficiently solved, we queue the intersection up via an
|
||||
// IntersectConstraint.
|
||||
|
@ -767,7 +764,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
|||
if (kind != RefinementsOpKind::None)
|
||||
ty = flushConstraints(kind, ty, discriminants);
|
||||
|
||||
if (FFlag::LuauWeakNilRefinementType && partition.shouldAppendNilType)
|
||||
if (partition.shouldAppendNilType)
|
||||
ty = createTypeFunctionInstance(builtinTypeFunctions().weakoptionalFunc, {ty}, {}, scope, location);
|
||||
|
||||
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);
|
||||
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)
|
||||
emplaceType<BoundType>(asMutable(functionType), sig.signature);
|
||||
|
||||
|
@ -1480,7 +1477,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||
|
||||
Checkpoint start = checkpoint(this);
|
||||
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);
|
||||
|
||||
|
@ -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;
|
||||
module->astCompoundAssignResultTypes[assign] = resultTy;
|
||||
|
||||
TypeId lhsType = check(scope, assign->var).ty;
|
||||
visitLValue(scope, assign->var, lhsType);
|
||||
if (!FFlag::LuauSkipLvalueForCompoundAssignment)
|
||||
{
|
||||
TypeId lhsType = check(scope, assign->var).ty;
|
||||
visitLValue(scope, assign->var, lhsType);
|
||||
|
||||
follow(lhsType);
|
||||
follow(resultTy);
|
||||
follow(lhsType);
|
||||
follow(resultTy);
|
||||
}
|
||||
|
||||
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
|
||||
scope->children.push_back(NotNull{sig.signatureScope.get()});
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.emplace_back();
|
||||
else
|
||||
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->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());
|
||||
|
||||
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.pop_back();
|
||||
else
|
||||
DEPRECATED_interiorTypes.pop_back();
|
||||
|
@ -2491,7 +2491,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
|
|||
return Inference{builtinTypes->stringType};
|
||||
|
||||
TypeId freeTy = nullptr;
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
freeTy = freshType(scope, Polarity::Positive);
|
||||
FreeType* ft = getMutable<FreeType>(freeTy);
|
||||
|
@ -2532,7 +2532,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
|
|||
return Inference{builtinTypes->booleanType};
|
||||
|
||||
TypeId freeTy = nullptr;
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
freeTy = freshType(scope, Polarity::Positive);
|
||||
FreeType* ft = getMutable<FreeType>(freeTy);
|
||||
|
@ -2691,7 +2691,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
|
|||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.emplace_back();
|
||||
else
|
||||
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->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
|
||||
|
@ -2849,16 +2849,14 @@ Inference ConstraintGenerator::checkAstExprBinary(
|
|||
}
|
||||
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);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
{
|
||||
if (FFlag::LuauNoMoreInjectiveTypeFunctions)
|
||||
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
|
||||
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
|
||||
|
||||
TypeId resultType = createTypeFunctionInstance(
|
||||
builtinTypeFunctions().ltFunc,
|
||||
|
@ -2871,16 +2869,14 @@ Inference ConstraintGenerator::checkAstExprBinary(
|
|||
}
|
||||
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);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareGt:
|
||||
{
|
||||
if (FFlag::LuauNoMoreInjectiveTypeFunctions)
|
||||
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
|
||||
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
|
||||
|
||||
TypeId resultType = createTypeFunctionInstance(
|
||||
builtinTypeFunctions().leFunc,
|
||||
|
@ -3217,7 +3213,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit))
|
||||
largeTableDepth++;
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
interiorFreeTypes.back().types.push_back(ty);
|
||||
else
|
||||
DEPRECATED_interiorTypes.back().push_back(ty);
|
||||
|
@ -3270,7 +3266,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
{
|
||||
LUAU_ASSERT(!indexValueLowerBound.empty());
|
||||
|
||||
if (FFlag::LuauSimplifyOutOfLine)
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
{
|
||||
TypeId indexKey = nullptr;
|
||||
TypeId indexValue = nullptr;
|
||||
|
@ -3478,7 +3474,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
|||
|
||||
LUAU_ASSERT(nullptr != varargPack);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// Some of the types in argTypes will eventually be generics, and some
|
||||
// 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))
|
||||
bindFreeType(*expectedType, actualFunctionType);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
scopeToFunction[signatureScope.get()] = actualFunctionType;
|
||||
|
||||
return {
|
||||
|
@ -4064,7 +4060,7 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location,
|
|||
if (get<NeverType>(follow(rhs)))
|
||||
return lhs;
|
||||
|
||||
if (FFlag::LuauSimplifyOutOfLine)
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
{
|
||||
TypeId result = simplifyUnion(scope, location, lhs, rhs);
|
||||
if (is<UnionType>(follow(result)))
|
||||
|
@ -4080,7 +4076,7 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location,
|
|||
|
||||
TypeId ConstraintGenerator::makeUnion(std::vector<TypeId> options)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine);
|
||||
LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine2);
|
||||
TypeId result = arena->addType(UnionType{std::move(options)});
|
||||
unionsToSimplify.push_back(result);
|
||||
return result;
|
||||
|
@ -4304,7 +4300,7 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
|
|||
scope->bindings[symbol] = Binding{tys.front(), location};
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauSimplifyOutOfLine)
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
{
|
||||
TypeId ty = makeUnion(std::move(tys));
|
||||
scope->bindings[symbol] = Binding{ty, location};
|
||||
|
@ -4352,7 +4348,7 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
|
|||
el = result[0];
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauSimplifyOutOfLine)
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
el = makeUnion(std::move(result));
|
||||
else
|
||||
el = module->internalTypes.addType(UnionType{std::move(result)});
|
||||
|
|
|
@ -32,17 +32,15 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
|
||||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -418,7 +416,7 @@ void ConstraintSolver::run()
|
|||
}
|
||||
|
||||
// Free types that have no constraints at all can be generalized right away.
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
for (TypeId ty : constraintSet.freeTypes)
|
||||
{
|
||||
|
@ -479,7 +477,7 @@ void ConstraintSolver::run()
|
|||
// expansion types, etc, so we need to follow it.
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
if (seen.contains(ty))
|
||||
continue;
|
||||
|
@ -498,7 +496,7 @@ void ConstraintSolver::run()
|
|||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2 && refCount == 0)
|
||||
if (FFlag::LuauEagerGeneralization3 && refCount == 0)
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
}
|
||||
|
@ -676,7 +674,7 @@ void ConstraintSolver::initFreeTypeTracking()
|
|||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
|
||||
it->second.insert(c.get());
|
||||
|
@ -689,13 +687,6 @@ void ConstraintSolver::initFreeTypeTracking()
|
|||
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)
|
||||
|
@ -739,7 +730,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
|
|||
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
|
||||
); // FIXME? Is this the right polarity?
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
trackInteriorFreeType(constraint->scope, ty);
|
||||
|
||||
return;
|
||||
|
@ -900,7 +891,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
{
|
||||
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (auto freeTy = get<FreeType>(ty))
|
||||
|
@ -909,7 +900,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
params.foundOutsideFunctions = true;
|
||||
params.useCount = 1;
|
||||
params.polarity = freeTy->polarity;
|
||||
|
||||
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params);
|
||||
if (res.resourceLimitsExceeded)
|
||||
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)
|
||||
{
|
||||
|
@ -942,7 +932,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
if (c.noGenerics)
|
||||
{
|
||||
|
@ -1387,7 +1377,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
TypePackId argsPack = follow(c.argsPack);
|
||||
TypePackId result = follow(c.result);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
if (isBlocked(fn))
|
||||
return block(c.fn, constraint);
|
||||
|
@ -1527,7 +1517,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
|
||||
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
for (TypeId freeTy : u2.newFreshTypes)
|
||||
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
|
||||
// `inferredTy` is never unified elsewhere.
|
||||
if (FFlag::LuauTrackInferredFunctionTypeFromCall)
|
||||
trackInteriorFreeType(constraint->scope, inferredTy);
|
||||
trackInteriorFreeType(constraint->scope, inferredTy);
|
||||
|
||||
unblock(c.result, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static AstExpr* unwrapGroup(AstExpr* expr)
|
||||
{
|
||||
while (auto group = expr->as<AstExprGroup>())
|
||||
expr = group->expr;
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
struct ContainsGenerics : public TypeOnceVisitor
|
||||
struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor
|
||||
{
|
||||
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)
|
||||
{
|
||||
TypeId fn = follow(c.fn);
|
||||
|
@ -1707,92 +1775,175 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
DenseHashMap<TypeId, TypeId> replacements{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
|
||||
|
||||
ContainsGenerics containsGenerics;
|
||||
|
||||
for (auto generic : ftv->generics)
|
||||
if (FFlag::LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
{
|
||||
replacements[generic] = builtinTypes->unknownType;
|
||||
containsGenerics.generics.insert(generic);
|
||||
}
|
||||
|
||||
for (auto genericPack : ftv->genericPacks)
|
||||
{
|
||||
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
|
||||
containsGenerics.generics.insert(genericPack);
|
||||
}
|
||||
DenseHashSet<const void*> genericTypesAndPacks{nullptr};
|
||||
|
||||
const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
|
||||
const std::vector<TypeId> argPackHead = flatten(argsPack).first;
|
||||
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
|
||||
// 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))
|
||||
for (auto generic : ftv->generics)
|
||||
{
|
||||
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)
|
||||
// We may see non-generic types here, for example when evaluating a
|
||||
// recursive function call.
|
||||
if (auto gty = get<GenericType>(follow(generic)))
|
||||
{
|
||||
// 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;
|
||||
replacements[generic] = gty->polarity == Polarity::Negative ? builtinTypes->neverType : builtinTypes->unknownType;
|
||||
genericTypesAndPacks.insert(generic);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
|
||||
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
|
||||
|
||||
if (expectedLambdaTy && lambdaTy && lambdaExpr)
|
||||
for (auto genericPack : ftv->genericPacks)
|
||||
{
|
||||
const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first;
|
||||
const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first;
|
||||
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
|
||||
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]);
|
||||
bind(constraint, lambdaArgTys[j], expectedLambdaArgTys[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>())
|
||||
{
|
||||
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}};
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
replacements[generic] = builtinTypes->unknownType;
|
||||
containsGenerics.generics.insert(generic);
|
||||
}
|
||||
else if (expr->is<AstExprTable>())
|
||||
|
||||
for (auto genericPack : ftv->genericPacks)
|
||||
{
|
||||
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());
|
||||
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
|
||||
containsGenerics.generics.insert(genericPack);
|
||||
}
|
||||
|
||||
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 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(canMutate(resultType, constraint));
|
||||
|
||||
if (FFlag::LuauHasPropProperBlock)
|
||||
{
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
}
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
if (const TableType* subjectTable = getTableType(subjectType))
|
||||
{
|
||||
|
@ -1941,7 +2084,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
|
|||
TypeId upperBound =
|
||||
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));
|
||||
|
||||
|
@ -1972,7 +2115,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
|
|||
|
||||
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
|
||||
emplace<FreeType>(constraint, resultType, freeResult);
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
trackInteriorFreeType(constraint->scope, resultType);
|
||||
|
||||
tt->indexer = TableIndexer{indexType, resultType};
|
||||
|
@ -2161,7 +2304,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
|
|||
{
|
||||
auto lhsFreeUpperBound = follow(lhsFree->upperBound);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
|
||||
if (!blocked.empty())
|
||||
|
@ -2341,10 +2484,21 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
|
|||
|
||||
if (auto lhsFree = getMutable<FreeType>(lhsType))
|
||||
{
|
||||
if (auto lhsTable = getMutable<TableType>(lhsFree->upperBound))
|
||||
if (FFlag::LuauMissingFollowInAssignIndexConstraint)
|
||||
{
|
||||
if (auto res = tableStuff(lhsTable))
|
||||
return *res;
|
||||
if (auto lhsTable = getMutable<TableType>(follow(lhsFree->upperBound)))
|
||||
{
|
||||
if (auto res = tableStuff(lhsTable))
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto lhsTable = getMutable<TableType>(lhsFree->upperBound))
|
||||
{
|
||||
if (auto res = tableStuff(lhsTable))
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
TypeId newUpperBound =
|
||||
|
@ -2603,8 +2757,8 @@ bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Co
|
|||
|
||||
struct FindAllUnionMembers : TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<TypeId> recordedTys{nullptr};
|
||||
DenseHashSet<TypeId> blockedTys{nullptr};
|
||||
TypeIds recordedTys;
|
||||
TypeIds blockedTys;
|
||||
|
||||
FindAllUnionMembers()
|
||||
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||
|
@ -3062,7 +3216,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
|
|||
{
|
||||
const TypeId upperBound = follow(ft->upperBound);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
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
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
auto it = mutatedFreeTypeToConstraint.find(source);
|
||||
if (it != mutatedFreeTypeToConstraint.end())
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
|
@ -1069,11 +1068,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
{
|
||||
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()))
|
||||
{
|
||||
|
@ -1105,12 +1101,6 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
visitLValue(firstArg, def);
|
||||
}
|
||||
|
||||
if (!FFlag::LuauPreprocessTypestatedArgument)
|
||||
{
|
||||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
}
|
||||
|
||||
// We treat function calls as "subscripted" as they could potentially
|
||||
// return a subscripted value, consider:
|
||||
//
|
||||
|
|
|
@ -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
|
|
@ -18,7 +18,7 @@
|
|||
#include <unordered_set>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
|
||||
|
||||
|
@ -663,7 +663,7 @@ struct ErrorConverter
|
|||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
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" +
|
||||
"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`.";
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -1240,6 +1259,16 @@ bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
|
|||
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)
|
||||
{
|
||||
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, CannotCheckDynamicStringFormatCalls>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GenericTypeCountMismatch>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GenericTypePackCountMismatch>)
|
||||
{
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ LUAU_FASTINT(LuauTarjanChildLimit)
|
|||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
|
||||
|
@ -1444,7 +1444,7 @@ ModulePtr check(
|
|||
// is set, and another when it is unset.
|
||||
std::optional<ConstraintSolver> cs;
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
ConstraintSet constraintSet = cg.run(sourceModule.root);
|
||||
result->errors = std::move(constraintSet.errors);
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeIds.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGeneralizationCannotMutateAcrossModules)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -469,7 +470,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
if (!subsumes(scope, ft.scope))
|
||||
return true;
|
||||
|
@ -520,7 +521,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
unsealedTables.insert(ty);
|
||||
else
|
||||
{
|
||||
|
@ -593,7 +594,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
if (tt.indexer)
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// {[K]: V} is equivalent to three functions: get, set, and iterate
|
||||
//
|
||||
|
@ -651,7 +652,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
if (!subsumes(scope, ftp.scope))
|
||||
return true;
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
GeneralizationParams<TypePackId>& params = typePacks[tp];
|
||||
++params.useCount;
|
||||
|
@ -1127,129 +1128,77 @@ struct TypeCacher : TypeOnceVisitor
|
|||
}
|
||||
};
|
||||
|
||||
struct RemoveType : Substitution // NOLINT
|
||||
namespace
|
||||
{
|
||||
|
||||
struct TypeRemover
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeArena> arena;
|
||||
|
||||
TypeId needle;
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
|
||||
RemoveType(NotNull<BuiltinTypes> builtinTypes, TypeArena* arena, TypeId needle)
|
||||
: Substitution(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, needle(needle)
|
||||
void process(TypeId item)
|
||||
{
|
||||
}
|
||||
item = follow(item);
|
||||
|
||||
bool ignoreChildren(TypeId ty) override
|
||||
{
|
||||
if (get<UnionType>(ty) || get<IntersectionType>(ty))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
// If we've already visited this item, or it's outside our arena, then
|
||||
// do not try to mutate it.
|
||||
if (seen.contains(item) || item->owningArena != arena || item->persistent)
|
||||
return;
|
||||
seen.insert(item);
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
// A union or intersection is dirty if it contains the needle or if it has any duplicate members.
|
||||
if (auto ut = get<UnionType>(ty))
|
||||
if (auto ut = getMutable<UnionType>(item))
|
||||
{
|
||||
DenseHashSet<TypeId> distinctParts{nullptr};
|
||||
size_t count = 0;
|
||||
for (TypeId part : ut)
|
||||
TypeIds newOptions;
|
||||
for (TypeId option : ut->options)
|
||||
{
|
||||
++count;
|
||||
if (part == needle)
|
||||
return true;
|
||||
distinctParts.insert(follow(part));
|
||||
process(option);
|
||||
option = follow(option);
|
||||
if (option != needle && !is<NeverType>(option) && option != item)
|
||||
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};
|
||||
size_t count = 0;
|
||||
for (TypeId part : it)
|
||||
TypeIds newParts;
|
||||
for (TypeId part : it->parts)
|
||||
{
|
||||
++count;
|
||||
if (part == needle)
|
||||
return true;
|
||||
distinctParts.insert(follow(part));
|
||||
process(part);
|
||||
part = follow(part);
|
||||
if (part != needle && !is<UnknownType>(part) && part != item)
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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)
|
||||
void removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
|
||||
{
|
||||
RemoveType rt{builtinTypes, arena, needle};
|
||||
return rt.substitute(haystack);
|
||||
TypeRemover tr{builtinTypes, arena, needle};
|
||||
tr.process(haystack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GeneralizationResult<TypeId> generalizeType(
|
||||
|
@ -1274,7 +1223,7 @@ GeneralizationResult<TypeId> generalizeType(
|
|||
|
||||
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);
|
||||
else
|
||||
{
|
||||
|
@ -1294,19 +1243,11 @@ GeneralizationResult<TypeId> generalizeType(
|
|||
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == freeTy)
|
||||
lowerFree->upperBound = builtinTypes->unknownType;
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> removed = removeType(arena, builtinTypes, lb, freeTy);
|
||||
if (removed)
|
||||
lb = *removed;
|
||||
else
|
||||
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
|
||||
|
||||
ft->lowerBound = lb;
|
||||
}
|
||||
removeType(arena, builtinTypes, lb, freeTy);
|
||||
|
||||
if (follow(lb) != freeTy)
|
||||
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);
|
||||
else
|
||||
{
|
||||
|
@ -1321,15 +1262,7 @@ GeneralizationResult<TypeId> generalizeType(
|
|||
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == freeTy)
|
||||
upperFree->lowerBound = builtinTypes->neverType;
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
removeType(arena, builtinTypes, ub, freeTy);
|
||||
|
||||
if (follow(ub) != freeTy)
|
||||
emplaceType<BoundType>(asMutable(freeTy), ub);
|
||||
|
@ -1339,17 +1272,14 @@ GeneralizationResult<TypeId> generalizeType(
|
|||
//
|
||||
// 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
|
||||
// union or intersection by clipping the free type from the upper
|
||||
// and lower bounds, then also cleaning the resulting intersection.
|
||||
std::optional<TypeId> removedLb = removeType(arena, builtinTypes, ft->lowerBound, freeTy);
|
||||
if (!removedLb)
|
||||
return {std::nullopt, false, true};
|
||||
std::optional<TypeId> cleanedTy = removeType(arena, builtinTypes, arena->addType(IntersectionType{{*removedLb, ub}}), freeTy);
|
||||
if (!cleanedTy)
|
||||
return {std::nullopt, false, true};
|
||||
emplaceType<BoundType>(asMutable(freeTy), *cleanedTy);
|
||||
removeType(arena, builtinTypes, ft->lowerBound, freeTy);
|
||||
TypeId cleanedTy = arena->addType(IntersectionType{{ft->lowerBound, ub}});
|
||||
removeType(arena, builtinTypes, cleanedTy, freeTy);
|
||||
emplaceType<BoundType>(asMutable(freeTy), cleanedTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1423,7 +1353,7 @@ std::optional<TypeId> generalize(
|
|||
FreeTypeSearcher fts{scope, cachedTypes};
|
||||
fts.traverse(ty);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
FunctionType* functionTy = getMutable<FunctionType>(ty);
|
||||
auto pushGeneric = [&](TypeId t)
|
||||
|
@ -1521,6 +1451,12 @@ struct GenericCounter : TypeVisitor
|
|||
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;
|
||||
DenseHashMap<TypeId, CounterState> generics{nullptr};
|
||||
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
|
||||
|
@ -1537,6 +1473,12 @@ struct GenericCounter : TypeVisitor
|
|||
if (ty->persistent)
|
||||
return false;
|
||||
|
||||
size_t& seenCount = seenCounts[ty];
|
||||
if (seenCount > 1)
|
||||
return false;
|
||||
|
||||
++seenCount;
|
||||
|
||||
polarity = invert(polarity);
|
||||
traverse(ft.argTypes);
|
||||
polarity = invert(polarity);
|
||||
|
@ -1550,6 +1492,11 @@ struct GenericCounter : TypeVisitor
|
|||
if (ty->persistent)
|
||||
return false;
|
||||
|
||||
size_t& seenCount = seenCounts[ty];
|
||||
if (seenCount > 1)
|
||||
return false;
|
||||
++seenCount;
|
||||
|
||||
const Polarity previous = polarity;
|
||||
|
||||
for (const auto& [_name, prop] : tt.props)
|
||||
|
@ -1650,7 +1597,7 @@ void pruneUnnecessaryGenerics(
|
|||
TypeId ty
|
||||
)
|
||||
{
|
||||
if (!FFlag::LuauEagerGeneralization2)
|
||||
if (!FFlag::LuauEagerGeneralization3)
|
||||
return;
|
||||
|
||||
ty = follow(ty);
|
||||
|
@ -1696,7 +1643,11 @@ void pruneUnnecessaryGenerics(
|
|||
for (const auto& [generic, state] : counter.generics)
|
||||
{
|
||||
if (state.count == 1 && state.polarity != Polarity::Mixed)
|
||||
{
|
||||
if (FFlag::LuauGeneralizationCannotMutateAcrossModules && arena.get() != generic->owningArena)
|
||||
continue;
|
||||
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates and types that aren't actually generics.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include "Luau/Scope.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor
|
|||
template<typename TID>
|
||||
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
|
||||
{
|
||||
if (!FFlag::LuauEagerGeneralization2)
|
||||
if (!FFlag::LuauEagerGeneralization3)
|
||||
return;
|
||||
|
||||
InferPolarity infer{arena, scope};
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
// 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/Error.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypePath.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -250,6 +253,18 @@ static void errorToString(std::ostream& stream, const T& err)
|
|||
}
|
||||
else if constexpr (std::is_same_v<T, 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
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
|||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
|
||||
|
||||
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))
|
||||
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);
|
||||
|
||||
intersectExternTypes(here.externTypes, there.externTypes);
|
||||
|
@ -2909,20 +2929,24 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
|
|||
intersectFunctions(here.functions, there.functions);
|
||||
intersectTables(here.tables, there.tables);
|
||||
|
||||
for (auto& [tyvar, inter] : there.tyvars)
|
||||
if (!FFlag::LuauNormalizationReorderFreeTypeIntersect)
|
||||
{
|
||||
int index = tyvarIndex(tyvar);
|
||||
if (ignoreSmallerTyvars < index)
|
||||
for (auto& [tyvar, inter] : there.tyvars)
|
||||
{
|
||||
auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique<NormalizedType>(NormalizedType{builtinTypes}));
|
||||
if (fresh)
|
||||
int index = tyvarIndex(tyvar);
|
||||
if (ignoreSmallerTyvars < index)
|
||||
{
|
||||
NormalizationResult res = unionNormals(*found->second, here, index);
|
||||
if (res != NormalizationResult::True)
|
||||
return res;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = here.tyvars.begin(); it != here.tyvars.end();)
|
||||
{
|
||||
TypeId tyvar = it->first;
|
||||
|
@ -3016,10 +3040,22 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
}
|
||||
else if (get<TableType>(there) || get<MetatableType>(there))
|
||||
{
|
||||
TypeIds tables = std::move(here.tables);
|
||||
clearNormal(here);
|
||||
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
|
||||
here.tables = std::move(tables);
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes)
|
||||
{
|
||||
NormalizedExternType externTypes = std::move(here.externTypes);
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
|
||||
|
||||
namespace Luau
|
||||
|
@ -771,12 +769,9 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
|
|||
|
||||
newParts.insert(simplified);
|
||||
|
||||
if (FFlag::LuauSimplificationRecheckAssumption)
|
||||
{
|
||||
// Initial combination size check could not predict nested union iteration
|
||||
if (newParts.size() > maxSize)
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
}
|
||||
// Initial combination size check could not predict nested union iteration
|
||||
if (newParts.size() > maxSize)
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
|
@ -818,12 +813,9 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
|
|||
|
||||
newParts.insert(simplified);
|
||||
|
||||
if (FFlag::LuauSimplificationRecheckAssumption)
|
||||
{
|
||||
// Initial combination size check could not predict nested union iteration
|
||||
if (newParts.size() > maxSize)
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
}
|
||||
// Initial combination size check could not predict nested union iteration
|
||||
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;
|
||||
}
|
||||
|
||||
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
|
||||
{
|
||||
if (isTruthyType(left))
|
||||
if (auto res = basicIntersectWithTruthy(right))
|
||||
return res;
|
||||
if (isTruthyType(left))
|
||||
if (auto res = basicIntersectWithTruthy(right))
|
||||
return res;
|
||||
|
||||
if (isTruthyType(right))
|
||||
if (auto res = basicIntersectWithTruthy(left))
|
||||
return res;
|
||||
if (isTruthyType(right))
|
||||
if (auto res = basicIntersectWithTruthy(left))
|
||||
return res;
|
||||
|
||||
if (isFalsyType(left))
|
||||
if (auto res = basicIntersectWithFalsy(right))
|
||||
return res;
|
||||
if (isFalsyType(left))
|
||||
if (auto res = basicIntersectWithFalsy(right))
|
||||
return res;
|
||||
|
||||
if (isFalsyType(right))
|
||||
if (auto res = basicIntersectWithFalsy(left))
|
||||
return res;
|
||||
}
|
||||
if (isFalsyType(right))
|
||||
if (auto res = basicIntersectWithFalsy(left))
|
||||
return res;
|
||||
|
||||
Relation relation = relate(left, right);
|
||||
if (left == right || Relation::Coincident == relation)
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
@ -19,9 +18,9 @@
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -669,27 +668,26 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
result = {false};
|
||||
else if (get<ErrorType>(subTy))
|
||||
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);
|
||||
result.isSubtype = ok;
|
||||
result.isCacheable = false;
|
||||
}
|
||||
else if (auto superGeneric = get<GenericType>(superTy);
|
||||
FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant)
|
||||
else if (auto superGeneric = get<GenericType>(superTy); superGeneric && variance == Variance::Contravariant)
|
||||
{
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
result.isSubtype = ok;
|
||||
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
|
||||
// both of them could be narrowed to never.
|
||||
result = {true};
|
||||
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)
|
||||
//
|
||||
|
@ -704,7 +702,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
if (result.isSubtype)
|
||||
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
|
||||
//
|
||||
|
@ -762,19 +760,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
|
||||
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))
|
||||
result = isCovariantWith(env, p, scope);
|
||||
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
|
||||
|
@ -1452,7 +1437,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
|||
{
|
||||
SubtypingResult result{true};
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
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)
|
||||
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
|
||||
// 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));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
|
||||
namespace Luau
|
||||
|
@ -297,9 +296,6 @@ TypeId matchLiteralType(
|
|||
}
|
||||
else if (item.kind == AstExprTable::Item::List)
|
||||
{
|
||||
if (!FFlag::LuauBidirectionalInferenceElideAssert)
|
||||
LUAU_ASSERT(tableTy->indexer);
|
||||
|
||||
if (expectedTableTy->indexer)
|
||||
{
|
||||
const TypeId* propTy = astTypes->find(item.value);
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
|
||||
|
||||
/*
|
||||
* Enables increasing levels of verbosity for Luau type names when stringifying.
|
||||
|
@ -959,34 +958,21 @@ struct TypeStringifier
|
|||
if (needParens)
|
||||
state.emit(")");
|
||||
|
||||
if (FFlag::LuauStringPartLengthLimit)
|
||||
resultsLength += state.result.name.length();
|
||||
|
||||
resultsLength += state.result.name.length();
|
||||
results.push_back(std::move(state.result.name));
|
||||
|
||||
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)
|
||||
break;
|
||||
}
|
||||
if (lengthLimitHit)
|
||||
break;
|
||||
}
|
||||
|
||||
state.unsee(&uv);
|
||||
|
||||
if (FFlag::LuauStringPartLengthLimit)
|
||||
{
|
||||
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
}
|
||||
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
|
||||
if (optional && results.size() > 1)
|
||||
state.emit("(");
|
||||
|
@ -1049,34 +1035,21 @@ struct TypeStringifier
|
|||
if (needParens)
|
||||
state.emit(")");
|
||||
|
||||
if (FFlag::LuauStringPartLengthLimit)
|
||||
resultsLength += state.result.name.length();
|
||||
|
||||
resultsLength += state.result.name.length();
|
||||
results.push_back(std::move(state.result.name));
|
||||
|
||||
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)
|
||||
break;
|
||||
}
|
||||
if (lengthLimitHit)
|
||||
break;
|
||||
}
|
||||
|
||||
state.unsee(&uv);
|
||||
|
||||
if (FFlag::LuauStringPartLengthLimit)
|
||||
{
|
||||
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
}
|
||||
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
|
||||
bool first = true;
|
||||
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);
|
||||
|
|
|
@ -23,10 +23,13 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -831,7 +834,25 @@ bool areEqual(SeenSet& seen, const TableType& lhs, const TableType& rhs)
|
|||
if (l->first != r->first)
|
||||
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;
|
||||
++l;
|
||||
++r;
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
|
||||
|
@ -38,6 +37,7 @@ LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
|
|||
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1409,14 +1409,18 @@ void TypeChecker2::visit(AstExprConstantBool* expr)
|
|||
const TypeId inferredType = lookupType(expr);
|
||||
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 (!isErrorSuppressing(expr->location, inferredType))
|
||||
{
|
||||
if (!r.isSubtype)
|
||||
reportError(TypeMismatch{inferredType, bestType}, expr->location);
|
||||
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = expr->location;
|
||||
}
|
||||
reportErrors(r.errors);
|
||||
}
|
||||
}
|
||||
|
@ -1447,14 +1451,18 @@ void TypeChecker2::visit(AstExprConstantString* expr)
|
|||
const TypeId inferredType = lookupType(expr);
|
||||
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 (!isErrorSuppressing(expr->location, inferredType))
|
||||
{
|
||||
if (!r.isSubtype)
|
||||
reportError(TypeMismatch{inferredType, bestType}, expr->location);
|
||||
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = expr->location;
|
||||
}
|
||||
reportErrors(r.errors);
|
||||
}
|
||||
}
|
||||
|
@ -1533,7 +1541,12 @@ void TypeChecker2::visitCall(AstExprCall* call)
|
|||
if (FFlag::LuauReportSubtypingErrors)
|
||||
{
|
||||
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)
|
||||
|
@ -2359,18 +2372,9 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
|
|||
return builtinTypes->numberType;
|
||||
case AstExprBinary::Op::Concat:
|
||||
{
|
||||
if (FFlag::LuauTypeCheckerAcceptNumberConcats)
|
||||
{
|
||||
const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
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);
|
||||
}
|
||||
|
||||
const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
testIsSubtype(leftType, numberOrString, expr->left->location);
|
||||
testIsSubtype(rightType, numberOrString, expr->right->location);
|
||||
return builtinTypes->stringType;
|
||||
}
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
|
@ -3113,7 +3117,12 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location
|
|||
if (FFlag::LuauReportSubtypingErrors)
|
||||
{
|
||||
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)
|
||||
|
@ -3133,7 +3142,12 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location
|
|||
if (FFlag::LuauReportSubtypingErrors)
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -48,16 +48,14 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
|
|||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
||||
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
|
||||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -285,7 +283,7 @@ struct TypeFunctionReducer
|
|||
}
|
||||
else if (is<GenericType>(ty))
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
return SkipTestResult::Generic;
|
||||
else
|
||||
return SkipTestResult::Irreducible;
|
||||
|
@ -307,7 +305,7 @@ struct TypeFunctionReducer
|
|||
}
|
||||
else if (is<GenericTypePack>(ty))
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
return SkipTestResult::Generic;
|
||||
else
|
||||
return SkipTestResult::Irreducible;
|
||||
|
@ -1261,7 +1259,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
|||
if (isPending(operandTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
operandTy = follow(operandTy);
|
||||
|
||||
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
||||
|
@ -1858,7 +1856,7 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
|
|||
return {rhsTy, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
// 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))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
|
@ -1905,7 +1903,7 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
|||
if (lhsTy == instance || rhsTy == instance)
|
||||
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
|
@ -1938,16 +1936,6 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
|||
emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType);
|
||||
else if (rhsFree && isNumber(lhsTy))
|
||||
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
|
||||
|
@ -2279,7 +2267,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
for (size_t i = 1; i < typeParams.size(); 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);
|
||||
|
||||
// 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
|
||||
// normalization.
|
||||
FindRefinementBlockers frb;
|
||||
frb.traverse(targetTy);
|
||||
if (!frb.found.empty())
|
||||
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
|
||||
}
|
||||
// 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
|
||||
// normalization.
|
||||
FindRefinementBlockers frb;
|
||||
frb.traverse(targetTy);
|
||||
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.
|
||||
// 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
|
||||
// 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))
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
// Simplification considers free and generic types to be
|
||||
// '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
|
||||
// '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()}};
|
||||
return {result.result, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!result.blockedTypes.empty())
|
||||
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
|
||||
}
|
||||
return {result.result, {}};
|
||||
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
|
||||
}
|
||||
}
|
||||
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))
|
||||
else
|
||||
{
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
|
||||
if (!result.blockedTypes.empty())
|
||||
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);
|
||||
|
||||
std::optional<TypeId> metatable = std::nullopt;
|
||||
std::optional<TypeId> result = std::nullopt;
|
||||
bool erroneous = true;
|
||||
|
||||
if (auto table = get<TableType>(targetTy))
|
||||
|
@ -3341,19 +3310,19 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
|
|||
|
||||
if (auto mt = get<MetatableType>(targetTy))
|
||||
{
|
||||
metatable = mt->metatable;
|
||||
result = mt->metatable;
|
||||
erroneous = false;
|
||||
}
|
||||
|
||||
if (auto clazz = get<ExternType>(targetTy))
|
||||
{
|
||||
metatable = clazz->metatable;
|
||||
result = clazz->metatable;
|
||||
erroneous = false;
|
||||
}
|
||||
|
||||
if (auto primitive = get<PrimitiveType>(targetTy))
|
||||
{
|
||||
metatable = primitive->metatable;
|
||||
result = primitive->metatable;
|
||||
erroneous = false;
|
||||
}
|
||||
|
||||
|
@ -3362,11 +3331,18 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
|
|||
if (get<StringSingleton>(singleton))
|
||||
{
|
||||
auto primitiveString = get<PrimitiveType>(ctx->builtins->stringType);
|
||||
metatable = primitiveString->metatable;
|
||||
result = primitiveString->metatable;
|
||||
}
|
||||
erroneous = false;
|
||||
}
|
||||
|
||||
if (FFlag::LuauUpdateGetMetatableTypeSignature && get<AnyType>(targetTy))
|
||||
{
|
||||
// getmetatable<any> ~ any
|
||||
result = targetTy;
|
||||
erroneous = false;
|
||||
}
|
||||
|
||||
if (erroneous)
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||
|
||||
|
@ -3379,8 +3355,8 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
|
|||
if (metatableMetamethod)
|
||||
return {metatableMetamethod, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
if (metatable)
|
||||
return {metatable, Reduction::MaybeOk, {}, {}};
|
||||
if (result)
|
||||
return {result, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
|
@ -3428,16 +3404,34 @@ TypeFunctionReductionResult<TypeId> getmetatableTypeFunction(
|
|||
std::vector<TypeId> parts{};
|
||||
parts.reserve(it->parts.size());
|
||||
|
||||
bool erroredWithUnknown = false;
|
||||
|
||||
for (auto part : it->parts)
|
||||
{
|
||||
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(part, location, ctx);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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, {}, {}};
|
||||
}
|
||||
|
||||
|
@ -3495,7 +3489,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
|
|||
, ltFunc{"lt", ltTypeFunction}
|
||||
, leFunc{"le", leTypeFunction}
|
||||
, eqFunc{"eq", eqTypeFunction}
|
||||
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization2}
|
||||
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization3}
|
||||
, singletonFunc{"singleton", singletonTypeFunction}
|
||||
, unionFunc{"union", unionTypeFunction}
|
||||
, intersectFunc{"intersect", intersectTypeFunction}
|
||||
|
|
|
@ -150,4 +150,11 @@ bool TypeIds::operator==(const TypeIds& there) const
|
|||
return true;
|
||||
}
|
||||
|
||||
std::vector<TypeId> TypeIds::take()
|
||||
{
|
||||
hash = 0;
|
||||
types.clear();
|
||||
return std::move(order);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs)
|
||||
|
||||
namespace Luau
|
||||
|
@ -306,7 +306,7 @@ TypePack extendTypePack(
|
|||
TypePack newPack;
|
||||
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
@ -588,7 +588,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
|
|||
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
if (!FFlag::LuauEagerGeneralization2)
|
||||
if (!FFlag::LuauEagerGeneralization3)
|
||||
return;
|
||||
|
||||
for (; scope; scope = scope->parent.get())
|
||||
|
@ -695,5 +695,14 @@ bool isRecord(const AstExprTable::Item& item)
|
|||
return false;
|
||||
}
|
||||
|
||||
AstExpr* unwrapGroup(AstExpr* expr)
|
||||
{
|
||||
while (auto group = expr->as<AstExprGroup>())
|
||||
expr = group->expr;
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -329,9 +329,9 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
|
|||
|
||||
for (TypePackId genericPack : subFn->genericPacks)
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
genericPack = follow(genericPack);
|
||||
|
||||
// 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->indexResultType, superTable->indexer->indexResultType);
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// FIXME: We can probably do something more efficient here.
|
||||
result &= unify(superTable->indexer->indexType, subTable->indexer->indexType);
|
||||
|
|
|
@ -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
|
||||
// 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[] = {
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||
"StudioReportLuauAny2", // takes telemetry data for usage of any types
|
||||
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||
"StudioReportLuauAny2", // takes telemetry data for usage of any types
|
||||
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
|
||||
"LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly
|
||||
"LuauSolverV2",
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
|
|
|
@ -185,7 +185,6 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/include/Luau/DataFlowGraph.h
|
||||
Analysis/include/Luau/DcrLogger.h
|
||||
Analysis/include/Luau/Def.h
|
||||
Analysis/include/Luau/Differ.h
|
||||
Analysis/include/Luau/Documentation.h
|
||||
Analysis/include/Luau/Error.h
|
||||
Analysis/include/Luau/EqSatSimplification.h
|
||||
|
@ -265,7 +264,6 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/src/DataFlowGraph.cpp
|
||||
Analysis/src/DcrLogger.cpp
|
||||
Analysis/src/Def.cpp
|
||||
Analysis/src/Differ.cpp
|
||||
Analysis/src/EmbeddedBuiltinDefinitions.cpp
|
||||
Analysis/src/Error.cpp
|
||||
Analysis/src/EqSatSimplification.cpp
|
||||
|
@ -464,9 +462,6 @@ if(TARGET Luau.UnitTest)
|
|||
tests/CostModel.test.cpp
|
||||
tests/DataFlowGraph.test.cpp
|
||||
tests/DenseHash.test.cpp
|
||||
tests/DiffAsserts.cpp
|
||||
tests/DiffAsserts.h
|
||||
tests/Differ.test.cpp
|
||||
tests/EqSat.language.test.cpp
|
||||
tests/EqSat.propositional.test.cpp
|
||||
tests/EqSat.slice.test.cpp
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauGcAgainstOom, false)
|
||||
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
|
||||
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.
|
||||
** 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
|
||||
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;
|
||||
}
|
||||
|
@ -545,6 +567,26 @@ static int isobjcleared(GCObject* 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
|
||||
*/
|
||||
|
@ -592,7 +634,12 @@ static size_t cleartable(lua_State* L, GCObject* l)
|
|||
{
|
||||
// shrink at 37.5% occupancy
|
||||
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)
|
||||
{
|
||||
global_State* g = L->global;
|
||||
// check size of string hash
|
||||
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)
|
||||
|
@ -651,7 +722,12 @@ static void shrinkbuffersfull(lua_State* L)
|
|||
while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2)
|
||||
hashsize /= 2;
|
||||
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)
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStringFormatFixC, false)
|
||||
|
||||
// macro to `unsign' a character
|
||||
#define uchar(c) ((unsigned char)(c))
|
||||
|
||||
|
@ -1001,17 +999,9 @@ static int str_format(lua_State* L)
|
|||
{
|
||||
case 'c':
|
||||
{
|
||||
if (DFFlag::LuauStringFormatFixC)
|
||||
{
|
||||
int count = snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
|
||||
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;
|
||||
}
|
||||
int count = snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
|
||||
luaL_addlstring(&b, buff, count);
|
||||
continue; // skip the 'luaL_addlstring' at the end
|
||||
}
|
||||
case 'd':
|
||||
case 'i':
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauExpectedTypeVisitor)
|
||||
|
||||
using namespace Luau;
|
||||
|
@ -4468,7 +4468,7 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
|
|||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// This `if` statement is because `LuauEagerGeneralization2`
|
||||
// sets some flags
|
||||
|
|
|
@ -36,11 +36,11 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauAbortingChecks)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
|
||||
LUAU_FASTFLAG(LuauYieldableContinuations)
|
||||
LUAU_FASTFLAG(LuauCurrentLineBounds)
|
||||
LUAU_FASTFLAG(LuauLoadNoOomThrow)
|
||||
LUAU_FASTFLAG(LuauHeapNameDetails)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauGcAgainstOom)
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
{
|
||||
|
@ -713,8 +713,6 @@ TEST_CASE("Clear")
|
|||
|
||||
TEST_CASE("Strings")
|
||||
{
|
||||
ScopedFastFlag luauStringFormatFixC{DFFlag::LuauStringFormatFixC, true};
|
||||
|
||||
runConformance("strings.luau");
|
||||
}
|
||||
|
||||
|
@ -768,9 +766,48 @@ TEST_CASE("Attrib")
|
|||
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")
|
||||
{
|
||||
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")
|
||||
|
|
|
@ -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
|
|
@ -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
|
@ -2,7 +2,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/Differ.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Frontend.h"
|
||||
|
@ -30,6 +29,7 @@ LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
|
|||
|
||||
LUAU_FASTFLAG(LuauTypeFunOptional)
|
||||
LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
|
||||
#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:
|
||||
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, 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.
|
||||
// This is useful for tracking down violations of Luau's memory model.
|
||||
|
@ -234,84 +235,6 @@ const E* findError(const CheckResult& result)
|
|||
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
|
||||
|
||||
#define LUAU_REQUIRE_ERRORS(result) \
|
||||
|
|
|
@ -3406,7 +3406,6 @@ local foo = 8)");
|
|||
CHECK(*pos == Position{2, 0});
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalReturn_assert")
|
||||
{
|
||||
const std::string source = R"()";
|
||||
|
@ -3452,7 +3451,6 @@ return target(bar)";
|
|||
}
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_finished_defining")
|
||||
{
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
|
||||
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
|
||||
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
|
||||
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})")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
|
||||
|
||||
TableType tt;
|
||||
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})) -> ()")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
|
||||
|
||||
auto [aTy, aFree] = freshType();
|
||||
auto [bTy, bFree] = freshType();
|
||||
|
@ -342,7 +342,7 @@ end
|
|||
|
||||
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
|
||||
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();
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2);
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3);
|
||||
|
||||
TEST_SUITE_BEGIN("InferPolarity");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
|
||||
|
||||
TypeArena arena;
|
||||
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LintRedundantNativeAttribute);
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute);
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2);
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1941,9 +1941,6 @@ print(foo:bar(2.0))
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
|
||||
{
|
||||
// FIXME: For now this flag causes a stack overflow on Windows.
|
||||
ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false};
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local t = {}
|
||||
local tt = {}
|
||||
|
|
|
@ -14,14 +14,9 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
|
||||
LUAU_FASTINT(LuauNormalizeUnionLimit)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1071,6 +1066,26 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
|
|||
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")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
@ -1200,13 +1215,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRefineWaitForBlockedTypesInTarget, true},
|
||||
{FFlag::LuauSimplifyOutOfLine, true},
|
||||
{FFlag::LuauSubtypeGenericsAndNegations, true},
|
||||
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
|
||||
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
|
||||
{FFlag::LuauEagerGeneralization2, true}
|
||||
};
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
||||
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
#include <initializer_list>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1619,8 +1618,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings")
|
|||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSubtypeGenericsAndNegations, true};
|
||||
|
||||
// <A, B>(x: A, y: B) -> (A & ~(false?)) | B
|
||||
// (~(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")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization3, true};
|
||||
|
||||
TypeId argTy = arena.freshType(builtinTypes, moduleScope.get());
|
||||
FreeType* freeArg = getMutable<FreeType>(argTy);
|
||||
|
|
|
@ -14,9 +14,8 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs)
|
||||
|
||||
|
@ -159,7 +158,7 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
|
|||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true};
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
@ -1652,7 +1651,8 @@ type foo<T> = { a: add<T, T>, b : add<T, T> }
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "len_typefun_on_metatable")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = setmetatable({}, { __mode = "v" })
|
||||
|
@ -1669,7 +1669,6 @@ end
|
|||
TEST_CASE_FIXTURE(BuiltinsFixture, "has_prop_on_irreducible_type_function")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauHasPropProperBlock{FFlag::LuauHasPropProperBlock, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local test = "a" + "b"
|
||||
|
@ -1725,7 +1724,7 @@ struct TFFixture
|
|||
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
|
||||
|
||||
const ScopedFastFlag sff[1] = {
|
||||
{FFlag::LuauEagerGeneralization2, true},
|
||||
{FFlag::LuauEagerGeneralization3, true},
|
||||
};
|
||||
|
||||
BuiltinTypeFunctions builtinTypeFunctions;
|
||||
|
|
|
@ -9,7 +9,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
|
||||
|
||||
|
@ -409,7 +409,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works_on_unions")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -458,7 +460,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -649,7 +653,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function hello(arg)
|
||||
|
@ -1098,7 +1106,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo(x)
|
||||
|
@ -1112,7 +1121,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1164,7 +1175,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1211,7 +1224,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recovery_no_upvalues")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_follow")
|
||||
{
|
||||
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type t0 = any
|
||||
|
@ -1226,7 +1240,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_follow")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1251,7 +1267,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "no_type_methods_on_types")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function test(x)
|
||||
|
@ -1377,7 +1394,8 @@ local a: foo<> = "a"
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "implicit_export")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
type function concat(a: type, b: type)
|
||||
|
@ -1430,7 +1448,8 @@ local a = test()
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::DebugLuauEqSatSimplification, true}};
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sffs[] = {{FFlag::DebugLuauEqSatSimplification, true}};
|
||||
|
||||
CheckResult _ = check(R"(
|
||||
type function t0(a)
|
||||
|
@ -1988,7 +2012,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
|
|||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// FIXME: CLI-151985
|
||||
// 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};
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// FIXME: CLI-151985
|
||||
// This test breaks because we can't see that eq<type?, b> is already fully reduced.
|
||||
|
|
|
@ -11,8 +11,9 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
|
||||
LUAU_FASTFLAG(LuauStringFormatImprovements)
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
||||
|
@ -459,9 +460,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2)
|
||||
CHECK("{ [number]: string | string | string, n: number }" == toString(requireType("t")));
|
||||
else if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));
|
||||
else
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
|
||||
TEST_SUITE_BEGIN("DefinitionTests");
|
||||
|
||||
|
@ -558,7 +558,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauSimplifyOutOfLine, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -22,14 +22,13 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
|
||||
LUAU_FASTFLAG(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAG(LuauFormatUseLastPosition)
|
||||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = {{x=4}, {x=7}, {x=1}}
|
||||
table.sort(a, function(x, y) return x.x < y.x end)
|
||||
|
@ -1683,7 +1686,7 @@ t.f = function(x)
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
|
||||
{
|
||||
// FIXME CLI-151985
|
||||
LUAU_CHECK_ERROR_COUNT(3, result);
|
||||
|
@ -1768,7 +1771,7 @@ t.f = function(x)
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
|
||||
{
|
||||
// FIXME CLI-151985
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")));
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_CHECK_NO_ERRORS(result);
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSimplifyOutOfLine, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
{FFlag::LuauTableLiteralSubtypeSpecificCheck, true},
|
||||
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauFormatUseLastPosition, true};
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local function foo(): (string, string, string)
|
||||
return "", "", ""
|
||||
|
@ -3170,7 +3169,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauFormatUseLastPosition, true};
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local foo : () -> (...string) = (nil :: any)
|
||||
print(string.format("%s %s %s", foo()))
|
||||
|
|
|
@ -15,6 +15,10 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
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")
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauSubtypingCheckFunctionGenericCounts, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type C = () -> ()
|
||||
|
@ -790,14 +794,64 @@ local c: 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")
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauSubtypingCheckFunctionGenericCounts, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type C = () -> ()
|
||||
|
@ -807,12 +861,26 @@ local c: 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(
|
||||
toString(result.errors[0]),
|
||||
R"(Type '() -> ()' could not be converted into '<T...>() -> ()'; different number of generic type pack 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 pack 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")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true};
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test2(a: number, b: string)
|
||||
|
@ -980,17 +1048,7 @@ wrapper(test2, 1, "", 3)
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
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)");
|
||||
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")
|
||||
|
@ -1364,19 +1422,13 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded"
|
|||
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")
|
||||
{
|
||||
|
||||
ScopedFastFlag _[] = {
|
||||
{FFlag::LuauReportSubtypingErrors, true},
|
||||
{FFlag::LuauSubtypingCheckFunctionGenericCounts, true},
|
||||
{FFlag::LuauEagerGeneralization3, true},
|
||||
};
|
||||
CheckResult result;
|
||||
|
||||
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
|
||||
)");
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -1406,9 +1459,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
|
|||
local b = sumrec(sum) -- ok
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
|
||||
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
|
||||
|
||||
|
@ -188,7 +188,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements"
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauAddCallConstraintForIterableFunctions, true},
|
||||
{FFlag::LuauSimplifyOutOfLine, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
{FFlag::LuauDfgAllowUpdatesInLoops, true},
|
||||
};
|
||||
|
||||
|
@ -1407,8 +1407,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1413")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_error_in_body")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDfgAllowUpdatesInLoops, true},
|
||||
};
|
||||
|
||||
|
@ -1499,8 +1501,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_unconditionally_fires_error")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
|
||||
{FFlag::LuauDfgAllowUpdatesInLoops, true},
|
||||
};
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
|
||||
using namespace Luau;
|
||||
|
@ -746,14 +745,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "spooky_blocked_type_laundered_by_bound_type"
|
|||
local _ = require(game.A);
|
||||
)");
|
||||
|
||||
if (FFlag::LuauAddCallConstraintForIterableFunctions && !FFlag::LuauOptimizeFalsyAndTruthyIntersect)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics")
|
||||
|
@ -785,7 +777,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23})));
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferOperators");
|
||||
|
||||
|
@ -29,7 +29,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
|
|||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// FIXME: Regression
|
||||
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);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// FIXME: Regression.
|
||||
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);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
// FIXME: Regression
|
||||
CHECK("(string & ~(false?)) | string" == toString(requireType("s")));
|
||||
|
@ -634,7 +634,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
|
|||
local a = -foo
|
||||
)");
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
// 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/VisitType.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
#include "DiffAsserts.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
|
@ -32,7 +27,7 @@ TEST_CASE_FIXTURE(Fixture, "string_length")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ_DIFF(builtinTypes->numberType, requireType("t"));
|
||||
CHECK_EQ(builtinTypes->numberType, requireType("t"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_index")
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
|
||||
LUAU_FASTFLAG(LuauWeakNilRefinementType)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauSimplificationTableExternType)
|
||||
LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
|
||||
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
|
||||
LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes)
|
||||
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauWeakNilRefinementType, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauWeakNilRefinementType, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = {"hello"}
|
||||
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);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 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
|
||||
)");
|
||||
|
||||
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.
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 15})));
|
||||
|
@ -1664,7 +1688,10 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_sh
|
|||
else
|
||||
{
|
||||
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")
|
||||
{
|
||||
// FIXME CLI-141364: An underlying bug in normalization means the type of
|
||||
// `isIndexKey` is platform dependent.
|
||||
CheckResult result = check(R"(
|
||||
local function isIndexKey(k, contiguousLength: 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
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
|
@ -2505,7 +2533,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauWeakNilRefinementType, true},
|
||||
{FFlag::DebugLuauEqSatSimplification, true},
|
||||
};
|
||||
|
||||
|
@ -2517,7 +2544,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
|
|||
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})));
|
||||
else
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauWeakNilRefinementType, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
--!strict
|
||||
function returns_two(): number
|
||||
|
@ -2639,7 +2665,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1687_equality_shouldnt_leak_nil")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauWeakNilRefinementType, true}};
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
type Part = {
|
||||
|
@ -2740,4 +2766,37 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1835")
|
|||
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();
|
||||
|
|
|
@ -21,20 +21,16 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
|
||||
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
|
||||
LUAU_FASTFLAG(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
|
||||
LUAU_FASTFLAG(LuauDisablePrimitiveInferenceInLargeTables)
|
||||
LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
|
||||
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauAutocompleteMissingFollows)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -702,7 +698,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization3)
|
||||
CHECK("<a>({a}) -> ()" == toString(requireType("swap")));
|
||||
else if (FFlag::LuauSolverV2)
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true};
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true};
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
)");
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauEagerGeneralization3 && FFlag::LuauSolverV2)
|
||||
{
|
||||
// FIXME CLI-151985
|
||||
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")));
|
||||
}
|
||||
|
||||
// TODO: CLI-39624
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_tables_at_scope_level")
|
||||
{
|
||||
ScopedFastFlag sff1{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local Option = {}
|
||||
|
@ -3749,9 +3746,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauReportSubtypingErrors, true},
|
||||
{FFlag::LuauEagerGeneralization2, true},
|
||||
{FFlag::LuauSubtypeGenericsAndNegations, true},
|
||||
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}
|
||||
{FFlag::LuauEagerGeneralization3, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -4644,7 +4639,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
|
|||
return;
|
||||
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true}
|
||||
{FFlag::LuauEagerGeneralization3, true},
|
||||
};
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x, y)
|
||||
if x then
|
||||
|
@ -4696,7 +4689,7 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization3)
|
||||
{
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
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);
|
||||
|
||||
// FIXME CLI-114134. We need to simplify types more consistently.
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f")));
|
||||
|
@ -5038,7 +5031,7 @@ end
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true};
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauBidirectionalInferenceElideAssert, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(_: { [string]: {unknown}} ) end
|
||||
f(
|
||||
|
|
|
@ -24,22 +24,16 @@ LUAU_FASTINT(LuauNormalizeCacheLimit)
|
|||
LUAU_FASTINT(LuauRecursionLimit)
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
|
||||
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
|
||||
LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2)
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAG(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAG(LuauStringPartLengthLimit)
|
||||
LUAU_FASTFLAG(LuauSimplificationRecheckAssumption)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
|
||||
LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
|
||||
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
|
||||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauSkipLvalueForCompoundAssignment)
|
||||
LUAU_FASTFLAG(LuauMissingFollowInAssignIndexConstraint)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -447,7 +441,7 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
|
|||
#endif
|
||||
ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100};
|
||||
ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100};
|
||||
ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false};
|
||||
ScopedFastFlag _{FFlag::LuauEagerGeneralization3, false};
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
auto result = check(R"(
|
||||
local function check(x): any
|
||||
return getmetatable(x)
|
||||
end
|
||||
)");
|
||||
|
||||
// CLI-144695: We're leaking the `MT` generic here.
|
||||
CHECK_EQ("({ @metatable MT, {+ +} }) -> any", toString(requireType("check")));
|
||||
if (FFlag::LuauSolverV2)
|
||||
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")
|
||||
|
@ -1929,7 +1923,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_derived_unsound_loops")
|
|||
TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag fixNumberConcats{FFlag::LuauTypeCheckerAcceptNumberConcats, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPreprocessTypestatedArgument, true},
|
||||
};
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local _
|
||||
|
@ -2034,11 +2024,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauHasPropProperBlock, true},
|
||||
{FFlag::LuauEagerGeneralization2, true},
|
||||
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
|
||||
{FFlag::LuauSubtypeGenericsAndNegations, true},
|
||||
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
|
||||
{FFlag::LuauEagerGeneralization3, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
|
@ -2073,10 +2059,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization2, true},
|
||||
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
|
||||
{FFlag::LuauSubtypeGenericsAndNegations, true},
|
||||
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
|
||||
{FFlag::LuauEagerGeneralization3, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -2109,13 +2092,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauHasPropProperBlock, true},
|
||||
{FFlag::LuauEagerGeneralization2, true},
|
||||
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
|
||||
{FFlag::LuauStringPartLengthLimit, true},
|
||||
{FFlag::LuauSimplificationRecheckAssumption, true},
|
||||
{FFlag::LuauSubtypeGenericsAndNegations, true},
|
||||
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
|
||||
};
|
||||
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
|
@ -2154,7 +2131,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::tim
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
|
||||
{FFlag::LuauAvoidDoubleNegation, true},
|
||||
};
|
||||
// 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))
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true};
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
_[_] += 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();
|
||||
|
|
|
@ -12,9 +12,8 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauFixEmptyTypePackStringification)
|
||||
|
||||
|
@ -100,7 +99,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function")
|
|||
|
||||
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")));
|
||||
else
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}};
|
||||
ScopedFastFlag _{FFlag::LuauReportSubtypingErrors, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any)
|
||||
|
|
|
@ -4,16 +4,12 @@
|
|||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
|
||||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
|
||||
LUAU_FASTFLAG(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
|
||||
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -418,9 +414,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur
|
|||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauReportSubtypingErrors, true},
|
||||
{FFlag::LuauEagerGeneralization2, true},
|
||||
{FFlag::LuauSubtypeGenericsAndNegations, true},
|
||||
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
|
||||
{FFlag::LuauEagerGeneralization3, true},
|
||||
};
|
||||
|
||||
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))
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauRefineWaitForBlockedTypesInTarget, true};
|
||||
// We do not care about the errors here, only that this finishes typing
|
||||
// in a sensible amount of time.
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
|
|
|
@ -10,7 +10,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization3)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
|
||||
|
||||
TEST_SUITE_BEGIN("UnionTypes");
|
||||
|
@ -895,7 +895,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization2)
|
||||
if (FFlag::LuauEagerGeneralization3)
|
||||
CHECK_EQ(
|
||||
"<a>(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions);
|
||||
|
||||
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);
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauNoMoreInjectiveTypeFunctions)
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// FIXME: CLI-152325
|
||||
CHECK_EQ("(nil, nil & ~nil) -> boolean", toString(requireType("ord")));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ("(nil, unknown) -> boolean", toString(requireType("ord")));
|
||||
else
|
||||
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("ord")));
|
||||
}
|
||||
|
|
|
@ -186,6 +186,49 @@ for i=1,lim do a[{}] = i end
|
|||
collectgarbage()
|
||||
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
|
||||
collectgarbage("stop") -- stop collection
|
||||
local u = newproxy(true)
|
||||
|
|
Loading…
Add table
Reference in a new issue