mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/638 (#1360)
New Solver * Fix some type inference issues surrounding updates to upvalues eg ```luau local x = 0 function f() x = x + 1 end ``` * User-defined type function progress * Bugfixes for normalization of negated class types. eg `SomeClass & (class & ~SomeClass)` * Fixes to subtyping between tables and the top `table` type. --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Junseo Yoo <jyoo@roblox.com>
This commit is contained in:
parent
ce8495a69e
commit
bfad1fa777
@ -82,7 +82,8 @@ struct AnyTypeSummary
|
||||
VarAny,
|
||||
TableProp,
|
||||
Alias,
|
||||
Assign
|
||||
Assign,
|
||||
TypePk
|
||||
};
|
||||
|
||||
struct TypeInfo
|
||||
|
@ -184,9 +184,9 @@ private:
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprInterpString* i);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprError* error);
|
||||
|
||||
void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment = false);
|
||||
DefId visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment);
|
||||
DefId visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment);
|
||||
void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef);
|
||||
|
@ -207,6 +207,7 @@ private:
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable);
|
||||
|
||||
|
@ -594,10 +594,21 @@ struct TypeFunctionInstanceType
|
||||
std::vector<TypeId> typeArguments;
|
||||
std::vector<TypePackId> packArguments;
|
||||
|
||||
TypeFunctionInstanceType(NotNull<const TypeFunction> function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
|
||||
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
|
||||
std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs
|
||||
|
||||
TypeFunctionInstanceType(
|
||||
NotNull<const TypeFunction> function,
|
||||
std::vector<TypeId> typeArguments,
|
||||
std::vector<TypePackId> packArguments,
|
||||
std::optional<AstName> userFuncName = std::nullopt,
|
||||
std::optional<AstExprFunction*> userFuncBody = std::nullopt
|
||||
)
|
||||
: function(function)
|
||||
, typeArguments(typeArguments)
|
||||
, packArguments(packArguments)
|
||||
, userFuncName(userFuncName)
|
||||
, userFuncBody(userFuncBody)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,9 @@ struct TypeFunctionContext
|
||||
// The constraint being reduced in this run of the reduction
|
||||
const Constraint* constraint;
|
||||
|
||||
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
|
||||
std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs
|
||||
|
||||
TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
|
||||
: arena(cs->arena)
|
||||
, builtins(cs->builtinTypes)
|
||||
@ -156,6 +159,8 @@ struct BuiltinTypeFunctions
|
||||
{
|
||||
BuiltinTypeFunctions();
|
||||
|
||||
TypeFunction userFunc;
|
||||
|
||||
TypeFunction notFunc;
|
||||
TypeFunction lenFunc;
|
||||
TypeFunction unmFunc;
|
||||
|
@ -136,6 +136,7 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module*
|
||||
const Scope* retScope = findInnerMostScope(ret->location, module);
|
||||
|
||||
auto ctxNode = getNode(rootSrc, ret);
|
||||
bool seenTP = false;
|
||||
|
||||
for (auto val : ret->list)
|
||||
{
|
||||
@ -160,8 +161,24 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module*
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret->list.size > 1 && !seenTP)
|
||||
{
|
||||
if (containsAny(retScope->returnType))
|
||||
{
|
||||
seenTP = true;
|
||||
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(retScope->returnType);
|
||||
|
||||
TypeInfo ti{Pattern::TypePk, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
|
@ -271,6 +271,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
||||
TypeId domainTy = builtinTypes->neverType;
|
||||
for (TypeId d : domain)
|
||||
{
|
||||
d = follow(d);
|
||||
if (d == ty)
|
||||
continue;
|
||||
domainTy = simplifyUnion(builtinTypes, arena, domainTy, d).result;
|
||||
@ -663,6 +664,51 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
|
||||
astTypeAliasDefiningScopes[alias] = defnScope;
|
||||
aliasDefinitionLocations[alias->name.value] = alias->location;
|
||||
}
|
||||
else if (auto function = stat->as<AstStatTypeFunction>())
|
||||
{
|
||||
// If a type function w/ same name has already been defined, error for having duplicates
|
||||
if (scope->exportedTypeBindings.count(function->name.value) || scope->privateTypeBindings.count(function->name.value))
|
||||
{
|
||||
auto it = aliasDefinitionLocations.find(function->name.value);
|
||||
LUAU_ASSERT(it != aliasDefinitionLocations.end());
|
||||
reportError(function->location, DuplicateTypeDefinition{function->name.value, it->second});
|
||||
continue;
|
||||
}
|
||||
|
||||
ScopePtr defnScope = childScope(function, scope);
|
||||
|
||||
// Create TypeFunctionInstanceType
|
||||
|
||||
std::vector<TypeId> typeParams;
|
||||
typeParams.reserve(function->body->args.size);
|
||||
|
||||
std::vector<GenericTypeDefinition> quantifiedTypeParams;
|
||||
quantifiedTypeParams.reserve(function->body->args.size);
|
||||
|
||||
for (size_t i = 0; i < function->body->args.size; i++)
|
||||
{
|
||||
std::string name = format("T%zu", i);
|
||||
TypeId ty = arena->addType(GenericType{name});
|
||||
typeParams.push_back(ty);
|
||||
|
||||
GenericTypeDefinition genericTy{ty};
|
||||
quantifiedTypeParams.push_back(genericTy);
|
||||
}
|
||||
|
||||
TypeId typeFunctionTy = arena->addType(TypeFunctionInstanceType{
|
||||
NotNull{&builtinTypeFunctions().userFunc},
|
||||
std::move(typeParams),
|
||||
{},
|
||||
function->name,
|
||||
function->body,
|
||||
});
|
||||
|
||||
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy};
|
||||
|
||||
// Set type bindings and definition locations for this user-defined type function
|
||||
scope->privateTypeBindings[function->name.value] = std::move(typeFunction);
|
||||
aliasDefinitionLocations[function->name.value] = function->location;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ControlFlow> firstControlFlow;
|
||||
@ -1368,6 +1414,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
|
||||
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function)
|
||||
{
|
||||
// If a type function with the same name was already defined, we skip over
|
||||
auto bindingIt = scope->privateTypeBindings.find(function->name.value);
|
||||
if (bindingIt == scope->privateTypeBindings.end())
|
||||
return ControlFlow::None;
|
||||
|
||||
TypeFun typeFunction = bindingIt->second;
|
||||
|
||||
// Adding typeAliasExpansionConstraint on user-defined type function for the constraint solver
|
||||
if (auto typeFunctionTy = get<TypeFunctionInstanceType>(typeFunction.type))
|
||||
{
|
||||
TypeId expansionTy = arena->addType(PendingExpansionType{{}, function->name, typeFunctionTy->typeArguments, typeFunctionTy->packArguments});
|
||||
addConstraint(scope, function->location, TypeAliasExpansionConstraint{/* target */ expansionTy});
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
|
@ -924,6 +924,10 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
return true;
|
||||
}
|
||||
|
||||
// Adding ReduceConstraint on type function for the constraint solver
|
||||
if (auto typeFn = get<TypeFunctionInstanceType>(follow(tf->type)))
|
||||
pushConstraint(NotNull(constraint->scope.get()), constraint->location, ReduceConstraint{tf->type});
|
||||
|
||||
// If there are no parameters to the type function we can just use the type
|
||||
// directly.
|
||||
if (tf->typeParams.empty() && tf->typePackParams.empty())
|
||||
@ -1051,7 +1055,6 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
// there are e.g. generic saturatedTypeArguments that go unused.
|
||||
const TableType* tfTable = getTableType(tf->type);
|
||||
|
||||
//clang-format off
|
||||
bool needsClone = follow(tf->type) == target || (tfTable != nullptr && tfTable == getTableType(target)) ||
|
||||
std::any_of(
|
||||
typeArguments.begin(),
|
||||
@ -1061,7 +1064,6 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
return other == target;
|
||||
}
|
||||
);
|
||||
//clang-format on
|
||||
|
||||
// Only tables have the properties we're trying to set.
|
||||
TableType* ttv = getMutableTableType(target);
|
||||
|
@ -570,15 +570,8 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatAssign* a)
|
||||
|
||||
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatCompoundAssign* c)
|
||||
{
|
||||
// TODO: This needs revisiting because this is incorrect. The `c->var` part is both being read and written to,
|
||||
// but the `c->var` only has one pointer address, so we need to come up with a way to store both.
|
||||
// For now, it's not important because we don't have type states, but it is going to be important, e.g.
|
||||
//
|
||||
// local a = 5 -- a-1
|
||||
// a += 5 -- a-2 = a-1 + 5
|
||||
// We can't just visit `c->var` as a rvalue and then separately traverse `c->var` as an lvalue, since that's O(n^2).
|
||||
DefId def = visitExpr(scope, c->value).def;
|
||||
visitLValue(scope, c->var, def, /* isCompoundAssignment */ true);
|
||||
(void) visitExpr(scope, c->value);
|
||||
(void) visitExpr(scope, c->var);
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
@ -920,14 +913,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprError* er
|
||||
return {defArena->freshCell(), nullptr};
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment)
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef)
|
||||
{
|
||||
auto go = [&]()
|
||||
{
|
||||
if (auto l = e->as<AstExprLocal>())
|
||||
return visitLValue(scope, l, incomingDef, isCompoundAssignment);
|
||||
return visitLValue(scope, l, incomingDef);
|
||||
else if (auto g = e->as<AstExprGlobal>())
|
||||
return visitLValue(scope, g, incomingDef, isCompoundAssignment);
|
||||
return visitLValue(scope, g, incomingDef);
|
||||
else if (auto i = e->as<AstExprIndexName>())
|
||||
return visitLValue(scope, i, incomingDef);
|
||||
else if (auto i = e->as<AstExprIndexExpr>())
|
||||
@ -941,15 +934,8 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomi
|
||||
graph.astDefs[e] = go();
|
||||
}
|
||||
|
||||
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment)
|
||||
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef)
|
||||
{
|
||||
// We need to keep the previous def around for a compound assignment.
|
||||
if (isCompoundAssignment)
|
||||
{
|
||||
DefId def = lookup(scope, l->local);
|
||||
graph.compoundAssignDefs[l] = def;
|
||||
}
|
||||
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||
if (scope->canUpdateDefinition(l->local))
|
||||
{
|
||||
@ -962,15 +948,8 @@ DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId
|
||||
return visitExpr(scope, static_cast<AstExpr*>(l)).def;
|
||||
}
|
||||
|
||||
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment)
|
||||
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef)
|
||||
{
|
||||
// We need to keep the previous def around for a compound assignment.
|
||||
if (isCompoundAssignment)
|
||||
{
|
||||
DefId def = lookup(scope, g->name);
|
||||
graph.compoundAssignDefs[g] = def;
|
||||
}
|
||||
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||
if (scope->canUpdateDefinition(g->name))
|
||||
{
|
||||
|
@ -2186,6 +2186,11 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
|
||||
|
||||
if (isSubclass(thereTy, hereTy))
|
||||
{
|
||||
// If thereTy is a subtype of hereTy, we need to replace hereTy
|
||||
// by thereTy and combine their negation lists.
|
||||
//
|
||||
// If any types in the negation list are not subtypes of
|
||||
// thereTy, they need to be removed from the negation list.
|
||||
TypeIds negations = std::move(hereNegations);
|
||||
|
||||
for (auto nIt = negations.begin(); nIt != negations.end();)
|
||||
@ -2209,22 +2214,45 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
|
||||
}
|
||||
else if (isSubclass(hereTy, thereTy))
|
||||
{
|
||||
// If thereTy is a supertype of hereTy, we need to extend the
|
||||
// negation list of hereTy by that of thereTy.
|
||||
//
|
||||
// If any of the types of thereTy's negations are not subtypes
|
||||
// of hereTy, they must not be added to hereTy's negation list.
|
||||
//
|
||||
// If any of the types of thereTy's negations are supertypes of
|
||||
// hereTy, then hereTy must be removed entirely.
|
||||
//
|
||||
// If any of the types of thereTy's negations are supertypes of
|
||||
// the negations of herety, the former must supplant the latter.
|
||||
TypeIds negations = thereNegations;
|
||||
|
||||
bool erasedHere = false;
|
||||
|
||||
for (auto nIt = negations.begin(); nIt != negations.end();)
|
||||
{
|
||||
if (!isSubclass(*nIt, hereTy))
|
||||
if (isSubclass(hereTy, *nIt))
|
||||
{
|
||||
nIt = negations.erase(nIt);
|
||||
}
|
||||
else
|
||||
{
|
||||
++nIt;
|
||||
}
|
||||
// eg SomeClass & (class & ~SomeClass)
|
||||
// or SomeClass & (class & ~ParentClass)
|
||||
heres.classes.erase(hereTy);
|
||||
it = heres.ordering.erase(it);
|
||||
erasedHere = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// eg SomeClass & (class & ~Unrelated)
|
||||
if (!isSubclass(*nIt, hereTy))
|
||||
nIt = negations.erase(nIt);
|
||||
else
|
||||
++nIt;
|
||||
}
|
||||
|
||||
if (!erasedHere)
|
||||
{
|
||||
unionClasses(hereNegations, negations);
|
||||
break;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
else if (hereTy == thereTy)
|
||||
{
|
||||
|
@ -230,7 +230,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
|
||||
// function arguments are options, then this function call
|
||||
// is ok.
|
||||
|
||||
const size_t firstUnsatisfiedArgument = argExprs->size();
|
||||
const size_t firstUnsatisfiedArgument = args->head.size();
|
||||
const auto [requiredHead, _requiredTail] = flatten(fn->argTypes);
|
||||
|
||||
// If too many arguments were supplied, this overload
|
||||
|
@ -1159,13 +1159,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
|
||||
for (const auto& [name, rightProp] : rt->props)
|
||||
mergedProps[name] = rightProp;
|
||||
|
||||
return arena->addType(TableType{
|
||||
mergedProps,
|
||||
std::nullopt,
|
||||
TypeLevel{},
|
||||
lt->scope,
|
||||
TableState::Sealed
|
||||
});
|
||||
return arena->addType(TableType{mergedProps, std::nullopt, TypeLevel{}, lt->scope, TableState::Sealed});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
||||
return dest.addType(NegationType{a.ty});
|
||||
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
|
||||
{
|
||||
TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments};
|
||||
TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName, a.userFuncBody};
|
||||
return dest.addType(std::move(clone));
|
||||
}
|
||||
else
|
||||
|
@ -639,6 +639,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
|
||||
result = isCovariantWith(env, subTy, p.first, superTy, p.second);
|
||||
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
||||
@ -1368,6 +1370,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Func
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim)
|
||||
{
|
||||
SubtypingResult result{false};
|
||||
if (superPrim->type == PrimitiveType::Table)
|
||||
result.isSubtype = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{false};
|
||||
@ -1387,6 +1398,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (subPrim->type == PrimitiveType::Table)
|
||||
{
|
||||
const bool isSubtype = superTable->props.empty() && !superTable->indexer.has_value();
|
||||
return {isSubtype};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1036,6 +1036,9 @@ struct TypeStringifier
|
||||
|
||||
void operator()(TypeId, const TypeFunctionInstanceType& tfitv)
|
||||
{
|
||||
if (tfitv.userFuncName) // Special stringification for user-defined type functions
|
||||
state.emit(tfitv.userFuncName->value);
|
||||
else
|
||||
state.emit(tfitv.function->name);
|
||||
state.emit("<");
|
||||
|
||||
|
@ -358,6 +358,9 @@ struct TypeFunctionReducer
|
||||
if (tryGuessing(subject))
|
||||
return;
|
||||
|
||||
ctx.userFuncName = tfit->userFuncName;
|
||||
ctx.userFuncBody = tfit->userFuncBody;
|
||||
|
||||
TypeFunctionReductionResult<TypeId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||
handleTypeFunctionReduction(subject, result);
|
||||
}
|
||||
@ -567,6 +570,24 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
||||
TypeId instance,
|
||||
const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams,
|
||||
NotNull<TypeFunctionContext> ctx
|
||||
)
|
||||
{
|
||||
if (!ctx->userFuncName || !ctx->userFuncBody)
|
||||
{
|
||||
ctx->ice->ice("all user-defined type functions must have an associated function definition");
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
|
||||
// TODO: implementation of user-defined type functions goes here
|
||||
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
|
||||
TypeFunctionReductionResult<TypeId> notTypeFunction(
|
||||
TypeId instance,
|
||||
const std::vector<TypeId>& typeParams,
|
||||
@ -2253,7 +2274,8 @@ TypeFunctionReductionResult<TypeId> rawgetTypeFunction(
|
||||
}
|
||||
|
||||
BuiltinTypeFunctions::BuiltinTypeFunctions()
|
||||
: notFunc{"not", notTypeFunction}
|
||||
: userFunc{"user", userDefinedTypeFunction}
|
||||
, notFunc{"not", notTypeFunction}
|
||||
, lenFunc{"len", lenTypeFunction}
|
||||
, unmFunc{"unm", unmTypeFunction}
|
||||
, addFunc{"add", addTypeFunction}
|
||||
|
@ -2243,15 +2243,24 @@ std::optional<AstExprBinary::Op> Parser::checkBinaryConfusables(const BinaryOpPr
|
||||
// where `binop' is any binary operator with a priority higher than `limit'
|
||||
AstExpr* Parser::parseExpr(unsigned int limit)
|
||||
{
|
||||
// clang-format off
|
||||
static const BinaryOpPriority binaryPriority[] = {
|
||||
{6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, {7, 7}, // `+' `-' `*' `/' `//' `%'
|
||||
{10, 9}, {5, 4}, // power and concat (right associative)
|
||||
{3, 3}, {3, 3}, // equality and inequality
|
||||
{3, 3}, {3, 3}, {3, 3}, {3, 3}, // order
|
||||
{2, 2}, {1, 1} // logical (and/or)
|
||||
{6, 6}, // '+'
|
||||
{6, 6}, // '-'
|
||||
{7, 7}, // '*'
|
||||
{7, 7}, // '/'
|
||||
{7, 7}, // '//'
|
||||
{7, 7}, // `%'
|
||||
{10, 9}, // power (right associative)
|
||||
{5, 4}, // concat (right associative)
|
||||
{3, 3}, // inequality
|
||||
{3, 3}, // equality
|
||||
{3, 3}, // '<'
|
||||
{3, 3}, // '<='
|
||||
{3, 3}, // '>'
|
||||
{3, 3}, // '>='
|
||||
{2, 2}, // logical and
|
||||
{1, 1} // logical or
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static_assert(sizeof(binaryPriority) / sizeof(binaryPriority[0]) == size_t(AstExprBinary::Op__Count), "binaryPriority needs an entry per op");
|
||||
|
||||
|
@ -70,6 +70,53 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "typepacks")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local function fallible(t: number): ...any
|
||||
if t > 0 then
|
||||
return true, t -- should catch this
|
||||
end
|
||||
return false, "must be positive" -- should catch this
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "typepacks_no_ret")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
-- TODO: if partially typed, we'd want to know too
|
||||
local function fallible(t: number)
|
||||
if t > 0 then
|
||||
return true, t
|
||||
end
|
||||
return false, "must be positive"
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen_table")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
@ -223,7 +270,10 @@ end
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = nil\n}\nelse\n local expected = x * 5\nend\nend");
|
||||
LUAU_ASSERT(
|
||||
module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = "
|
||||
"nil\n}\nelse\n local expected = x * 5\nend\nend"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,7 +528,13 @@ initialize()
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end\nend");
|
||||
LUAU_ASSERT(
|
||||
module->ats.typeInfo[0].node ==
|
||||
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
|
||||
"descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
|
||||
"character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
|
||||
"end\nend"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -541,7 +597,14 @@ initialize()
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, `{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, `{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend");
|
||||
LUAU_ASSERT(
|
||||
module->ats.typeInfo[0].node ==
|
||||
"local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, "
|
||||
"`{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, "
|
||||
"`{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = "
|
||||
"spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n "
|
||||
"destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeFunction.h"
|
||||
|
||||
#include "IostreamOptional.h"
|
||||
#include "ScopedFlags.h"
|
||||
|
@ -847,6 +847,8 @@ TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
|
||||
"(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" ==
|
||||
toString(normal("Not<cls & Not<Parent> & Not<Child> & Not<Unrelated>>"))
|
||||
);
|
||||
|
||||
CHECK("Child" == toString(normal("(Child | Unrelated) & Not<Unrelated>")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_unknown")
|
||||
@ -998,17 +1000,13 @@ TEST_CASE_FIXTURE(NormalizeFixture, "truthy_table_property_and_optional_table_wi
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
// { x: ~(false?) }
|
||||
TypeId t1 = arena.addType(TableType{
|
||||
TableType::Props{{"x", builtinTypes->truthyType}}, std::nullopt, TypeLevel{}, TableState::Sealed
|
||||
});
|
||||
TypeId t1 = arena.addType(TableType{TableType::Props{{"x", builtinTypes->truthyType}}, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
|
||||
// { x: number? }?
|
||||
TypeId t2 = arena.addType(UnionType{{
|
||||
arena.addType(TableType{
|
||||
TableType::Props{{"x", builtinTypes->optionalNumberType}}, std::nullopt, TypeLevel{}, TableState::Sealed
|
||||
}),
|
||||
builtinTypes->nilType
|
||||
}});
|
||||
TypeId t2 = arena.addType(UnionType{
|
||||
{arena.addType(TableType{TableType::Props{{"x", builtinTypes->optionalNumberType}}, std::nullopt, TypeLevel{}, TableState::Sealed}),
|
||||
builtinTypes->nilType}
|
||||
});
|
||||
|
||||
TypeId intersection = arena.addType(IntersectionType{{t2, t1}});
|
||||
|
||||
|
@ -2085,7 +2085,6 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_variadics")
|
||||
{
|
||||
//clang-format off
|
||||
AstStatBlock* stat = parseEx(R"(
|
||||
function foo(bar, ...: number): ...string
|
||||
end
|
||||
@ -2094,7 +2093,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_variadics")
|
||||
type Bar = () -> (number, ...boolean)
|
||||
)")
|
||||
.root;
|
||||
//clang-format on
|
||||
|
||||
REQUIRE(stat);
|
||||
REQUIRE_EQ(stat->body.size, 3);
|
||||
|
@ -893,6 +893,9 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number
|
||||
CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}}));
|
||||
}
|
||||
|
||||
TEST_IS_SUBTYPE(builtinTypes->tableType, tbl({}));
|
||||
TEST_IS_SUBTYPE(tbl({}), builtinTypes->tableType);
|
||||
|
||||
// Negated subtypes
|
||||
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType);
|
||||
TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType);
|
||||
@ -1213,7 +1216,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...any) -> () <: <T>(T...) -> ()")
|
||||
// See https://github.com/luau-lang/luau/issues/767
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: <T>(T...) -> ()")
|
||||
{
|
||||
TypeId unknownsToNothing = arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack});
|
||||
TypeId unknownsToNothing =
|
||||
arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack});
|
||||
TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack});
|
||||
|
||||
CHECK_MESSAGE(subtyping.isSubtype(unknownsToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: <T>(T...) -> ()");
|
||||
@ -1222,25 +1226,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: <T>(T...) -> ()")
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "bill")
|
||||
{
|
||||
TypeId a = arena.addType(TableType{
|
||||
{{"a", builtinTypes->stringType}},
|
||||
TableIndexer{
|
||||
builtinTypes->stringType,
|
||||
builtinTypes->numberType
|
||||
},
|
||||
TypeLevel{},
|
||||
nullptr,
|
||||
TableState::Sealed
|
||||
{{"a", builtinTypes->stringType}}, TableIndexer{builtinTypes->stringType, builtinTypes->numberType}, TypeLevel{}, nullptr, TableState::Sealed
|
||||
});
|
||||
|
||||
TypeId b = arena.addType(TableType{
|
||||
{{"a", builtinTypes->stringType}},
|
||||
TableIndexer{
|
||||
builtinTypes->stringType,
|
||||
builtinTypes->numberType
|
||||
},
|
||||
TypeLevel{},
|
||||
nullptr,
|
||||
TableState::Sealed
|
||||
{{"a", builtinTypes->stringType}}, TableIndexer{builtinTypes->stringType, builtinTypes->numberType}, TypeLevel{}, nullptr, TableState::Sealed
|
||||
});
|
||||
|
||||
CHECK(subtyping.isSubtype(a, b).isSubtype);
|
||||
@ -1250,22 +1240,17 @@ TEST_CASE_FIXTURE(SubtypeFixture, "bill")
|
||||
// TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()")
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "fred")
|
||||
{
|
||||
auto makeTheType = [&]() {
|
||||
auto makeTheType = [&]()
|
||||
{
|
||||
TypeId argType = arena.addType(TableType{
|
||||
{{"a", builtinTypes->stringType}},
|
||||
TableIndexer{
|
||||
builtinTypes->stringType,
|
||||
builtinTypes->numberType
|
||||
},
|
||||
TableIndexer{builtinTypes->stringType, builtinTypes->numberType},
|
||||
TypeLevel{},
|
||||
nullptr,
|
||||
TableState::Sealed
|
||||
});
|
||||
|
||||
return arena.addType(FunctionType {
|
||||
arena.addTypePack({argType}),
|
||||
builtinTypes->emptyTypePack
|
||||
});
|
||||
return arena.addType(FunctionType{arena.addTypePack({argType}), builtinTypes->emptyTypePack});
|
||||
};
|
||||
|
||||
TypeId a = makeTheType();
|
||||
|
@ -159,8 +159,10 @@ n4 [label="VariadicTypePack 4"];
|
||||
n4 -> n5;
|
||||
n5 [label="string"];
|
||||
n1 -> n6 [label="ret"];
|
||||
n6 [label="TypePack 6"];
|
||||
n6 -> n3;
|
||||
n6 [label="BoundTypePack 6"];
|
||||
n6 -> n7;
|
||||
n7 [label="TypePack 7"];
|
||||
n7 -> n3;
|
||||
})",
|
||||
toDot(requireType("f"), opts)
|
||||
);
|
||||
|
@ -13,6 +13,7 @@ using namespace Luau;
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions)
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
@ -21,8 +22,13 @@ TEST_CASE_FIXTURE(Fixture, "primitive")
|
||||
CheckResult result = check("local a = nil local b = 44 local c = 'lalala' local d = true");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("nil" == toString(requireType("a")));
|
||||
else
|
||||
{
|
||||
// A variable without an annotation and with a nil literal should infer as 'free', not 'nil'
|
||||
CHECK_NE("nil", toString(requireType("a")));
|
||||
}
|
||||
|
||||
CHECK_EQ("number", toString(requireType("b")));
|
||||
CHECK_EQ("string", toString(requireType("c")));
|
||||
@ -39,6 +45,8 @@ TEST_CASE_FIXTURE(Fixture, "bound_types")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_types")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check("local a");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
@ -95,7 +103,6 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = true;
|
||||
|
||||
//clang-format off
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(
|
||||
"{\n"
|
||||
@ -114,7 +121,6 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||
"|}",
|
||||
toString(requireType("a"), opts)
|
||||
);
|
||||
//clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nil_or_nil_is_nil_not_question_mark")
|
||||
@ -160,6 +166,8 @@ TEST_CASE_FIXTURE(Fixture, "named_metatable")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "named_metatable_toStringNamedFunction")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function createTbl(): NamedMetatable
|
||||
return setmetatable({}, {})
|
||||
@ -199,14 +207,24 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table")
|
||||
CHECK_EQ(std::string::npos, a.find("CYCLE"));
|
||||
CHECK_EQ(std::string::npos, a.find("TRUNCATED"));
|
||||
|
||||
//clang-format off
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK(
|
||||
"t2 where "
|
||||
"t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; "
|
||||
"t2 = { @metatable t1, { x: number, y: number, z: number } }" ==
|
||||
a
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(
|
||||
"t2 where "
|
||||
"t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; "
|
||||
"t2 = { @metatable t1, {| x: number, y: number, z: number |} }",
|
||||
a
|
||||
);
|
||||
//clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -263,14 +281,12 @@ TEST_CASE_FIXTURE(Fixture, "complex_intersections_printed_on_multiple_lines")
|
||||
opts.useLineBreaks = true;
|
||||
opts.compositeTypesSingleLineLimit = 2;
|
||||
|
||||
//clang-format off
|
||||
CHECK_EQ(
|
||||
"boolean\n"
|
||||
"& number\n"
|
||||
"& string",
|
||||
toString(requireType("a"), opts)
|
||||
);
|
||||
//clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_lines")
|
||||
@ -282,13 +298,11 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_line
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = true;
|
||||
|
||||
//clang-format off
|
||||
CHECK_EQ(
|
||||
"((number) -> number)\n"
|
||||
"& ((string) -> string)",
|
||||
toString(requireType("a"), opts)
|
||||
);
|
||||
//clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "simple_unions_printed_on_one_line")
|
||||
@ -313,14 +327,12 @@ TEST_CASE_FIXTURE(Fixture, "complex_unions_printed_on_multiple_lines")
|
||||
opts.compositeTypesSingleLineLimit = 2;
|
||||
opts.useLineBreaks = true;
|
||||
|
||||
//clang-format off
|
||||
CHECK_EQ(
|
||||
"boolean\n"
|
||||
"| number\n"
|
||||
"| string",
|
||||
toString(requireType("a"), opts)
|
||||
);
|
||||
//clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded")
|
||||
@ -582,6 +594,8 @@ TEST_CASE_FIXTURE(Fixture, "toStringDetailed")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringErrorPack")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function target(callback: nil) return callback(4, "hello") end
|
||||
)");
|
||||
@ -666,6 +680,9 @@ TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_inters
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("(() -> t1) & ((number) -> ()) where t1 = () -> t1" == toString(requireType("a")));
|
||||
else
|
||||
CHECK_EQ("((number) -> ()) & t1 where t1 = () -> t1", toString(requireType("a")));
|
||||
}
|
||||
|
||||
@ -824,6 +841,11 @@ TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_
|
||||
function foo<a>(x: a, y) end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("<a>(a, 'b) -> ()" == toString(requireType("foo")));
|
||||
}
|
||||
else
|
||||
CHECK("<a, b>(a, b) -> ()" == toString(requireType("foo")));
|
||||
}
|
||||
|
||||
@ -934,4 +956,21 @@ TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack")
|
||||
CHECK("tp1 where tp1 = {| BaseField: unknown, BaseMethod: (tp1) -> () |}, number" == toString(thePack));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "correct_stringification_user_defined_type_functions")
|
||||
{
|
||||
TypeFunction user{"user", nullptr};
|
||||
TypeFunctionInstanceType tftt{
|
||||
NotNull{&user},
|
||||
std::vector<TypeId>{builtinTypes->numberType}, // Type Function Arguments
|
||||
{},
|
||||
{AstName{"woohoo"}}, // Type Function Name
|
||||
std::nullopt
|
||||
};
|
||||
|
||||
Type tv{tftt};
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && FFlag::LuauUserDefinedTypeFunctions)
|
||||
CHECK_EQ(toString(&tv, {}), "woohoo<number>");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -217,8 +217,14 @@ TEST_CASE_FIXTURE(Fixture, "add_function_at_work")
|
||||
CHECK(toString(requireType("a")) == "number");
|
||||
CHECK(toString(requireType("b")) == "add<number, string>");
|
||||
CHECK(toString(requireType("c")) == "add<string, number>");
|
||||
CHECK(toString(result.errors[0]) == "Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add");
|
||||
CHECK(toString(result.errors[1]) == "Operator '+' could not be applied to operands of types string and number; there is no corresponding overload for __add");
|
||||
CHECK(
|
||||
toString(result.errors[0]) ==
|
||||
"Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add"
|
||||
);
|
||||
CHECK(
|
||||
toString(result.errors[1]) ==
|
||||
"Operator '+' could not be applied to operands of types string and number; there is no corresponding overload for __add"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_add_function_at_work")
|
||||
@ -290,7 +296,8 @@ TEST_CASE_FIXTURE(Fixture, "internal_functions_raise_errors")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(
|
||||
toString(result.errors[0]) == "Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add"
|
||||
toString(result.errors[0]) ==
|
||||
"Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1115,6 +1115,20 @@ type Foo<T> = Foo<T> | string
|
||||
REQUIRE(err);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_adds_reduce_constraint_for_type_function")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution || !FFlag::LuauUserDefinedTypeFunctions)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type plus<T> = add<number, T>
|
||||
|
||||
local sum: plus<number> = 10
|
||||
)");
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors")
|
||||
{
|
||||
if (!FFlag::LuauUserDefinedTypeFunctions)
|
||||
|
@ -692,16 +692,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// The argument count is the same, but the errors are currently cyclic type family instance ones.
|
||||
// This isn't great, but the desired behavior here was that it didn't cause a crash and that is still true.
|
||||
// The larger fix for this behavior will likely be integration of egraph-based normalization throughout the new solver.
|
||||
// Counterintuitively, the parametr l0 is unconstrained and therefore it is valid to pass nil.
|
||||
// The new solver therefore considers that parameter to be optional.
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK("Argument count mismatch. Function expects 1 argument, but none are specified" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ("Argument count mismatch. Function '_' expects at least 1 argument, but none are specified", toString(result.errors[0]));
|
||||
CHECK_EQ("Argument count mismatch. Function 'select' expects 1 argument, but none are specified", toString(result.errors[1]));
|
||||
}
|
||||
|
@ -447,14 +447,17 @@ end
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"(Type pack 'X & Y & Z' could not be converted into 'number'; type X & Y & Z[0][0] (X) is not a subtype of number[0] (number)
|
||||
CHECK_EQ(
|
||||
R"(Type pack 'X & Y & Z' could not be converted into 'number'; type X & Y & Z[0][0] (X) is not a subtype of number[0] (number)
|
||||
type X & Y & Z[0][1] (Y) is not a subtype of number[0] (number)
|
||||
type X & Y & Z[0][2] (Z) is not a subtype of number[0] (number))",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else
|
||||
CHECK_EQ(
|
||||
toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)");
|
||||
toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overload_is_not_a_function")
|
||||
@ -497,13 +500,16 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true)
|
||||
CHECK_EQ(
|
||||
R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true)
|
||||
type boolean & false[1] (false) is not a subtype of true (true))",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else
|
||||
CHECK_EQ(
|
||||
toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
@ -522,10 +528,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
R"(Type 'boolean & false & false' could not be converted into 'true'; type boolean & false & false[0] (false) is not a subtype of true (true)
|
||||
type boolean & false & false[1] (boolean) is not a subtype of true (true)
|
||||
type boolean & false & false[2] (false) is not a subtype of true (true))",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0])
|
||||
);
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(
|
||||
toString(result.errors[0]),
|
||||
"Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
|
@ -55,11 +55,26 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
||||
end
|
||||
)";
|
||||
|
||||
const std::string expectedWithNewSolver = R"(
|
||||
function f(a:{fn:()->(unknown,...unknown)}): ()
|
||||
if type(a) == 'boolean'then
|
||||
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||
elseif a.fn()then
|
||||
local a2:{fn:()->(unknown,...unknown)}&(class|function|nil|number|string|thread|buffer|table)=a
|
||||
end
|
||||
end
|
||||
)";
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(expectedWithNewSolver, decorateWithTypes(code));
|
||||
else
|
||||
CHECK_EQ(expected, decorateWithTypes(code));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "luau-polyfill.Array.filter")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
// This test exercises the fact that we should reduce sealed/unsealed/free tables
|
||||
// res is a unsealed table with type {((T & ~nil)?) & any}
|
||||
// Because we do not reduce it fully, we cannot unify it with `Array<T> = { [number] : T}
|
||||
@ -157,6 +172,8 @@ TEST_CASE_FIXTURE(Fixture, "it_should_be_agnostic_of_actual_size")
|
||||
// For now, infer it as just a free table.
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_constrains_free_type_into_free_table")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = {}
|
||||
local b
|
||||
@ -175,6 +192,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_constrains_free_type_into_free_
|
||||
// Luau currently doesn't yet know how to allow assignments when the binding was refined.
|
||||
TEST_CASE_FIXTURE(Fixture, "while_body_are_also_refined")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Node<T> = { value: T, child: Node<T>? }
|
||||
|
||||
@ -198,6 +217,8 @@ TEST_CASE_FIXTURE(Fixture, "while_body_are_also_refined")
|
||||
// We should be type checking the metamethod at the call site of setmetatable.
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "error_on_eq_metamethod_returning_a_type_other_than_boolean")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local tab = {a = 1}
|
||||
setmetatable(tab, {__eq = function(a, b): number
|
||||
@ -258,11 +279,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
|
||||
CHECK_EQ("{ x: string, y: number }", toString(requireTypeAtPosition({5, 28})));
|
||||
|
||||
// Should be { x: nil, y: nil }
|
||||
CHECK_EQ("{ x: nil, y: nil } | { x: string, y: number }", toString(requireTypeAtPosition({7, 28})));
|
||||
CHECK_EQ("{ x: nil, y: nil }", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -341,11 +359,20 @@ TEST_CASE_FIXTURE(Fixture, "do_not_ice_when_trying_to_pick_first_of_generic_type
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("() -> ()" == toString(requireType("f")));
|
||||
CHECK("() -> ()" == toString(requireType("g")));
|
||||
CHECK("nil" == toString(requireType("x")));
|
||||
}
|
||||
else
|
||||
{
|
||||
// f and g should have the type () -> ()
|
||||
CHECK_EQ("() -> (a...)", toString(requireType("f")));
|
||||
CHECK_EQ("<a...>() -> (a...)", toString(requireType("g")));
|
||||
CHECK_EQ("any", toString(requireType("x"))); // any is returned instead of ICE for now
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
||||
{
|
||||
@ -355,6 +382,9 @@ TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
||||
local s2s: (string) -> string = id
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
||||
}
|
||||
|
||||
@ -487,6 +517,8 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
TypeArena arena;
|
||||
TypeId nilType = builtinTypes->nilType;
|
||||
|
||||
@ -569,7 +601,7 @@ return wrapStrictTable(Constants, "Constants")
|
||||
std::optional<TypeId> result = first(m->returnType);
|
||||
REQUIRE(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(any & ~(*error-type* | table))?", toString(*result));
|
||||
CHECK_EQ("unknown", toString(*result));
|
||||
else
|
||||
CHECK_MESSAGE(get<AnyType>(*result), *result);
|
||||
}
|
||||
@ -610,7 +642,11 @@ return wrapStrictTable(Constants, "Constants")
|
||||
|
||||
std::optional<TypeId> result = first(m->returnType);
|
||||
REQUIRE(result);
|
||||
CHECK(get<AnyType>(*result));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("unknown" == toString(*result));
|
||||
else
|
||||
CHECK("any" == toString(*result));
|
||||
}
|
||||
|
||||
namespace
|
||||
@ -793,6 +829,10 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_NO_ERRORS(result); // This is wrong. We should be rejecting this assignment.
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'{| x: number? |}'
|
||||
@ -803,9 +843,12 @@ caused by:
|
||||
Type 'number?' could not be converted into 'number' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function foo(t, x)
|
||||
if x == "hi" or x == "bye" then
|
||||
@ -861,15 +904,25 @@ TEST_CASE_FIXTURE(Fixture, "expected_type_should_be_a_helpful_deduction_guide_fo
|
||||
local x: Ref<number?> = useRef(nil)
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// This bug is fixed in the new solver.
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is actually wrong! Sort of. It's doing the wrong thing, it's actually asking whether
|
||||
// `{| val: number? |} <: {| val: nil |}`
|
||||
// instead of the correct way, which is
|
||||
// `{| val: nil |} <: {| val: number? |}`
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "floating_generics_should_not_be_allowed")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local assign : <T, U, V, W>(target: T, source0: U?, source1: V?, source2: W?, ...any) -> T & U & V & W = (nil :: any)
|
||||
|
||||
@ -892,6 +945,8 @@ TEST_CASE_FIXTURE(Fixture, "floating_generics_should_not_be_allowed")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
TypeArena arena;
|
||||
TypeId nilType = builtinTypes->nilType;
|
||||
|
||||
@ -935,8 +990,10 @@ TEST_CASE_FIXTURE(Fixture, "unify_more_complex_unions_that_include_nil")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_class_instances_are_invariant")
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_class_instances_are_invariant_old_solver")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
createSomeClasses(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -951,6 +1008,24 @@ TEST_CASE_FIXTURE(Fixture, "optional_class_instances_are_invariant")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_class_instances_are_invariant_new_solver")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
createSomeClasses(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(ref: {read current: Parent?})
|
||||
end
|
||||
|
||||
function bar(ref: {read current: Child?})
|
||||
foo(ref)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "luau-polyfill.Map.entries")
|
||||
{
|
||||
|
||||
@ -1000,6 +1075,9 @@ end
|
||||
// We would prefer this unification to be able to complete, but at least it should not crash
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_unification_infinite_recursion")
|
||||
{
|
||||
// The new solver doesn't recurse as heavily in this situation.
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
#if defined(_NOOPT) || defined(_DEBUG)
|
||||
ScopedFastInt LuauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 100};
|
||||
#endif
|
||||
@ -1027,17 +1105,9 @@ local tbl = require(game.A)
|
||||
tbl:f3()
|
||||
)";
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// TODO: DCR should transform RecursionLimitException into a CodeTooComplex error (currently it rethows it as InternalCompilerError)
|
||||
CHECK_THROWS_AS(frontend.check("game/B"), Luau::InternalCompilerError);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally, unification with any will not cause a 2^n normalization of a function overload
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "normalization_limit_in_unify_with_any")
|
||||
@ -1148,7 +1218,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_roact_useState_minimization")
|
||||
update("hello")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// We actually expect this code to be fine.
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "bin_prov")
|
||||
|
@ -317,14 +317,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a
|
||||
function f(a)
|
||||
assert(type(a) == "number")
|
||||
local b = a
|
||||
return b
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
REQUIRE_EQ("number", toString(requireType("b")));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("<a>(a) -> a & number" == toString(requireType("f")));
|
||||
else
|
||||
CHECK("<a>(a) -> number" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_test_a_prop")
|
||||
@ -440,15 +445,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_ty
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
|
||||
CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[0]));
|
||||
CHECK(Location{{ 7, 18}, {7, 19}} == result.errors[0].location);
|
||||
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[1]));
|
||||
CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[1]));
|
||||
CHECK(Location{{ 13, 18}, {13, 19}} == result.errors[1].location);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error")
|
||||
@ -485,7 +488,8 @@ TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("{| x: number |}" == toString(requireTypeAtPosition({4, 23})));
|
||||
// CLI-115281 - Types produced by refinements don't always get simplified
|
||||
CHECK("{ x: number? } & { x: ~(false?) }" == toString(requireTypeAtPosition({4, 23})));
|
||||
CHECK("number" == toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
|
||||
@ -699,6 +703,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_narrow_to_vector")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
else
|
||||
CHECK_EQ("*error-type*", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
@ -722,12 +729,24 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// CLI-115281 Types produced by refinements do not consistently get simplified
|
||||
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
|
||||
CHECK_EQ("(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
|
||||
|
||||
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
|
||||
CHECK_EQ("(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
|
||||
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string")
|
||||
{
|
||||
@ -844,6 +863,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_narrowed_into_nothingness")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// CLI-115281 Types produced by refinements do not consistently get simplified
|
||||
CHECK_EQ("{ x: number } & ~table", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
else
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
@ -950,7 +975,10 @@ TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("{| x: true |}?", toString(requireTypeAtPosition({3, 28})));
|
||||
{
|
||||
// CLI-115281 Types produced by refinements do not consistently get simplified
|
||||
CHECK_EQ("({ x: boolean } & { x: ~(false?) })?", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
else
|
||||
CHECK_EQ("{| x: boolean |}?", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
@ -1196,12 +1224,18 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(R"({| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
|
||||
{
|
||||
// CLI-115281 Types produced by refinements do not consistently get simplified
|
||||
CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK("({ tag: \"exists\", x: string } & { x: ~~(false?) }) | { tag: \"missing\", x: nil }" == toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
|
||||
{
|
||||
@ -1328,8 +1362,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"({ tag: "Part", x: Part })", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({ tag: "Folder", x: Folder })", toString(requireTypeAtPosition({7, 28})));
|
||||
CHECK(R"({ tag: "Part", x: Part })" == toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK(R"({ tag: "Folder", x: Folder })" == toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1340,6 +1374,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
||||
{
|
||||
// CLI-115286 - Refining via type(x) == 'vector' does not work in the new solver
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(vec)
|
||||
local X, Y, Z = vec.X, vec.Y, vec.Z
|
||||
@ -1527,6 +1564,10 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_w
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahead_of_time")
|
||||
{
|
||||
// CLI-115087 - The new solver does not consistently combine tables with
|
||||
// class types when they appear in the upper bounds of a free type.
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x): Instance
|
||||
if x:IsA("Folder") then
|
||||
@ -1819,7 +1860,7 @@ TEST_CASE_FIXTURE(Fixture, "refine_a_property_of_some_global")
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
|
||||
CHECK_EQ("~(false?)", toString(requireTypeAtPosition({4, 30})));
|
||||
CHECK_EQ("*error-type* | buffer | class | function | number | string | table | thread | true", toString(requireTypeAtPosition({4, 30})));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1843,9 +1884,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dataflow_analysis_can_tell_refinements_when_
|
||||
end
|
||||
|
||||
if typeof(s) == "nil" then
|
||||
local foo = s
|
||||
local foo = s -- line 18
|
||||
else
|
||||
local foo = s
|
||||
local foo = s -- line 20
|
||||
end
|
||||
end
|
||||
)");
|
||||
@ -1860,7 +1901,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dataflow_analysis_can_tell_refinements_when_
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({18, 28})));
|
||||
// CLI-115281 - Types produced by refinements don't always get simplified
|
||||
CHECK_EQ("nil & string", toString(requireTypeAtPosition({18, 28})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({20, 28})));
|
||||
}
|
||||
else
|
||||
@ -1948,7 +1990,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_annotations_arent_relevant_when_doing_d
|
||||
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({8, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({9, 28})));
|
||||
{
|
||||
// CLI-115478 - This should be never
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
else
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 28})));
|
||||
}
|
||||
@ -2044,7 +2089,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "conditional_refinement_should_stay_error_suppressing")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
// this test is DCR-only as an instance of DCR fixing a bug in the old solver
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function test(element: any?)
|
||||
@ -2065,6 +2109,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too")
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// CLI-114134
|
||||
CHECK("string & typeof(string)" == toString(requireTypeAtPosition(Position{2, 24})));
|
||||
}
|
||||
else
|
||||
CHECK("never" == toString(requireTypeAtPosition(Position{2, 24})));
|
||||
}
|
||||
|
||||
@ -2096,17 +2146,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
if typeof(string) == 'string' then
|
||||
local foo = string
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK("never" == toString(requireTypeAtPosition(Position{2, 24})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "ex")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -2536,7 +2536,10 @@ local y: number = tmp.p.y
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" == toString(result.errors[0]));
|
||||
CHECK(
|
||||
"Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" ==
|
||||
toString(result.errors[0])
|
||||
);
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper'
|
||||
|
@ -33,6 +33,10 @@ until _._
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint")
|
||||
{
|
||||
// CLI-114134 We need egraphs to consistently reduce the cyclic union
|
||||
// introduced by the increment here.
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local count = 0
|
||||
function most_of_the_natural_numbers(): number?
|
||||
@ -51,6 +55,27 @@ TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint")
|
||||
REQUIRE(utv != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint_using_compound_assignment")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local count = 0
|
||||
function most_of_the_natural_numbers(): number?
|
||||
if count < 10 then
|
||||
-- count = count + 1
|
||||
count += 1
|
||||
return count
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
const FunctionType* utv = get<FunctionType>(requireType("most_of_the_natural_numbers"));
|
||||
REQUIRE(utv != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "allow_specific_assign")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -95,6 +120,9 @@ TEST_CASE_FIXTURE(Fixture, "optional_arguments")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_arguments_table")
|
||||
{
|
||||
// CLI-115588 - Bidirectional inference does not happen for assignments
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a:{a:string, b:string?}
|
||||
a = {a="ok"}
|
||||
@ -209,7 +237,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_missing_property")
|
||||
CHECK_EQ("Key 'x' is missing from 'B' in the type 'A | B'", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A | B) -> number | *error-type*", toString(requireType("f")));
|
||||
CHECK_EQ("(A | B) -> number", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A | B) -> *error-type*", toString(requireType("f")));
|
||||
}
|
||||
@ -261,10 +289,6 @@ TEST_CASE_FIXTURE(Fixture, "optional_union_members")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A?) -> number | *error-type*", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A?) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
@ -282,10 +306,6 @@ TEST_CASE_FIXTURE(Fixture, "optional_union_functions")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A?) -> number | *error-type*", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A?) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
@ -303,10 +323,6 @@ TEST_CASE_FIXTURE(Fixture, "optional_union_methods")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A?) -> number | *error-type*", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A?) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
@ -456,6 +472,8 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unify_unsealed_table_union_check")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = { x = 3 }
|
||||
type A = number?
|
||||
@ -547,6 +565,9 @@ local a: XYZ = { w = 4 }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors[0]) == "Type '{ w: number }' could not be converted into 'X | Y | Z'");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'a' could not be converted into 'X | Y | Z'; none of the union options are compatible)");
|
||||
}
|
||||
|
||||
@ -559,12 +580,17 @@ local a: X? = { w = 4 }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("Type '{ w: number }' could not be converted into 'X?'" == toString(result.errors[0]));
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'a' could not be converted into 'X?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'X' because the former is missing field 'x')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
// We had a bug where a cyclic union caused a stack overflow.
|
||||
// ex type U = number | U
|
||||
@ -615,6 +641,7 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string }
|
||||
@ -690,6 +717,8 @@ TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a,b>()
|
||||
function g(x : (a) -> a?)
|
||||
@ -708,6 +737,8 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
function g(x : (number, a...) -> (number?, a...))
|
||||
@ -727,6 +758,8 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(x : (number) -> number?)
|
||||
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK
|
||||
@ -744,6 +777,8 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(x : () -> (number | string))
|
||||
local y : (() -> number) | (() -> string) = x -- OK
|
||||
@ -761,6 +796,8 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(x : (...nil) -> (...number?))
|
||||
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK
|
||||
@ -786,15 +823,27 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK(R"(Type
|
||||
'(number) -> ()'
|
||||
could not be converted into
|
||||
'((...number?) -> ()) | ((number?) -> ())')" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'(number) -> ()'
|
||||
could not be converted into
|
||||
'((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(x : () -> (number?, ...number))
|
||||
local y : (() -> (...number)) | (() -> nil) = x -- OK
|
||||
@ -824,7 +873,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} | {| x: string |}) -> {| x: number |} | {| x: string |}", toString(requireType("f")));
|
||||
CHECK_EQ("(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2")
|
||||
@ -840,10 +889,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("({ x: number } | { x: string }) -> number | string", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("({| x: number |} | {| x: string |}) -> number | string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_table_any_property")
|
||||
@ -864,6 +910,8 @@ TEST_CASE_FIXTURE(Fixture, "union_table_any_property")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_function_any_args")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(sup : ((...any) -> (...any))?, sub : ((number) -> (...any)))
|
||||
sup = sub
|
||||
@ -886,6 +934,8 @@ TEST_CASE_FIXTURE(Fixture, "optional_any")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_function_with_optional_arg")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<T>(x : T?) : {T}
|
||||
local result = {}
|
||||
|
@ -63,53 +63,9 @@ Negations.cofinite_strings_can_be_compared_for_equality
|
||||
Normalize.higher_order_function_with_annotation
|
||||
Normalize.negations_of_tables
|
||||
Normalize.specific_functions_cannot_be_negated
|
||||
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||
ProvisionalTests.discriminate_from_x_not_equal_to_nil
|
||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||
ProvisionalTests.expected_type_should_be_a_helpful_deduction_guide_for_function_calls
|
||||
ProvisionalTests.floating_generics_should_not_be_allowed
|
||||
ProvisionalTests.free_options_can_be_unified_together
|
||||
ProvisionalTests.free_options_cannot_be_unified_together
|
||||
ProvisionalTests.generic_type_leak_to_module_interface
|
||||
ProvisionalTests.generic_type_leak_to_module_interface_variadic
|
||||
ProvisionalTests.luau-polyfill.Array.filter
|
||||
ProvisionalTests.luau_roact_useState_minimization
|
||||
ProvisionalTests.optional_class_instances_are_invariant
|
||||
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
|
||||
ProvisionalTests.specialization_binds_with_prototypes_too_early
|
||||
ProvisionalTests.table_insert_with_a_singleton_argument
|
||||
ProvisionalTests.table_unification_infinite_recursion
|
||||
ProvisionalTests.typeguard_inference_incomplete
|
||||
ProvisionalTests.while_body_are_also_refined
|
||||
RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||
RefinementTest.discriminate_from_isa_of_x
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.globals_can_be_narrowed_too
|
||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage
|
||||
RefinementTest.refine_a_property_of_some_global
|
||||
RefinementTest.refine_param_of_type_folder_or_part_without_using_typeof
|
||||
RefinementTest.refine_unknown_to_table_then_clone_it
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_annotations_arent_relevant_when_doing_dataflow_analysis
|
||||
RefinementTest.type_guard_narrowed_into_nothingness
|
||||
RefinementTest.type_narrow_to_vector
|
||||
RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RefinementTest.x_is_not_instance_or_else_not_part
|
||||
ToDot.function
|
||||
ToString.exhaustive_toString_of_cyclic_table
|
||||
ToString.free_types
|
||||
ToString.named_metatable_toStringNamedFunction
|
||||
ToString.no_parentheses_around_cyclic_function_type_in_intersection
|
||||
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
||||
ToString.primitive
|
||||
ToString.toStringErrorPack
|
||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
TryUnifyTests.uninhabited_table_sub_anything
|
||||
@ -261,24 +217,4 @@ TypeSingletons.return_type_of_f_is_not_widened
|
||||
TypeSingletons.table_properties_type_error_escapes
|
||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||
TypeStatesTest.typestates_preserve_error_suppression_properties
|
||||
UnionTypes.error_detailed_optional
|
||||
UnionTypes.error_detailed_union_all
|
||||
UnionTypes.generic_function_with_optional_arg
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.less_greedy_unification_with_union_types
|
||||
UnionTypes.optional_arguments_table
|
||||
UnionTypes.optional_union_functions
|
||||
UnionTypes.optional_union_members
|
||||
UnionTypes.optional_union_methods
|
||||
UnionTypes.return_types_can_be_disjoint
|
||||
UnionTypes.table_union_write_indirect
|
||||
UnionTypes.unify_unsealed_table_union_check
|
||||
UnionTypes.union_function_any_args
|
||||
UnionTypes.union_of_functions_mentioning_generic_typepacks
|
||||
UnionTypes.union_of_functions_mentioning_generics
|
||||
UnionTypes.union_of_functions_with_mismatching_arg_arities
|
||||
UnionTypes.union_of_functions_with_mismatching_arg_variadics
|
||||
UnionTypes.union_of_functions_with_mismatching_result_arities
|
||||
UnionTypes.union_of_functions_with_mismatching_result_variadics
|
||||
UnionTypes.union_of_functions_with_variadics
|
||||
VisitType.throw_when_limit_is_exceeded
|
||||
|
Loading…
Reference in New Issue
Block a user