mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/644
This commit is contained in:
parent
cd27a20223
commit
a45eb2c9e0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
/build/
|
||||
/build[.-]*/
|
||||
/out
|
||||
/cmake/
|
||||
/cmake[.-]*/
|
||||
/coverage/
|
||||
|
@ -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,216 +2949,243 @@ void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFuncti
|
||||
addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType});
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::resolveReferenceType(
|
||||
const ScopePtr& scope,
|
||||
AstType* ty,
|
||||
AstTypeReference* ref,
|
||||
bool inTypeArguments,
|
||||
bool replaceErrorWithFresh
|
||||
)
|
||||
{
|
||||
TypeId result = nullptr;
|
||||
|
||||
if (FFlag::DebugLuauMagicTypes)
|
||||
{
|
||||
if (ref->name == "_luau_ice")
|
||||
ice->ice("_luau_ice encountered", ty->location);
|
||||
else if (ref->name == "_luau_print")
|
||||
{
|
||||
if (ref->parameters.size != 1 || !ref->parameters.data[0].type)
|
||||
{
|
||||
reportError(ty->location, GenericError{"_luau_print requires one generic parameter"});
|
||||
module->astResolvedTypes[ty] = builtinTypes->errorRecoveryType();
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
else
|
||||
return resolveType(scope, ref->parameters.data[0].type, inTypeArguments);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeFun> alias;
|
||||
|
||||
if (ref->prefix.has_value())
|
||||
{
|
||||
alias = scope->lookupImportedType(ref->prefix->value, ref->name.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
alias = scope->lookupType(ref->name.value);
|
||||
}
|
||||
|
||||
if (alias.has_value())
|
||||
{
|
||||
// If the alias is not generic, we don't need to set up a blocked
|
||||
// type and an instantiation constraint.
|
||||
if (alias.has_value() && alias->typeParams.empty() && alias->typePackParams.empty())
|
||||
{
|
||||
result = alias->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> parameters;
|
||||
std::vector<TypePackId> packParameters;
|
||||
|
||||
for (const AstTypeOrPack& p : ref->parameters)
|
||||
{
|
||||
// We do not enforce the ordering of types vs. type packs here;
|
||||
// that is done in the parser.
|
||||
if (p.type)
|
||||
{
|
||||
parameters.push_back(resolveType(scope, p.type, /* inTypeArguments */ true));
|
||||
}
|
||||
else if (p.typePack)
|
||||
{
|
||||
TypePackId tp = resolveTypePack(scope, p.typePack, /*inTypeArguments*/ true);
|
||||
|
||||
// If we need more regular types, we can use single element type packs to fill those in
|
||||
if (parameters.size() < alias->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
|
||||
parameters.push_back(*first(tp));
|
||||
else
|
||||
packParameters.push_back(tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This indicates a parser bug: one of these two pointers
|
||||
// should be set.
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
result = arena->addType(PendingExpansionType{ref->prefix, ref->name, parameters, packParameters});
|
||||
|
||||
// If we're not in a type argument context, we need to create a constraint that expands this.
|
||||
// The dispatching of the above constraint will queue up additional constraints for nested
|
||||
// type function applications.
|
||||
if (!inTypeArguments)
|
||||
addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = builtinTypes->errorRecoveryType();
|
||||
if (replaceErrorWithFresh)
|
||||
result = freshType(scope);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
|
||||
|
||||
Property& p = props[prop.name.value];
|
||||
p.typeLocation = prop.location;
|
||||
|
||||
switch (prop.access)
|
||||
{
|
||||
case AstTableAccess::ReadWrite:
|
||||
p.readTy = propTy;
|
||||
p.writeTy = propTy;
|
||||
break;
|
||||
case AstTableAccess::Read:
|
||||
p.readTy = propTy;
|
||||
break;
|
||||
case AstTableAccess::Write:
|
||||
reportError(*prop.accessLocation, GenericError{"write keyword is illegal here"});
|
||||
p.readTy = propTy;
|
||||
p.writeTy = propTy;
|
||||
break;
|
||||
default:
|
||||
ice->ice("Unexpected property access " + std::to_string(int(prop.access)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (AstTableIndexer* astIndexer = tab->indexer)
|
||||
{
|
||||
if (astIndexer->access == AstTableAccess::Read)
|
||||
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
|
||||
else if (astIndexer->access == AstTableAccess::Write)
|
||||
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
|
||||
else if (astIndexer->access == AstTableAccess::ReadWrite)
|
||||
{
|
||||
indexer = TableIndexer{
|
||||
resolveType(scope, astIndexer->indexType, inTypeArguments),
|
||||
resolveType(scope, astIndexer->resultType, inTypeArguments),
|
||||
};
|
||||
}
|
||||
else
|
||||
ice->ice("Unexpected property access " + std::to_string(int(astIndexer->access)));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
std::vector<TypeId> genericTypes;
|
||||
std::vector<TypePackId> genericTypePacks;
|
||||
|
||||
// If we don't have generics, we do not need to generate a child scope
|
||||
// for the generic bindings to live on.
|
||||
if (hasGenerics)
|
||||
{
|
||||
signatureScope = childScope(fn, scope);
|
||||
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureScope, fn->generics);
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks);
|
||||
|
||||
for (const auto& [name, g] : genericDefinitions)
|
||||
{
|
||||
genericTypes.push_back(g.ty);
|
||||
}
|
||||
|
||||
for (const auto& [name, g] : genericPackDefinitions)
|
||||
{
|
||||
genericTypePacks.push_back(g.tp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// To eliminate the need to branch on hasGenerics below, we say that
|
||||
// the signature scope is the parent scope if we don't have
|
||||
// generics.
|
||||
signatureScope = scope;
|
||||
}
|
||||
|
||||
TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes, inTypeArguments, replaceErrorWithFresh);
|
||||
TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh);
|
||||
|
||||
// TODO: FunctionType needs a pointer to the scope so that we know
|
||||
// how to quantify/instantiate it.
|
||||
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
||||
ftv.isCheckedFunction = fn->isCheckedFunction();
|
||||
|
||||
// This replicates the behavior of the appropriate FunctionType
|
||||
// constructors.
|
||||
ftv.generics = std::move(genericTypes);
|
||||
ftv.genericPacks = std::move(genericTypePacks);
|
||||
|
||||
ftv.argNames.reserve(fn->argNames.size);
|
||||
for (const auto& el : fn->argNames)
|
||||
{
|
||||
if (el)
|
||||
{
|
||||
const auto& [name, location] = *el;
|
||||
ftv.argNames.push_back(FunctionArgument{name.value, location});
|
||||
}
|
||||
else
|
||||
{
|
||||
ftv.argNames.push_back(std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
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>())
|
||||
{
|
||||
if (FFlag::DebugLuauMagicTypes)
|
||||
{
|
||||
if (ref->name == "_luau_ice")
|
||||
ice->ice("_luau_ice encountered", ty->location);
|
||||
else if (ref->name == "_luau_print")
|
||||
{
|
||||
if (ref->parameters.size != 1 || !ref->parameters.data[0].type)
|
||||
{
|
||||
reportError(ty->location, GenericError{"_luau_print requires one generic parameter"});
|
||||
module->astResolvedTypes[ty] = builtinTypes->errorRecoveryType();
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
else
|
||||
return resolveType(scope, ref->parameters.data[0].type, inTypeArguments);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeFun> alias;
|
||||
|
||||
if (ref->prefix.has_value())
|
||||
{
|
||||
alias = scope->lookupImportedType(ref->prefix->value, ref->name.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
alias = scope->lookupType(ref->name.value);
|
||||
}
|
||||
|
||||
if (alias.has_value())
|
||||
{
|
||||
// If the alias is not generic, we don't need to set up a blocked
|
||||
// type and an instantiation constraint.
|
||||
if (alias.has_value() && alias->typeParams.empty() && alias->typePackParams.empty())
|
||||
{
|
||||
result = alias->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> parameters;
|
||||
std::vector<TypePackId> packParameters;
|
||||
|
||||
for (const AstTypeOrPack& p : ref->parameters)
|
||||
{
|
||||
// We do not enforce the ordering of types vs. type packs here;
|
||||
// that is done in the parser.
|
||||
if (p.type)
|
||||
{
|
||||
parameters.push_back(resolveType(scope, p.type, /* inTypeArguments */ true));
|
||||
}
|
||||
else if (p.typePack)
|
||||
{
|
||||
TypePackId tp = resolveTypePack(scope, p.typePack, /*inTypeArguments*/ true);
|
||||
|
||||
// If we need more regular types, we can use single element type packs to fill those in
|
||||
if (parameters.size() < alias->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
|
||||
parameters.push_back(*first(tp));
|
||||
else
|
||||
packParameters.push_back(tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This indicates a parser bug: one of these two pointers
|
||||
// should be set.
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
result = arena->addType(PendingExpansionType{ref->prefix, ref->name, parameters, packParameters});
|
||||
|
||||
// If we're not in a type argument context, we need to create a constraint that expands this.
|
||||
// The dispatching of the above constraint will queue up additional constraints for nested
|
||||
// type function applications.
|
||||
if (!inTypeArguments)
|
||||
addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = builtinTypes->errorRecoveryType();
|
||||
if (replaceErrorWithFresh)
|
||||
result = freshType(scope);
|
||||
}
|
||||
result = resolveReferenceType(scope, ty, ref, inTypeArguments, replaceErrorWithFresh);
|
||||
}
|
||||
else if (auto tab = ty->as<AstTypeTable>())
|
||||
{
|
||||
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];
|
||||
p.typeLocation = prop.location;
|
||||
|
||||
switch (prop.access)
|
||||
{
|
||||
case AstTableAccess::ReadWrite:
|
||||
p.readTy = propTy;
|
||||
p.writeTy = propTy;
|
||||
break;
|
||||
case AstTableAccess::Read:
|
||||
p.readTy = propTy;
|
||||
break;
|
||||
case AstTableAccess::Write:
|
||||
reportError(*prop.accessLocation, GenericError{"write keyword is illegal here"});
|
||||
p.readTy = propTy;
|
||||
p.writeTy = propTy;
|
||||
break;
|
||||
default:
|
||||
ice->ice("Unexpected property access " + std::to_string(int(prop.access)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (AstTableIndexer* astIndexer = tab->indexer)
|
||||
{
|
||||
if (astIndexer->access == AstTableAccess::Read)
|
||||
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
|
||||
else if (astIndexer->access == AstTableAccess::Write)
|
||||
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),
|
||||
};
|
||||
}
|
||||
else
|
||||
ice->ice("Unexpected property access " + std::to_string(int(astIndexer->access)));
|
||||
}
|
||||
|
||||
result = arena->addType(TableType{props, indexer, scope->level, scope.get(), TableState::Sealed});
|
||||
result = resolveTableType(scope, ty, tab, inTypeArguments, replaceErrorWithFresh);
|
||||
}
|
||||
else if (auto fn = ty->as<AstTypeFunction>())
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0;
|
||||
ScopePtr signatureScope = nullptr;
|
||||
|
||||
std::vector<TypeId> genericTypes;
|
||||
std::vector<TypePackId> genericTypePacks;
|
||||
|
||||
// If we don't have generics, we do not need to generate a child scope
|
||||
// for the generic bindings to live on.
|
||||
if (hasGenerics)
|
||||
{
|
||||
signatureScope = childScope(fn, scope);
|
||||
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureScope, fn->generics);
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks);
|
||||
|
||||
for (const auto& [name, g] : genericDefinitions)
|
||||
{
|
||||
genericTypes.push_back(g.ty);
|
||||
}
|
||||
|
||||
for (const auto& [name, g] : genericPackDefinitions)
|
||||
{
|
||||
genericTypePacks.push_back(g.tp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// To eliminate the need to branch on hasGenerics below, we say that
|
||||
// the signature scope is the parent scope if we don't have
|
||||
// generics.
|
||||
signatureScope = scope;
|
||||
}
|
||||
|
||||
TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes, inTypeArguments, replaceErrorWithFresh);
|
||||
TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh);
|
||||
|
||||
// TODO: FunctionType needs a pointer to the scope so that we know
|
||||
// how to quantify/instantiate it.
|
||||
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
||||
ftv.isCheckedFunction = fn->isCheckedFunction();
|
||||
|
||||
// This replicates the behavior of the appropriate FunctionType
|
||||
// constructors.
|
||||
ftv.generics = std::move(genericTypes);
|
||||
ftv.genericPacks = std::move(genericTypePacks);
|
||||
|
||||
ftv.argNames.reserve(fn->argNames.size);
|
||||
for (const auto& el : fn->argNames)
|
||||
{
|
||||
if (el)
|
||||
{
|
||||
const auto& [name, location] = *el;
|
||||
ftv.argNames.push_back(FunctionArgument{name.value, location});
|
||||
}
|
||||
else
|
||||
{
|
||||
ftv.argNames.push_back(std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
result = arena->addType(std::move(ftv));
|
||||
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,50 +267,86 @@ 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;
|
||||
if (bounds.upperBound.empty())
|
||||
return builtinTypes->unknownType;
|
||||
|
||||
if (bounds.upperBound.size() == 1)
|
||||
return *begin(bounds.upperBound);
|
||||
if (bounds.upperBound.size() == 1)
|
||||
return *begin(bounds.upperBound);
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
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,10 +486,23 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
||||
result.isSubtype = false;
|
||||
}
|
||||
|
||||
SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound, scope);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
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
|
||||
@ -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,11 +3013,20 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
|
||||
if (tt->indexer)
|
||||
{
|
||||
TypeId indexType = follow(tt->indexer->indexType);
|
||||
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 (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}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
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 (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
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.
|
||||
@ -2206,6 +2224,10 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
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);
|
||||
|
||||
// if the indexer failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
|
@ -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;
|
||||
|
@ -927,6 +927,29 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
|
||||
CHECK_EQ("string", toString(tpm->givenTp));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_wait_for_pending_no_crash")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local PlayerData = {
|
||||
Coins = 0,
|
||||
Level = 1,
|
||||
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)
|
||||
)");
|
||||
|
||||
// Should not crash!
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_array")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
@ -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