mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Merge branch 'upstream' into merge
This commit is contained in:
commit
0d6b70b80b
@ -321,6 +321,11 @@ private:
|
||||
*/
|
||||
void checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn);
|
||||
|
||||
// Specializations of 'resolveType' below
|
||||
TypeId resolveReferenceType(const ScopePtr& scope, AstType* ty, AstTypeReference* ref, bool inTypeArguments, bool replaceErrorWithFresh);
|
||||
TypeId resolveTableType(const ScopePtr& scope, AstType* ty, AstTypeTable* tab, bool inTypeArguments, bool replaceErrorWithFresh);
|
||||
TypeId resolveFunctionType(const ScopePtr& scope, AstType* ty, AstTypeFunction* fn, bool inTypeArguments, bool replaceErrorWithFresh);
|
||||
|
||||
/**
|
||||
* Resolves a type from its AST annotation.
|
||||
* @param scope the scope that the type annotation appears within.
|
||||
|
@ -96,6 +96,22 @@ struct SubtypingEnvironment
|
||||
DenseHashSet<TypeId> upperBound{nullptr};
|
||||
};
|
||||
|
||||
/* For nested subtyping relationship tests of mapped generic bounds, we keep the outer environment immutable */
|
||||
SubtypingEnvironment* parent = nullptr;
|
||||
|
||||
/// Applies `mappedGenerics` to the given type.
|
||||
/// This is used specifically to substitute for generics in type function instances.
|
||||
std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
|
||||
|
||||
const TypeId* tryFindSubstitution(TypeId ty) const;
|
||||
const SubtypingResult* tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const;
|
||||
|
||||
bool containsMappedType(TypeId ty) const;
|
||||
bool containsMappedPack(TypePackId tp) const;
|
||||
|
||||
GenericBounds& getMappedTypeBounds(TypeId ty);
|
||||
TypePackId* getMappedPackBounds(TypePackId tp);
|
||||
|
||||
/*
|
||||
* When we encounter a generic over the course of a subtyping test, we need
|
||||
* to tentatively map that generic onto a type on the other side.
|
||||
@ -112,10 +128,6 @@ struct SubtypingEnvironment
|
||||
DenseHashMap<TypeId, TypeId> substitutions{nullptr};
|
||||
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
|
||||
|
||||
/// Applies `mappedGenerics` to the given type.
|
||||
/// This is used specifically to substitute for generics in type function instances.
|
||||
std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
|
||||
};
|
||||
|
||||
struct Subtyping
|
||||
|
@ -13,7 +13,12 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAutocompleteNewSolverLimit)
|
||||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
||||
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
@ -144,6 +149,12 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
if (FFlag::LuauAutocompleteNewSolverLimit)
|
||||
{
|
||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
||||
}
|
||||
|
||||
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}};
|
||||
|
||||
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
||||
@ -199,6 +210,9 @@ static TypeCorrectKind checkTypeCorrectKind(
|
||||
{
|
||||
for (TypeId id : itv->parts)
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
id = follow(id);
|
||||
|
||||
if (const FunctionType* ftv = get<FunctionType>(id); ftv && checkFunctionType(ftv))
|
||||
{
|
||||
return TypeCorrectKind::CorrectFunctionResult;
|
||||
|
@ -2949,12 +2949,16 @@ void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFuncti
|
||||
addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType});
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
TypeId ConstraintGenerator::resolveReferenceType(
|
||||
const ScopePtr& scope,
|
||||
AstType* ty,
|
||||
AstTypeReference* ref,
|
||||
bool inTypeArguments,
|
||||
bool replaceErrorWithFresh
|
||||
)
|
||||
{
|
||||
TypeId result = nullptr;
|
||||
|
||||
if (auto ref = ty->as<AstTypeReference>())
|
||||
{
|
||||
if (FFlag::DebugLuauMagicTypes)
|
||||
{
|
||||
if (ref->name == "_luau_ice")
|
||||
@ -3037,15 +3041,17 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
if (replaceErrorWithFresh)
|
||||
result = freshType(scope);
|
||||
}
|
||||
}
|
||||
else if (auto tab = ty->as<AstTypeTable>())
|
||||
{
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::resolveTableType(const ScopePtr& scope, AstType* ty, AstTypeTable* tab, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
{
|
||||
TableType::Props props;
|
||||
std::optional<TableIndexer> indexer;
|
||||
|
||||
for (const AstTableProp& prop : tab->props)
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
|
||||
|
||||
Property& p = props[prop.name.value];
|
||||
@ -3079,7 +3085,6 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
|
||||
else if (astIndexer->access == AstTableAccess::ReadWrite)
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
indexer = TableIndexer{
|
||||
resolveType(scope, astIndexer->indexType, inTypeArguments),
|
||||
resolveType(scope, astIndexer->resultType, inTypeArguments),
|
||||
@ -3089,11 +3094,17 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
ice->ice("Unexpected property access " + std::to_string(int(astIndexer->access)));
|
||||
}
|
||||
|
||||
result = arena->addType(TableType{props, indexer, scope->level, scope.get(), TableState::Sealed});
|
||||
}
|
||||
else if (auto fn = ty->as<AstTypeFunction>())
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
return arena->addType(TableType{props, indexer, scope->level, scope.get(), TableState::Sealed});
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::resolveFunctionType(
|
||||
const ScopePtr& scope,
|
||||
AstType* ty,
|
||||
AstTypeFunction* fn,
|
||||
bool inTypeArguments,
|
||||
bool replaceErrorWithFresh
|
||||
)
|
||||
{
|
||||
bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0;
|
||||
ScopePtr signatureScope = nullptr;
|
||||
|
||||
@ -3154,11 +3165,27 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
}
|
||||
}
|
||||
|
||||
result = arena->addType(std::move(ftv));
|
||||
return arena->addType(std::move(ftv));
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
{
|
||||
TypeId result = nullptr;
|
||||
|
||||
if (auto ref = ty->as<AstTypeReference>())
|
||||
{
|
||||
result = resolveReferenceType(scope, ty, ref, inTypeArguments, replaceErrorWithFresh);
|
||||
}
|
||||
else if (auto tab = ty->as<AstTypeTable>())
|
||||
{
|
||||
result = resolveTableType(scope, ty, tab, inTypeArguments, replaceErrorWithFresh);
|
||||
}
|
||||
else if (auto fn = ty->as<AstTypeFunction>())
|
||||
{
|
||||
result = resolveFunctionType(scope, ty, fn, inTypeArguments, replaceErrorWithFresh);
|
||||
}
|
||||
else if (auto tof = ty->as<AstTypeTypeof>())
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
TypeId exprType = check(scope, tof->expr).ty;
|
||||
result = exprType;
|
||||
}
|
||||
@ -3167,7 +3194,6 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : unionAnnotation->types)
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
|
||||
@ -3178,7 +3204,6 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : intersectionAnnotation->types)
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||
}
|
||||
|
||||
|
@ -27,10 +27,14 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings, false);
|
||||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500);
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings, false)
|
||||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
||||
|
||||
// The default value here is 643 because the first release in which this was implemented is 644,
|
||||
// and actively we want new changes to be off by default until they're enabled consciously.
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSolverRelease, 643)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -21,11 +21,12 @@ LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixCyclicTablesBlowingStack, false);
|
||||
|
||||
// This could theoretically be 2000 on amd64, but x86 requires this.
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauUseNormalizeIntersectionLimit, false)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||
|
||||
static bool fixReduceStackPressure()
|
||||
{
|
||||
return FFlag::LuauFixReduceStackPressure || FFlag::LuauSolverV2;
|
||||
@ -3035,6 +3036,14 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
|
||||
return unionNormals(here, there, ignoreSmallerTyvars);
|
||||
}
|
||||
|
||||
if (FFlag::LuauUseNormalizeIntersectionLimit)
|
||||
{
|
||||
// Limit based on worst-case expansion of the table intersection
|
||||
// This restriction can be relaxed when table intersection simplification is improved
|
||||
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
||||
return NormalizationResult::HitLimits;
|
||||
}
|
||||
|
||||
here.booleans = intersectionOfBools(here.booleans, there.booleans);
|
||||
|
||||
intersectClasses(here.classes, there.classes);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/Substitution.h"
|
||||
@ -21,6 +22,8 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteNewSolverLimit, false);
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -264,37 +267,48 @@ struct ApplyMappedGenerics : Substitution
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeArena> arena;
|
||||
|
||||
MappedGenerics& mappedGenerics;
|
||||
MappedGenericPacks& mappedGenericPacks;
|
||||
SubtypingEnvironment& env;
|
||||
|
||||
MappedGenerics& mappedGenerics_DEPRECATED;
|
||||
MappedGenericPacks& mappedGenericPacks_DEPRECATED;
|
||||
|
||||
ApplyMappedGenerics(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
SubtypingEnvironment& env,
|
||||
MappedGenerics& mappedGenerics,
|
||||
MappedGenericPacks& mappedGenericPacks
|
||||
)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, arena(arena)
|
||||
, mappedGenerics(mappedGenerics)
|
||||
, mappedGenericPacks(mappedGenericPacks)
|
||||
, env(env)
|
||||
, mappedGenerics_DEPRECATED(mappedGenerics)
|
||||
, mappedGenericPacks_DEPRECATED(mappedGenericPacks)
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
return mappedGenerics.contains(ty);
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
return env.containsMappedType(ty);
|
||||
else
|
||||
return mappedGenerics_DEPRECATED.contains(ty);
|
||||
}
|
||||
|
||||
bool isDirty(TypePackId tp) override
|
||||
{
|
||||
return mappedGenericPacks.contains(tp);
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
return env.containsMappedPack(tp);
|
||||
else
|
||||
return mappedGenericPacks_DEPRECATED.contains(tp);
|
||||
}
|
||||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
const auto& bounds = mappedGenerics[ty];
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
const auto& bounds = env.getMappedTypeBounds(ty);
|
||||
|
||||
if (bounds.upperBound.empty())
|
||||
return builtinTypes->unknownType;
|
||||
@ -304,10 +318,35 @@ struct ApplyMappedGenerics : Substitution
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& bounds = mappedGenerics_DEPRECATED[ty];
|
||||
|
||||
if (bounds.upperBound.empty())
|
||||
return builtinTypes->unknownType;
|
||||
|
||||
if (bounds.upperBound.size() == 1)
|
||||
return *begin(bounds.upperBound);
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
}
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
return mappedGenericPacks[tp];
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (auto it = env.getMappedPackBounds(tp))
|
||||
return *it;
|
||||
|
||||
// Clean is only called when isDirty found a pack bound
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return mappedGenericPacks_DEPRECATED[tp];
|
||||
}
|
||||
}
|
||||
|
||||
bool ignoreChildren(TypeId ty) override
|
||||
@ -325,10 +364,78 @@ struct ApplyMappedGenerics : Substitution
|
||||
|
||||
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty)
|
||||
{
|
||||
ApplyMappedGenerics amg{builtinTypes, arena, mappedGenerics, mappedGenericPacks};
|
||||
ApplyMappedGenerics amg{builtinTypes, arena, *this, mappedGenerics, mappedGenericPacks};
|
||||
return amg.substitute(ty);
|
||||
}
|
||||
|
||||
const TypeId* SubtypingEnvironment::tryFindSubstitution(TypeId ty) const
|
||||
{
|
||||
if (auto it = substitutions.find(ty))
|
||||
return it;
|
||||
|
||||
if (parent)
|
||||
return parent->tryFindSubstitution(ty);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const
|
||||
{
|
||||
if (auto it = ephemeralCache.find(subAndSuper))
|
||||
return it;
|
||||
|
||||
if (parent)
|
||||
return parent->tryFindSubtypingResult(subAndSuper);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool SubtypingEnvironment::containsMappedType(TypeId ty) const
|
||||
{
|
||||
if (mappedGenerics.contains(ty))
|
||||
return true;
|
||||
|
||||
if (parent)
|
||||
return parent->containsMappedType(ty);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const
|
||||
{
|
||||
if (mappedGenericPacks.contains(tp))
|
||||
return true;
|
||||
|
||||
if (parent)
|
||||
return parent->containsMappedPack(tp);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(TypeId ty)
|
||||
{
|
||||
if (auto it = mappedGenerics.find(ty))
|
||||
return *it;
|
||||
|
||||
if (parent)
|
||||
return parent->getMappedTypeBounds(ty);
|
||||
|
||||
LUAU_ASSERT(!"Use containsMappedType before asking for bounds!");
|
||||
return mappedGenerics[ty];
|
||||
}
|
||||
|
||||
TypePackId* SubtypingEnvironment::getMappedPackBounds(TypePackId tp)
|
||||
{
|
||||
if (auto it = mappedGenericPacks.find(tp))
|
||||
return it;
|
||||
|
||||
if (parent)
|
||||
return parent->getMappedPackBounds(tp);
|
||||
|
||||
// This fallback is reachable in valid cases, unlike the final part of getMappedTypeBounds
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Subtyping::Subtyping(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> typeArena,
|
||||
@ -379,11 +486,24 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
||||
result.isSubtype = false;
|
||||
}
|
||||
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
SubtypingEnvironment boundsEnv;
|
||||
boundsEnv.parent = &env;
|
||||
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound, scope);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: We presently don't store subtype test results in the persistent
|
||||
* cache if the left-side type is a generic function.
|
||||
@ -442,20 +562,36 @@ struct SeenSetPopper
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope)
|
||||
{
|
||||
std::optional<RecursionCounter> rc;
|
||||
|
||||
if (FFlag::LuauAutocompleteNewSolverLimit)
|
||||
{
|
||||
UnifierCounters& counters = normalizer->sharedState->counters;
|
||||
rc.emplace(&counters.recursionCount);
|
||||
|
||||
if (counters.recursionLimit > 0 && counters.recursionLimit < counters.recursionCount)
|
||||
{
|
||||
SubtypingResult result;
|
||||
result.normalizationTooComplex = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
|
||||
if (TypeId* subIt = env.substitutions.find(subTy); subIt && *subIt)
|
||||
if (const TypeId* subIt = (DFInt::LuauTypeSolverRelease >= 644 ? env.tryFindSubstitution(subTy) : env.substitutions.find(subTy)); subIt && *subIt)
|
||||
subTy = *subIt;
|
||||
|
||||
if (TypeId* superIt = env.substitutions.find(superTy); superIt && *superIt)
|
||||
if (const TypeId* superIt = (DFInt::LuauTypeSolverRelease >= 644 ? env.tryFindSubstitution(superTy) : env.substitutions.find(superTy));
|
||||
superIt && *superIt)
|
||||
superTy = *superIt;
|
||||
|
||||
SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
|
||||
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
|
||||
if (cachedResult)
|
||||
return *cachedResult;
|
||||
|
||||
cachedResult = env.ephemeralCache.find({subTy, superTy});
|
||||
cachedResult = DFInt::LuauTypeSolverRelease >= 644 ? env.tryFindSubtypingResult({subTy, superTy}) : env.ephemeralCache.find({subTy, superTy});
|
||||
if (cachedResult)
|
||||
return *cachedResult;
|
||||
|
||||
@ -700,7 +836,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
|
||||
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
||||
|
||||
if (TypePackId* other = env.mappedGenericPacks.find(*subTail))
|
||||
if (TypePackId* other =
|
||||
(DFInt::LuauTypeSolverRelease >= 644 ? env.getMappedPackBounds(*subTail) : env.mappedGenericPacks.find(*subTail)))
|
||||
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||
results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail));
|
||||
else
|
||||
@ -755,7 +892,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
|
||||
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
||||
|
||||
if (TypePackId* other = env.mappedGenericPacks.find(*superTail))
|
||||
if (TypePackId* other =
|
||||
(DFInt::LuauTypeSolverRelease >= 644 ? env.getMappedPackBounds(*superTail) : env.mappedGenericPacks.find(*superTail)))
|
||||
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
|
||||
else
|
||||
@ -1688,6 +1826,12 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
|
||||
if (!get<GenericType>(subTy))
|
||||
return false;
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (!env.mappedGenerics.find(subTy) && env.containsMappedType(subTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
}
|
||||
|
||||
env.mappedGenerics[subTy].upperBound.insert(superTy);
|
||||
}
|
||||
else
|
||||
@ -1695,6 +1839,12 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
|
||||
if (!get<GenericType>(superTy))
|
||||
return false;
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (!env.mappedGenerics.find(superTy) && env.containsMappedType(superTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
}
|
||||
|
||||
env.mappedGenerics[superTy].lowerBound.insert(subTy);
|
||||
}
|
||||
|
||||
@ -1740,7 +1890,7 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePac
|
||||
if (!get<GenericTypePack>(subTp))
|
||||
return false;
|
||||
|
||||
if (TypePackId* m = env.mappedGenericPacks.find(subTp))
|
||||
if (TypePackId* m = (DFInt::LuauTypeSolverRelease >= 644 ? env.getMappedPackBounds(subTp) : env.mappedGenericPacks.find(subTp)))
|
||||
return *m == superTp;
|
||||
|
||||
env.mappedGenericPacks[subTp] = superTp;
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <ostream>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -3012,12 +3013,21 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
|
||||
if (tt->indexer)
|
||||
{
|
||||
TypeId indexType = follow(tt->indexer->indexType);
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
TypeId givenType = module->internalTypes.addType(SingletonType{StringSingleton{prop}});
|
||||
if (isSubtype(givenType, indexType, NotNull{module->getModuleScope().get()}, builtinTypes, *ice))
|
||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isPrim(indexType, PrimitiveType::String))
|
||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
||||
// If the indexer looks like { [any] : _} - the prop lookup should be allowed!
|
||||
else if (get<AnyType>(indexType) || get<UnknownType>(indexType))
|
||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we are in a conditional context, we treat the property as present and `unknown` because
|
||||
|
@ -37,6 +37,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false);
|
||||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -669,8 +671,16 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
||||
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(lenTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
@ -758,8 +768,16 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
||||
if (normTy->isExactlyNumber())
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(unmTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
@ -2208,9 +2226,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
||||
TypeId indexerTy = follow(typeParams.at(1));
|
||||
|
||||
if (isPending(indexerTy, ctx->solver))
|
||||
{
|
||||
return {std::nullopt, false, {indexerTy}, {}};
|
||||
}
|
||||
|
||||
std::shared_ptr<const NormalizedType> indexerNormTy = ctx->normalizer->normalize(indexerTy);
|
||||
|
||||
|
@ -15,6 +15,9 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauAutocompleteNewSolverLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauUseNormalizeIntersectionLimit)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -3815,6 +3818,36 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_response_perf1" * doctest::timeout(0.
|
||||
CHECK(ac.entryMap.count("Instance"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_subtyping_recursion_limit")
|
||||
{
|
||||
ScopedFastFlag luauAutocompleteNewSolverLimit{FFlag::LuauAutocompleteNewSolverLimit, true};
|
||||
ScopedFastInt luauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 10};
|
||||
|
||||
const int parts = 100;
|
||||
std::string source;
|
||||
|
||||
source += "function f()\n";
|
||||
|
||||
std::string prefix;
|
||||
for (int i = 0; i < parts; i++)
|
||||
formatAppend(prefix, "(nil|({a%d:number}&", i);
|
||||
formatAppend(prefix, "(nil|{a%d:number})", parts);
|
||||
for (int i = 0; i < parts; i++)
|
||||
formatAppend(prefix, "))");
|
||||
|
||||
source += "local x1 : " + prefix + "\n";
|
||||
source += "local y : {a1:number} = x@1\n";
|
||||
|
||||
source += "end\n";
|
||||
|
||||
check(source);
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
CHECK(ac.entryMap.count("true"));
|
||||
CHECK(ac.entryMap.count("x1"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "strict_mode_force")
|
||||
{
|
||||
check(R"(
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "doctest.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <iostream>
|
||||
@ -27,6 +28,7 @@ LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
|
||||
LUAU_FASTFLAG(LuauDCRMagicFunctionTypeChecker);
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
extern std::optional<unsigned> randomSeed; // tests/main.cpp
|
||||
|
||||
@ -152,8 +154,12 @@ const Config& TestConfigResolver::getConfig(const ModuleName& name) const
|
||||
|
||||
Fixture::Fixture(bool freeze, bool prepareAutocomplete)
|
||||
: sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, freeze)
|
||||
// In tests, we *always* want to register the extra magic functions for typechecking `string.format`.
|
||||
, sff_LuauDCRMagicFunctionTypeChecker(FFlag::LuauDCRMagicFunctionTypeChecker, true)
|
||||
// The first value of LuauTypeSolverRelease was 643, so as long as this is
|
||||
// some number greater than 900 (5 years worth of releases), all tests that
|
||||
// run under the new solver will run against all of the changes guarded by
|
||||
// this flag.
|
||||
, sff_LuauTypeSolverRelease(DFInt::LuauTypeSolverRelease, std::numeric_limits<int>::max())
|
||||
, frontend(
|
||||
&fileResolver,
|
||||
&configResolver,
|
||||
|
@ -98,9 +98,37 @@ struct Fixture
|
||||
TypeId requireTypeAlias(const std::string& name);
|
||||
TypeId requireExportedType(const ModuleName& moduleName, const std::string& name);
|
||||
|
||||
// TODO: Should this be in a container of some kind? Seems a little silly
|
||||
// to have a bunch of flags sitting on the text fixture.
|
||||
|
||||
// We have a couple flags that are OK to set for all tests and, in some
|
||||
// cases, cannot easily be flipped on or off on a per-test basis. For these
|
||||
// we set them as part of constructing the test fixture.
|
||||
|
||||
/* From the original commit:
|
||||
*
|
||||
* > This enables arena freezing for all but two unit tests. 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.
|
||||
*/
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena;
|
||||
|
||||
/* Magic typechecker functions for the new solver are initialized when the
|
||||
* typechecker frontend is initialized, which is done at the beginning of
|
||||
* the test: we set this flag as part of the fixture as we always want to
|
||||
* enable the magic functions for, say, `string.format`.
|
||||
*/
|
||||
ScopedFastFlag sff_LuauDCRMagicFunctionTypeChecker;
|
||||
|
||||
/* While the new solver is being rolled out we are using a monotonically
|
||||
* increasing version number to track new changes, we just set it to a
|
||||
* sufficiently high number in tests to ensure that any guards in prod
|
||||
* code pass in tests (so we don't accidentally reintroduce a bug before
|
||||
* it's unflagged).
|
||||
*/
|
||||
ScopedFastInt sff_LuauTypeSolverRelease;
|
||||
|
||||
TestFileResolver fileResolver;
|
||||
TestConfigResolver configResolver;
|
||||
NullModuleResolver moduleResolver;
|
||||
|
@ -939,14 +939,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_wait_for_pending_no_crash")
|
||||
Exp = 0,
|
||||
MaxExp = 100
|
||||
}
|
||||
|
||||
type Keys = index<typeof(PlayerData), keyof<typeof(PlayerData)>>
|
||||
|
||||
-- This function makes it think that there's going to be a pending expansion
|
||||
local function UpdateData(key: Keys, value)
|
||||
PlayerData[key] = value
|
||||
end
|
||||
|
||||
UpdateData("Coins", 2)
|
||||
)");
|
||||
|
||||
|
@ -1125,7 +1125,11 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments")
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("((number) -> number, string) -> number", toString(tm->wantedType));
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
// The new solver does not attempt to instantiate generics here, so if
|
||||
// either the instantiate in subtyping flag _or_ the new solver flags
|
||||
// are set, assert that we're getting back the original generic
|
||||
// function definition.
|
||||
if (FFlag::LuauInstantiateInSubtyping || FFlag::LuauSolverV2)
|
||||
CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
|
||||
else
|
||||
CHECK_EQ("((number) -> number, number) -> number", toString(tm->givenType));
|
||||
@ -1148,7 +1152,11 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments2")
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("(string, string) -> number", toString(tm->wantedType));
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
// The new solver does not attempt to instantiate generics here, so if
|
||||
// either the instantiate in subtyping flag _or_ the new solver flags
|
||||
// are set, assert that we're getting back the original generic
|
||||
// function definition.
|
||||
if (FFlag::LuauInstantiateInSubtyping || FFlag::LuauSolverV2)
|
||||
CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
|
||||
else
|
||||
CHECK_EQ("((string) -> number, string) -> number", toString(*tm->givenType));
|
||||
@ -1587,4 +1595,31 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_functions_work_in_subtyping")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_type_subtyping_nested_bounds_with_new_mappings")
|
||||
{
|
||||
// Test shows how going over mapped generics in a subtyping check can generate more mapped generics when making a subtyping check between bounds.
|
||||
// It has previously caused iterator invalidation in the new solver, but this specific test doesn't trigger a UAF, only shows an example.
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Dispatch<A> = (A) -> ()
|
||||
type BasicStateAction<S> = ((S) -> S) | S
|
||||
|
||||
function updateReducer<S, I, A>(reducer: (S, A) -> S, initialArg: I, init: ((I) -> S)?): (S, Dispatch<A>)
|
||||
return 1 :: any
|
||||
end
|
||||
|
||||
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S
|
||||
return action
|
||||
end
|
||||
|
||||
function updateState<S>(initialState: (() -> S) | S): (S, Dispatch<BasicStateAction<S>>)
|
||||
return updateReducer(basicStateReducer, initialState)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1576,7 +1576,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "compare_singleton_string_to_string")
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauRemoveBadRelationalOperatorWarning)
|
||||
// There is a flag to gate turning this off, and this warning is not
|
||||
// implemented in the new solver, so assert there are no errors.
|
||||
if (FFlag::LuauRemoveBadRelationalOperatorWarning || FFlag::LuauSolverV2)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauUseNormalizeIntersectionLimit)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -2324,4 +2325,50 @@ end)
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "refinements_table_intersection_limits" * doctest::timeout(0.5))
|
||||
{
|
||||
ScopedFastFlag LuauUseNormalizeIntersectionLimit{FFlag::LuauUseNormalizeIntersectionLimit, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Dir = {
|
||||
a: number?, b: number?, c: number?, d: number?, e: number?, f: number?,
|
||||
g: number?, h: number?, i: number?, j: number?, k: number?, l: number?,
|
||||
m: number?, n: number?, o: number?, p: number?, q: number?, r: number?,
|
||||
}
|
||||
|
||||
local function test(dirs: {Dir})
|
||||
for k, dir in dirs
|
||||
local success, message = pcall(function()
|
||||
assert(dir.a == nil or type(dir.a) == "number")
|
||||
assert(dir.b == nil or type(dir.b) == "number")
|
||||
assert(dir.c == nil or type(dir.c) == "number")
|
||||
assert(dir.d == nil or type(dir.d) == "number")
|
||||
assert(dir.e == nil or type(dir.e) == "number")
|
||||
assert(dir.f == nil or type(dir.f) == "number")
|
||||
assert(dir.g == nil or type(dir.g) == "number")
|
||||
assert(dir.h == nil or type(dir.h) == "number")
|
||||
assert(dir.i == nil or type(dir.i) == "number")
|
||||
assert(dir.j == nil or type(dir.j) == "number")
|
||||
assert(dir.k == nil or type(dir.k) == "number")
|
||||
assert(dir.l == nil or type(dir.l) == "number")
|
||||
assert(dir.m == nil or type(dir.m) == "number")
|
||||
assert(dir.n == nil or type(dir.n) == "number")
|
||||
assert(dir.o == nil or type(dir.o) == "number")
|
||||
assert(dir.p == nil or type(dir.p) == "number")
|
||||
assert(dir.q == nil or type(dir.q) == "number")
|
||||
assert(dir.r == nil or type(dir.r) == "number")
|
||||
assert(dir.t == nil or type(dir.t) == "number")
|
||||
assert(dir.u == nil or type(dir.u) == "number")
|
||||
assert(dir.v == nil or type(dir.v) == "number")
|
||||
local checkpoint = dir
|
||||
|
||||
checkpoint.w = 1
|
||||
end)
|
||||
assert(success)
|
||||
end
|
||||
end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -334,6 +334,27 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_alias_or_parens_is_indexer")
|
||||
CHECK_EQ("Cannot have more than one table indexer", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexer_can_be_union_of_singletons")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Target = "A" | "B"
|
||||
|
||||
type Test = {[Target]: number}
|
||||
|
||||
local test: Test = {}
|
||||
|
||||
test.A = 2
|
||||
test.C = 4
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK(8 == result.errors[0].location.begin.line);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections)
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
@ -2653,12 +2654,15 @@ local y = #x
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "length_operator_union_errors")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x: {number} | number | string
|
||||
local y = #x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// CLI-119936: This shouldn't double error but does under the new solver.
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_hang_when_trying_to_look_up_in_cyclic_metatable_index")
|
||||
@ -3261,22 +3265,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
CHECK("Cannot call a value of type a" == toString(result.errors[0]));
|
||||
else
|
||||
CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
if (!FFlag::LuauSolverV2)
|
||||
{
|
||||
TypeError e{
|
||||
Location{{5, 20}, {5, 21}},
|
||||
CannotCallNonFunction{builtinTypes->numberType},
|
||||
};
|
||||
|
||||
CHECK(result.errors[0] == e);
|
||||
}
|
||||
else if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
{
|
||||
CHECK("Cannot call a value of type a" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK("Cannot call non-function a" == toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_generic")
|
||||
@ -4832,4 +4836,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table2")
|
||||
CHECK("any" == toString(requireType("test2")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "length_of_array_is_number")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function TestFunc(ranges: {number}): number
|
||||
if true then
|
||||
ranges = {} :: {number}
|
||||
end
|
||||
local numRanges: number = #ranges
|
||||
return numRanges
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -419,6 +419,9 @@ TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors_2")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
||||
{
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = {number}
|
||||
function f(a: A?)
|
||||
@ -426,8 +429,10 @@ TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
// CLI-119936: This shouldn't double error but does under the new solver.
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ("Operator '#' could not be applied to operand of type A?; there is no corresponding overload for __len", toString(result.errors[0]));
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_missing_key_error_details")
|
||||
@ -638,8 +643,9 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
|
||||
)");
|
||||
|
||||
// this is a cyclic union of number arrays, so it _is_ a table, even if it's a nonsense type.
|
||||
// no need to generate a NotATable error here.
|
||||
if (FFlag::LuauAcceptIndexingTableUnionsIntersections)
|
||||
// no need to generate a NotATable error here. The new solver automatically handles this and
|
||||
// correctly reports no errors.
|
||||
if (FFlag::LuauAcceptIndexingTableUnionsIntersections || FFlag::LuauSolverV2)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
Loading…
Reference in New Issue
Block a user