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);
|
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.
|
* Resolves a type from its AST annotation.
|
||||||
* @param scope the scope that the type annotation appears within.
|
* @param scope the scope that the type annotation appears within.
|
||||||
|
@ -96,6 +96,22 @@ struct SubtypingEnvironment
|
|||||||
DenseHashSet<TypeId> upperBound{nullptr};
|
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
|
* 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.
|
* to tentatively map that generic onto a type on the other side.
|
||||||
@ -112,10 +128,6 @@ struct SubtypingEnvironment
|
|||||||
DenseHashMap<TypeId, TypeId> substitutions{nullptr};
|
DenseHashMap<TypeId, TypeId> substitutions{nullptr};
|
||||||
|
|
||||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
|
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
|
struct Subtyping
|
||||||
|
@ -13,7 +13,12 @@
|
|||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#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 =
|
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
||||||
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
{"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::LuauSolverV2)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauAutocompleteNewSolverLimit)
|
||||||
|
{
|
||||||
|
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||||
|
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
||||||
|
}
|
||||||
|
|
||||||
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}};
|
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}};
|
||||||
|
|
||||||
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
||||||
@ -199,6 +210,9 @@ static TypeCorrectKind checkTypeCorrectKind(
|
|||||||
{
|
{
|
||||||
for (TypeId id : itv->parts)
|
for (TypeId id : itv->parts)
|
||||||
{
|
{
|
||||||
|
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||||
|
id = follow(id);
|
||||||
|
|
||||||
if (const FunctionType* ftv = get<FunctionType>(id); ftv && checkFunctionType(ftv))
|
if (const FunctionType* ftv = get<FunctionType>(id); ftv && checkFunctionType(ftv))
|
||||||
{
|
{
|
||||||
return TypeCorrectKind::CorrectFunctionResult;
|
return TypeCorrectKind::CorrectFunctionResult;
|
||||||
|
@ -2949,216 +2949,243 @@ void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFuncti
|
|||||||
addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType});
|
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 ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||||
{
|
{
|
||||||
TypeId result = nullptr;
|
TypeId result = nullptr;
|
||||||
|
|
||||||
if (auto ref = ty->as<AstTypeReference>())
|
if (auto ref = ty->as<AstTypeReference>())
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauMagicTypes)
|
result = resolveReferenceType(scope, ty, ref, inTypeArguments, replaceErrorWithFresh);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (auto tab = ty->as<AstTypeTable>())
|
else if (auto tab = ty->as<AstTypeTable>())
|
||||||
{
|
{
|
||||||
TableType::Props props;
|
result = resolveTableType(scope, ty, tab, inTypeArguments, replaceErrorWithFresh);
|
||||||
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});
|
|
||||||
}
|
}
|
||||||
else if (auto fn = ty->as<AstTypeFunction>())
|
else if (auto fn = ty->as<AstTypeFunction>())
|
||||||
{
|
{
|
||||||
// TODO: Recursion limit.
|
result = resolveFunctionType(scope, ty, fn, inTypeArguments, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = arena->addType(std::move(ftv));
|
|
||||||
}
|
}
|
||||||
else if (auto tof = ty->as<AstTypeTypeof>())
|
else if (auto tof = ty->as<AstTypeTypeof>())
|
||||||
{
|
{
|
||||||
// TODO: Recursion limit.
|
|
||||||
TypeId exprType = check(scope, tof->expr).ty;
|
TypeId exprType = check(scope, tof->expr).ty;
|
||||||
result = exprType;
|
result = exprType;
|
||||||
}
|
}
|
||||||
@ -3167,7 +3194,6 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
|||||||
std::vector<TypeId> parts;
|
std::vector<TypeId> parts;
|
||||||
for (AstType* part : unionAnnotation->types)
|
for (AstType* part : unionAnnotation->types)
|
||||||
{
|
{
|
||||||
// TODO: Recursion limit.
|
|
||||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3178,7 +3204,6 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
|||||||
std::vector<TypeId> parts;
|
std::vector<TypeId> parts;
|
||||||
for (AstType* part : intersectionAnnotation->types)
|
for (AstType* part : intersectionAnnotation->types)
|
||||||
{
|
{
|
||||||
// TODO: Recursion limit.
|
|
||||||
parts.push_back(resolveType(scope, part, inTypeArguments));
|
parts.push_back(resolveType(scope, part, inTypeArguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,14 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings, false)
|
||||||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500);
|
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
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -21,11 +21,12 @@ LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false);
|
LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixCyclicTablesBlowingStack, 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_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2);
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauUseNormalizeIntersectionLimit, false)
|
||||||
|
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||||
|
|
||||||
static bool fixReduceStackPressure()
|
static bool fixReduceStackPressure()
|
||||||
{
|
{
|
||||||
return FFlag::LuauFixReduceStackPressure || FFlag::LuauSolverV2;
|
return FFlag::LuauFixReduceStackPressure || FFlag::LuauSolverV2;
|
||||||
@ -3035,6 +3036,14 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
|
|||||||
return unionNormals(here, there, ignoreSmallerTyvars);
|
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);
|
here.booleans = intersectionOfBools(here.booleans, there.booleans);
|
||||||
|
|
||||||
intersectClasses(here.classes, there.classes);
|
intersectClasses(here.classes, there.classes);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
|
#include "Luau/RecursionCounter.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/Substitution.h"
|
#include "Luau/Substitution.h"
|
||||||
@ -21,6 +22,8 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity, false);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteNewSolverLimit, false);
|
||||||
|
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -264,50 +267,86 @@ struct ApplyMappedGenerics : Substitution
|
|||||||
NotNull<BuiltinTypes> builtinTypes;
|
NotNull<BuiltinTypes> builtinTypes;
|
||||||
NotNull<TypeArena> arena;
|
NotNull<TypeArena> arena;
|
||||||
|
|
||||||
MappedGenerics& mappedGenerics;
|
SubtypingEnvironment& env;
|
||||||
MappedGenericPacks& mappedGenericPacks;
|
|
||||||
|
|
||||||
|
MappedGenerics& mappedGenerics_DEPRECATED;
|
||||||
|
MappedGenericPacks& mappedGenericPacks_DEPRECATED;
|
||||||
|
|
||||||
ApplyMappedGenerics(
|
ApplyMappedGenerics(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
|
SubtypingEnvironment& env,
|
||||||
MappedGenerics& mappedGenerics,
|
MappedGenerics& mappedGenerics,
|
||||||
MappedGenericPacks& mappedGenericPacks
|
MappedGenericPacks& mappedGenericPacks
|
||||||
)
|
)
|
||||||
: Substitution(TxnLog::empty(), arena)
|
: Substitution(TxnLog::empty(), arena)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
, arena(arena)
|
, arena(arena)
|
||||||
, mappedGenerics(mappedGenerics)
|
, env(env)
|
||||||
, mappedGenericPacks(mappedGenericPacks)
|
, mappedGenerics_DEPRECATED(mappedGenerics)
|
||||||
|
, mappedGenericPacks_DEPRECATED(mappedGenericPacks)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDirty(TypeId ty) override
|
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
|
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
|
TypeId clean(TypeId ty) override
|
||||||
{
|
{
|
||||||
const auto& bounds = mappedGenerics[ty];
|
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||||
|
{
|
||||||
|
const auto& bounds = env.getMappedTypeBounds(ty);
|
||||||
|
|
||||||
if (bounds.upperBound.empty())
|
if (bounds.upperBound.empty())
|
||||||
return builtinTypes->unknownType;
|
return builtinTypes->unknownType;
|
||||||
|
|
||||||
if (bounds.upperBound.size() == 1)
|
if (bounds.upperBound.size() == 1)
|
||||||
return *begin(bounds.upperBound);
|
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
|
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
|
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)
|
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);
|
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(
|
Subtyping::Subtyping(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> typeArena,
|
NotNull<TypeArena> typeArena,
|
||||||
@ -379,10 +486,23 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
|||||||
result.isSubtype = false;
|
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
|
/* 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)
|
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);
|
subTy = follow(subTy);
|
||||||
superTy = follow(superTy);
|
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;
|
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;
|
superTy = *superIt;
|
||||||
|
|
||||||
SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
|
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
|
||||||
if (cachedResult)
|
if (cachedResult)
|
||||||
return *cachedResult;
|
return *cachedResult;
|
||||||
|
|
||||||
cachedResult = env.ephemeralCache.find({subTy, superTy});
|
cachedResult = DFInt::LuauTypeSolverRelease >= 644 ? env.tryFindSubtypingResult({subTy, superTy}) : env.ephemeralCache.find({subTy, superTy});
|
||||||
if (cachedResult)
|
if (cachedResult)
|
||||||
return *cachedResult;
|
return *cachedResult;
|
||||||
|
|
||||||
@ -700,7 +836,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||||||
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
|
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
|
||||||
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
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".
|
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||||
results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail));
|
results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail));
|
||||||
else
|
else
|
||||||
@ -755,7 +892,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||||||
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
|
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
|
||||||
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
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".
|
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||||
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
|
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
|
||||||
else
|
else
|
||||||
@ -1688,6 +1826,12 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
|
|||||||
if (!get<GenericType>(subTy))
|
if (!get<GenericType>(subTy))
|
||||||
return false;
|
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);
|
env.mappedGenerics[subTy].upperBound.insert(superTy);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1695,6 +1839,12 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
|
|||||||
if (!get<GenericType>(superTy))
|
if (!get<GenericType>(superTy))
|
||||||
return false;
|
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);
|
env.mappedGenerics[superTy].lowerBound.insert(subTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1740,7 +1890,7 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePac
|
|||||||
if (!get<GenericTypePack>(subTp))
|
if (!get<GenericTypePack>(subTp))
|
||||||
return false;
|
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;
|
return *m == superTp;
|
||||||
|
|
||||||
env.mappedGenericPacks[subTp] = superTp;
|
env.mappedGenericPacks[subTp] = superTp;
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||||
|
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -3012,11 +3013,20 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
|
|||||||
if (tt->indexer)
|
if (tt->indexer)
|
||||||
{
|
{
|
||||||
TypeId indexType = follow(tt->indexer->indexType);
|
TypeId indexType = follow(tt->indexer->indexType);
|
||||||
if (isPrim(indexType, PrimitiveType::String))
|
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
{
|
||||||
// If the indexer looks like { [any] : _} - the prop lookup should be allowed!
|
TypeId givenType = module->internalTypes.addType(SingletonType{StringSingleton{prop}});
|
||||||
else if (get<AnyType>(indexType) || get<UnknownType>(indexType))
|
if (isSubtype(givenType, indexType, NotNull{module->getModuleScope().get()}, builtinTypes, *ice))
|
||||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
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_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false);
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -669,8 +671,16 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
|||||||
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
|
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
|
||||||
return {ctx->builtins->numberType, false, {}, {}};
|
return {ctx->builtins->numberType, false, {}, {}};
|
||||||
|
|
||||||
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||||
return *result;
|
{
|
||||||
|
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
|
// 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.
|
// 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())
|
if (normTy->isExactlyNumber())
|
||||||
return {ctx->builtins->numberType, false, {}, {}};
|
return {ctx->builtins->numberType, false, {}, {}};
|
||||||
|
|
||||||
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||||
return *result;
|
{
|
||||||
|
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
|
// 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.
|
// 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));
|
TypeId indexerTy = follow(typeParams.at(1));
|
||||||
|
|
||||||
if (isPending(indexerTy, ctx->solver))
|
if (isPending(indexerTy, ctx->solver))
|
||||||
{
|
|
||||||
return {std::nullopt, false, {indexerTy}, {}};
|
return {std::nullopt, false, {indexerTy}, {}};
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<const NormalizedType> indexerNormTy = ctx->normalizer->normalize(indexerTy);
|
std::shared_ptr<const NormalizedType> indexerNormTy = ctx->normalizer->normalize(indexerTy);
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
|
|
||||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||||
|
LUAU_FASTFLAG(LuauAutocompleteNewSolverLimit)
|
||||||
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
|
LUAU_FASTFLAG(LuauUseNormalizeIntersectionLimit)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
@ -3815,6 +3818,36 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_response_perf1" * doctest::timeout(0.
|
|||||||
CHECK(ac.entryMap.count("Instance"));
|
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")
|
TEST_CASE_FIXTURE(ACFixture, "strict_mode_force")
|
||||||
{
|
{
|
||||||
check(R"(
|
check(R"(
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -27,6 +28,7 @@ LUAU_FASTFLAG(LuauSolverV2);
|
|||||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||||
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
|
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
|
||||||
LUAU_FASTFLAG(LuauDCRMagicFunctionTypeChecker);
|
LUAU_FASTFLAG(LuauDCRMagicFunctionTypeChecker);
|
||||||
|
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||||
|
|
||||||
extern std::optional<unsigned> randomSeed; // tests/main.cpp
|
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)
|
Fixture::Fixture(bool freeze, bool prepareAutocomplete)
|
||||||
: sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, freeze)
|
: 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)
|
, 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(
|
, frontend(
|
||||||
&fileResolver,
|
&fileResolver,
|
||||||
&configResolver,
|
&configResolver,
|
||||||
|
@ -98,9 +98,37 @@ struct Fixture
|
|||||||
TypeId requireTypeAlias(const std::string& name);
|
TypeId requireTypeAlias(const std::string& name);
|
||||||
TypeId requireExportedType(const ModuleName& moduleName, 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;
|
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;
|
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;
|
TestFileResolver fileResolver;
|
||||||
TestConfigResolver configResolver;
|
TestConfigResolver configResolver;
|
||||||
NullModuleResolver moduleResolver;
|
NullModuleResolver moduleResolver;
|
||||||
|
@ -939,14 +939,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_wait_for_pending_no_crash")
|
|||||||
Exp = 0,
|
Exp = 0,
|
||||||
MaxExp = 100
|
MaxExp = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
type Keys = index<typeof(PlayerData), keyof<typeof(PlayerData)>>
|
type Keys = index<typeof(PlayerData), keyof<typeof(PlayerData)>>
|
||||||
|
|
||||||
-- This function makes it think that there's going to be a pending expansion
|
-- This function makes it think that there's going to be a pending expansion
|
||||||
local function UpdateData(key: Keys, value)
|
local function UpdateData(key: Keys, value)
|
||||||
PlayerData[key] = value
|
PlayerData[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
UpdateData("Coins", 2)
|
UpdateData("Coins", 2)
|
||||||
)");
|
)");
|
||||||
|
|
||||||
|
@ -1125,7 +1125,11 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments")
|
|||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
REQUIRE(tm);
|
REQUIRE(tm);
|
||||||
CHECK_EQ("((number) -> number, string) -> number", toString(tm->wantedType));
|
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));
|
CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
|
||||||
else
|
else
|
||||||
CHECK_EQ("((number) -> number, number) -> number", toString(tm->givenType));
|
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]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
REQUIRE(tm);
|
REQUIRE(tm);
|
||||||
CHECK_EQ("(string, string) -> number", toString(tm->wantedType));
|
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));
|
CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
|
||||||
else
|
else
|
||||||
CHECK_EQ("((string) -> number, string) -> number", toString(*tm->givenType));
|
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);
|
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();
|
TEST_SUITE_END();
|
||||||
|
@ -1576,7 +1576,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "compare_singleton_string_to_string")
|
|||||||
end
|
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);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
else
|
else
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauUseNormalizeIntersectionLimit)
|
||||||
|
|
||||||
using namespace Luau;
|
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();
|
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]));
|
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")
|
TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
|||||||
LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections)
|
LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections)
|
||||||
|
|
||||||
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||||
|
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TableTests");
|
TEST_SUITE_BEGIN("TableTests");
|
||||||
|
|
||||||
@ -2653,12 +2654,15 @@ local y = #x
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "length_operator_union_errors")
|
TEST_CASE_FIXTURE(Fixture, "length_operator_union_errors")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local x: {number} | number | string
|
local x: {number} | number | string
|
||||||
local y = #x
|
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")
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2)
|
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
|
|
||||||
{
|
{
|
||||||
TypeError e{
|
TypeError e{
|
||||||
Location{{5, 20}, {5, 21}},
|
Location{{5, 20}, {5, 21}},
|
||||||
CannotCallNonFunction{builtinTypes->numberType},
|
CannotCallNonFunction{builtinTypes->numberType},
|
||||||
};
|
};
|
||||||
|
|
||||||
CHECK(result.errors[0] == e);
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_generic")
|
||||||
@ -4832,4 +4836,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table2")
|
|||||||
CHECK("any" == toString(requireType("test2")));
|
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();
|
TEST_SUITE_END();
|
||||||
|
@ -419,6 +419,9 @@ TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors_2")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
||||||
{
|
{
|
||||||
|
|
||||||
|
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = {number}
|
type A = {number}
|
||||||
function f(a: A?)
|
function f(a: A?)
|
||||||
@ -426,8 +429,10 @@ TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
|||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
// CLI-119936: This shouldn't double error but does under the new solver.
|
||||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
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")
|
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.
|
// 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.
|
// no need to generate a NotATable error here. The new solver automatically handles this and
|
||||||
if (FFlag::LuauAcceptIndexingTableUnionsIntersections)
|
// correctly reports no errors.
|
||||||
|
if (FFlag::LuauAcceptIndexingTableUnionsIntersections || FFlag::LuauSolverV2)
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
else
|
else
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
Loading…
Reference in New Issue
Block a user