Sync to upstream/release/683 (#1921)

Another week, another release!

## Analysis
- Hide errors in all solver modes (not just strict mode) if the only
error is that type inference failed to complete.
- Make various analysis components solver-agnostic (`setType`, `visit`,
`Scope` methods).
- Fix an issue where type inference may fail to complete when assigning
a table's member to the table itself.
- Fix a bug when accessing a table member on a local after the local is
assigned to in an if-else block, loop, or other similar language
construct.
  - Fixes #1914.
- Fix type-checking of if-then-else expressions.
  - Fixes #1815.

---

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
Varun Saini 2025-07-18 13:46:08 -07:00 committed by GitHub
parent 6ff0650a8d
commit 0ce993fe6c
Signed by: DevComp
GPG key ID: B5690EEEBB952194
30 changed files with 626 additions and 391 deletions

View file

@ -254,7 +254,7 @@ public:
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const PushFunctionTypeConstraint& c, NotNull<const Constraint> constraint);

View file

@ -347,4 +347,18 @@ bool isApproximatelyTruthyType(TypeId ty);
// Unwraps any grouping expressions iteratively.
AstExpr* unwrapGroup(AstExpr* expr);
// These are magic types used in `TypeChecker2` and `NonStrictTypeChecker`
//
// `_luau_print` causes it's argument to be printed out, as in:
//
// local x: _luau_print<number>
//
// ... will cause `number` to be printed.
inline constexpr char kLuauPrint[] = "_luau_print";
// `_luau_force_constraint_solving_incomplete` will cause us to _always_ emit
// a constraint solving incomplete error to test semantics around that specific
// error.
inline constexpr char kLuauForceConstraintSolvingIncomplete[] = "_luau_force_constraint_solving_incomplete";
} // namespace Luau

View file

@ -11,6 +11,7 @@
LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSolverAgnosticVisitType)
namespace Luau
{
@ -231,7 +232,20 @@ struct GenericTypeVisitor
}
else if (auto ftv = get<FreeType>(ty))
{
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverAgnosticVisitType)
{
if (visit(ty, *ftv))
{
// Regardless of the choice of solver, all free types are guaranteed to have
// lower and upper bounds
LUAU_ASSERT(ftv->lowerBound);
LUAU_ASSERT(ftv->upperBound);
traverse(ftv->lowerBound);
traverse(ftv->upperBound);
}
}
else if (FFlag::LuauSolverV2)
{
if (visit(ty, *ftv))
{
@ -281,7 +295,7 @@ struct GenericTypeVisitor
{
for (auto& [_name, prop] : ttv->props)
{
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType)
{
if (auto ty = prop.readTy)
traverse(*ty);
@ -319,7 +333,7 @@ struct GenericTypeVisitor
{
for (const auto& [name, prop] : etv->props)
{
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType)
{
if (auto ty = prop.readTy)
traverse(*ty);

View file

@ -21,7 +21,6 @@ LUAU_FASTFLAG(LuauNotAllBinaryTypeFunsHaveDefaults)
LUAU_FASTFLAG(LuauEmptyStringInKeyOf)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
LUAU_FASTFLAG(LuauOccursCheckForRefinement)

View file

@ -1,10 +1,12 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Constraint.h"
#include "Luau/TypeFunction.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
namespace Luau
{
@ -61,8 +63,11 @@ struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor
return false;
}
bool visit(TypeId, const TypeFunctionInstanceType&) override
bool visit(TypeId, const TypeFunctionInstanceType& tfit) override
{
if (FFlag::LuauForceSimplifyConstraint)
return tfit.function->canReduceGenerics;
else
return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions;
}
};
@ -112,8 +117,11 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false;
}
bool visit(TypeId, const TypeFunctionInstanceType&) override
bool visit(TypeId, const TypeFunctionInstanceType& tfit) override
{
if (FFlag::LuauForceSimplifyConstraint)
return tfit.function->canReduceGenerics;
else
return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions;
}
};

View file

@ -39,20 +39,18 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases)
LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauFollowTypeAlias)
LUAU_FASTFLAGVARIABLE(LuauFollowExistingTypeFunction)
LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType)
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements)
LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType)
LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex)
namespace Luau
{
@ -583,17 +581,12 @@ void ConstraintGenerator::computeRefinement(
// if we have a negative sense, then we need to negate the discriminant
if (!sense)
{
if (FFlag::LuauAvoidDoubleNegation)
{
if (auto nt = get<NegationType>(follow(discriminantTy)))
discriminantTy = nt->ty;
else
discriminantTy = arena->addType(NegationType{discriminantTy});
}
else
discriminantTy = arena->addType(NegationType{discriminantTy});
}
if (eq)
discriminantTy = createTypeFunctionInstance(builtinTypeFunctions().singletonFunc, {discriminantTy}, {}, scope, location);
@ -958,8 +951,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
auto addToEnvironment =
[this, &globalNameCollector](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeFun tf, size_t level)
{
if (auto ty = get<TypeFunctionInstanceType>(FFlag::LuauFollowTypeAlias ? follow(tf.type) : tf.type);
ty && ty->userFuncData.definition)
if (auto ty = get<TypeFunctionInstanceType>(follow(tf.type)); ty && ty->userFuncData.definition)
{
if (userFuncData.environmentFunction.find(name))
return;
@ -972,8 +964,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location};
}
}
else if (FFlag::LuauUserTypeFunctionAliases &&
!get<TypeFunctionInstanceType>(FFlag::LuauFollowTypeAlias ? follow(tf.type) : tf.type))
else if (!get<TypeFunctionInstanceType>(follow(tf.type)))
{
if (userFuncData.environmentAlias.find(name))
return;
@ -995,7 +986,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
};
// Go up the scopes to register type functions and alises, but without reaching into the global scope
for (Scope* curr = scope.get(); curr && (!FFlag::LuauUserTypeFunctionAliases || curr != globalScope.get()); curr = curr->parent.get())
for (Scope* curr = scope.get(); curr && curr != globalScope.get(); curr = curr->parent.get())
{
for (auto& [name, tf] : curr->privateTypeBindings)
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf, level);
@ -1931,18 +1922,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
if (!existingFunctionTy)
ice->ice("checkAliases did not populate type function name", function->nameLocation);
if (FFlag::LuauFollowExistingTypeFunction)
{
TypeId unpackedTy = follow(*existingFunctionTy);
if (auto bt = get<BlockedType>(unpackedTy); bt && nullptr == bt->getOwner())
emplaceType<BoundType>(asMutable(unpackedTy), generalizedTy);
}
else
{
if (auto bt = get<BlockedType>(*existingFunctionTy); bt && nullptr == bt->getOwner())
emplaceType<BoundType>(asMutable(*existingFunctionTy), generalizedTy);
}
return ControlFlow::None;
}
@ -2807,7 +2790,7 @@ Inference ConstraintGenerator::checkIndexName(
if (key)
{
if (auto ty = lookup(scope, indexLocation, key->def))
if (auto ty = lookup(scope, indexLocation, key->def, !FFlag::LuauDoNotPrototypeTableIndex))
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
@ -3119,6 +3102,9 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifEls
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty;
if (FFlag::LuauInferActualIfElseExprType)
return Inference{makeUnion(scope, ifElse->location, thenType, elseType)};
else
return Inference{expectedType ? *expectedType : makeUnion(scope, ifElse->location, thenType, elseType)};
}

View file

@ -35,7 +35,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint)
namespace Luau
{
@ -905,7 +905,7 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
else if (auto eqc = get<EqualityConstraint>(*constraint))
success = tryDispatch(*eqc, constraint);
else if (auto sc = get<SimplifyConstraint>(*constraint))
success = tryDispatch(*sc, constraint);
success = tryDispatch(*sc, constraint, force);
else if (auto pftc = get<PushFunctionTypeConstraint>(*constraint))
success = tryDispatch(*pftc, constraint);
else
@ -2286,7 +2286,6 @@ bool ConstraintSolver::tryDispatchHasIndexer(
continue;
r = follow(r);
if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get<ErrorType>(r))
results.insert(r);
}
@ -2911,7 +2910,7 @@ struct FindAllUnionMembers : TypeOnceVisitor
}
};
bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint)
bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint, bool force)
{
TypeId target = follow(c.ty);
@ -2927,7 +2926,14 @@ bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull<const Co
FindAllUnionMembers finder;
finder.traverse(target);
if (!finder.blockedTys.empty())
// Clip this comment with LuauForceSimplifyConstraint
//
// The flagging logic is roughly: if `LuauForceSimplifyConstraint` is
// _not_ set, then we ignore the input `force`, as the RHS of the &&
// is always true. Otherwise, when the flag is set, the RHS of the &&
// is equivalent to `!force`: we only block on types when we're not
// being force solved.
if (!finder.blockedTys.empty() && !(FFlag::LuauForceSimplifyConstraint && force))
{
for (TypeId ty : finder.blockedTys)
block(ty, constraint);
@ -3874,6 +3880,19 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
if (!isReferenceCountedType(target))
return;
if (FFlag::LuauForceSimplifyConstraint)
{
// This can happen in the _very_ specific case of:
//
// local Tbl = {}
// Tbl.__index = Tbl
//
// This would probably not be required if table type stating worked in
// a reasonable manner.
if (source == target)
return;
}
auto sourceRefs = unresolvedConstraints.find(source);
if (sourceRefs)
{

View file

@ -44,10 +44,11 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypeVisitor)
LUAU_FASTFLAGVARIABLE(LuauTrackTypeAllocations)
LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete)
namespace Luau
{
@ -1576,7 +1577,7 @@ ModulePtr check(
for (auto& [name, tf] : result->exportedTypeBindings)
tf.type = builtinTypes->errorType;
}
else if (FFlag::LuauNewSolverTypecheckCatchTimeouts)
else
{
try
{
@ -1622,40 +1623,15 @@ ModulePtr check(
result->cancelled = true;
}
}
else
// if the only error we're producing is one about constraint solving being incomplete, we can silence it.
// this means we won't give this warning if types seem totally nonsensical, but there are no other errors.
// this is probably, on the whole, a good decision to not annoy users though.
if (FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
{
switch (mode)
{
case Mode::Nonstrict:
Luau::checkNonStrict(
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
iceHandler,
NotNull{&unifierState},
NotNull{&dfg},
NotNull{&limits},
sourceModule,
result.get()
);
break;
case Mode::Definition:
// fallthrough intentional
case Mode::Strict:
Luau::check(
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull{&unifierState},
NotNull{&limits},
logger.get(),
sourceModule,
result.get()
);
break;
case Mode::NoCheck:
break;
};
if (result->errors.size() == 1 && get<ConstraintSolvingIncompleteError>(result->errors[0]) &&
!FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete)
result->errors.clear();
}
if (FFlag::LuauExpectedTypeVisitor)

View file

@ -15,7 +15,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
namespace Luau
{
@ -306,13 +305,10 @@ void Module::clonePublicInterface_DEPRECATED(NotNull<BuiltinTypes> builtinTypes,
ty = clonePublicInterface.cloneType(ty);
}
if (FFlag::LuauUserTypeFunctionAliases)
{
for (auto& tf : typeFunctionAliases)
{
*tf = clonePublicInterface.cloneTypeFun(*tf);
}
}
// Copy external stuff over to Module itself
this->returnType = moduleScope->returnType;
@ -350,13 +346,10 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
ty = clonePublicInterface.cloneType(ty);
}
if (FFlag::LuauUserTypeFunctionAliases)
{
for (auto& tf : typeFunctionAliases)
{
*tf = clonePublicInterface.cloneTypeFun(*tf);
}
}
// Copy external stuff over to Module itself
this->returnType = moduleScope->returnType;

View file

@ -863,9 +863,20 @@ struct NonStrictTypeChecker
void visit(AstTypeReference* ty)
{
// No further validation is necessary in this case. The main logic for
// _luau_print is contained in lookupAnnotation.
if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print")
if (FFlag::DebugLuauMagicTypes)
{
// No further validation is necessary in this case.
if (ty->name == kLuauPrint)
return;
if (ty->name == kLuauForceConstraintSolvingIncomplete)
{
reportError(ConstraintSolvingIncompleteError{}, ty->location);
return;
}
}
if (FFlag::DebugLuauMagicTypes && (ty->name == kLuauPrint || ty->name == kLuauForceConstraintSolvingIncomplete))
return;
for (const AstTypeOrPack& param : ty->parameters)

View file

@ -4,6 +4,8 @@
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauScopeMethodsAreSolverAgnostic)
namespace Luau
{
@ -217,6 +219,13 @@ std::optional<std::pair<Symbol, Binding>> Scope::linearSearchForBindingPair(cons
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
void Scope::inheritAssignments(const ScopePtr& childScope)
{
if (FFlag::LuauScopeMethodsAreSolverAgnostic)
{
for (const auto& [k, a] : childScope->lvalueTypes)
lvalueTypes[k] = a;
}
else
{
if (!FFlag::LuauSolverV2)
return;
@ -224,11 +233,12 @@ void Scope::inheritAssignments(const ScopePtr& childScope)
for (const auto& [k, a] : childScope->lvalueTypes)
lvalueTypes[k] = a;
}
}
// Updates the `this` scope with the refinements from the `childScope` excluding ones that doesn't exist in `this`.
void Scope::inheritRefinements(const ScopePtr& childScope)
{
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2 || FFlag::LuauScopeMethodsAreSolverAgnostic)
{
for (const auto& [k, a] : childScope->rvalueRefinements)
{

View file

@ -32,6 +32,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticVisitType)
LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticSetType)
namespace Luau
{
@ -722,7 +724,7 @@ TypeId Property::type_DEPRECATED() const
void Property::setType(TypeId ty)
{
readTy = ty;
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticSetType)
writeTy = ty;
}

View file

@ -32,13 +32,14 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
namespace Luau
{
@ -48,7 +49,6 @@ namespace Luau
using PrintLineProc = void (*)(const std::string&);
extern PrintLineProc luauPrintLine;
/* Push a scope onto the end of a stack for the lifetime of the StackPusher instance.
* TypeChecker2 uses this to maintain knowledge about which scope encloses every
* given AstNode.
@ -289,11 +289,11 @@ void check(
typeChecker.visit(sourceModule.root);
// if the only error we're producing is one about constraint solving being incomplete, we can silence it.
// this means we won't give this warning if types seem totally nonsensical, but there are no other errors.
// this is probably, on the whole, a good decision to not annoy users though.
if (!FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
{
if (module->errors.size() == 1 && get<ConstraintSolvingIncompleteError>(module->errors[0]))
module->errors.clear();
}
unfreeze(module->interfaceTypes);
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
@ -539,7 +539,7 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation)
{
if (FFlag::DebugLuauMagicTypes)
{
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->name == "_luau_print" && ref->parameters.size > 0)
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->name == kLuauPrint && ref->parameters.size > 0)
{
if (auto ann = ref->parameters.data[0].type)
{
@ -550,6 +550,11 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation)
return follow(argTy);
}
}
else if (auto ref = annotation->as<AstTypeReference>(); ref && ref->name == kLuauForceConstraintSolvingIncomplete)
{
reportError(ConstraintSolvingIncompleteError{}, ref->location);
return builtinTypes->anyType;
}
}
TypeId* ty = module->astResolvedTypes.find(annotation);
@ -1288,7 +1293,7 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
// We will not visit type aliases that do not have an associated scope,
// this means that (probably) this was a duplicate type alias or a
// type alias with an illegal name (like `typeof`).
if (FFlag::LuauSkipMalformedTypeAliases && !module->astScopes.contains(stat))
if (!module->astScopes.contains(stat))
return;
visitGenerics(stat->generics, stat->genericPacks);
@ -2634,7 +2639,7 @@ void TypeChecker2::visit(AstTypeReference* ty)
{
// No further validation is necessary in this case. The main logic for
// _luau_print is contained in lookupAnnotation.
if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print")
if (FFlag::DebugLuauMagicTypes && (ty->name == kLuauPrint || ty->name == kLuauForceConstraintSolvingIncomplete))
return;
for (const AstTypeOrPack& param : ty->parameters)
@ -3051,6 +3056,30 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT
auto exprType = follow(lookupType(expr));
expectedType = follow(expectedType);
if (FFlag::LuauInferActualIfElseExprType)
{
if (auto group = expr->as<AstExprGroup>())
{
return testPotentialLiteralIsSubtype(group->expr, expectedType);
}
else if (auto ifElse = expr->as<AstExprIfElse>())
{
bool passes = testPotentialLiteralIsSubtype(ifElse->trueExpr, expectedType);
passes &= testPotentialLiteralIsSubtype(ifElse->falseExpr, expectedType);
return passes;
}
else if (auto binExpr = expr->as<AstExprBinary>(); binExpr && binExpr->op == AstExprBinary::Or)
{
// In this case: `{ ... } or { ... }` is literal _enough_ that
// we should do this covariant check.
auto relaxedExpectedLhs = module->internalTypes.addType(UnionType{{builtinTypes->falsyType, expectedType}});
bool passes = testPotentialLiteralIsSubtype(binExpr->left, relaxedExpectedLhs);
passes &= testPotentialLiteralIsSubtype(binExpr->right, expectedType);
return passes;
}
// FIXME: We probably should do a check for `and` here.
}
auto exprTable = expr->as<AstExprTable>();
auto exprTableType = get<TableType>(exprType);
auto expectedTableType = get<TableType>(expectedType);

View file

@ -37,7 +37,6 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement)

View file

@ -12,8 +12,6 @@
#include "lua.h"
#include "lualib.h"
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
namespace Luau
{
@ -200,15 +198,12 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
for (auto typeParam : typeParams)
check.traverse(follow(typeParam));
if (FFlag::LuauUserTypeFunctionAliases)
{
// Check that our environment doesn't depend on any type aliases that are blocked
for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias)
{
if (definition.first->typeParams.empty() && definition.first->typePackParams.empty())
check.traverse(follow(definition.first->type));
}
}
if (!check.blockingTypes.empty())
return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}};
@ -281,8 +276,6 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
}
}
if (FFlag::LuauUserTypeFunctionAliases)
{
for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias)
{
// Filter visibility based on original scope depth
@ -312,7 +305,6 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
}
}
}
}
lua_setreadonly(L, -1, true);
lua_pop(L, 2);
@ -388,4 +380,4 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
return {retTypeId, Reduction::MaybeOk, {}, {}, std::nullopt, ctx->typeFunctionRuntime->messages};
}
}
} // namespace Luau

View file

@ -15,8 +15,6 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauUnrefExisting, false)
/*
* This file contains most implementations of core Lua APIs from lua.h.
*
@ -1448,8 +1446,6 @@ void lua_unref(lua_State* L, int ref)
global_State* g = L->global;
LuaTable* reg = hvalue(registry(L));
if (DFFlag::LuauUnrefExisting)
{
const TValue* slot = luaH_getnum(reg, ref);
api_check(L, slot != luaO_nilobject);
@ -1461,13 +1457,6 @@ void lua_unref(lua_State* L, int ref)
g->registryfree = ref;
}
else
{
TValue* slot = luaH_setnum(L, reg, ref);
setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable
g->registryfree = ref;
}
}
void lua_setuserdatatag(lua_State* L, int idx, int tag)
{

View file

@ -11,8 +11,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauYieldableContinuations)
// convert a stack index to positive
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
@ -355,8 +353,6 @@ const char* luaL_typename(lua_State* L, int idx)
int luaL_callyieldable(lua_State* L, int nargs, int nresults)
{
LUAU_ASSERT(FFlag::LuauYieldableContinuations);
api_check(L, iscfunction(L->ci->func));
Closure* cl = clvalue(L->ci->func);
api_check(L, cl->c.cont);

View file

@ -11,8 +11,6 @@
#include <stdio.h>
#include <stdlib.h>
LUAU_FASTFLAG(LuauYieldableContinuations)
static void writestring(const char* s, size_t l)
{
fwrite(s, 1, l, stdout);
@ -296,19 +294,8 @@ static int luaB_pcally(lua_State* L)
// any errors from this point on are handled by continuation
L->ci->flags |= LUA_CALLINFO_HANDLE;
if (!FFlag::LuauYieldableContinuations)
{
// maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls++;
}
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0);
if (!FFlag::LuauYieldableContinuations)
{
L->baseCcalls--;
}
// necessary to accomodate functions that return lots of values
expandstacklimit(L, L->top);
@ -358,19 +345,8 @@ static int luaB_xpcally(lua_State* L)
StkId errf = L->base;
StkId func = L->base + 1;
if (!FFlag::LuauYieldableContinuations)
{
// maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls++;
}
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf));
if (!FFlag::LuauYieldableContinuations)
{
L->baseCcalls--;
}
// necessary to accommodate functions that return lots of values
expandstacklimit(L, L->top);

View file

@ -17,8 +17,6 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauYieldableContinuations)
// keep max stack allocation request under 1GB
#define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024)
@ -262,8 +260,6 @@ void luaD_call(lua_State* L, StkId func, int nresults)
if (++L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L);
if (FFlag::LuauYieldableContinuations)
{
// when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls)
bool fromyieldableccall = false;
@ -313,28 +309,6 @@ void luaD_call(lua_State* L, StkId func, int nresults)
if (nresults != LUA_MULTRET && !yielded)
L->top = restorestack(L, funcoffset) + nresults;
}
else
{
ptrdiff_t old_func = savestack(L, func);
if (luau_precall(L, func, nresults) == PCRLUA)
{ // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
luau_execute(L); // call it
if (!oldactive)
L->isactive = false;
}
if (nresults != LUA_MULTRET)
L->top = restorestack(L, old_func) + nresults;
}
L->nCcalls--;
luaC_checkGC(L);
@ -380,18 +354,9 @@ static void resume_continue(lua_State* L)
// C continuation; we expect this to be followed by Lua continuations
int n = cl->c.cont(L, 0);
if (FFlag::LuauYieldableContinuations)
{
// continuation can break or yield again
if (L->status == LUA_BREAK || L->status == LUA_YIELD)
break;
}
else
{
// Continuation can break again
if (L->status == LUA_BREAK)
break;
}
luau_poscall(L, L->top - n);
}
@ -436,7 +401,7 @@ static void resume(lua_State* L, void* ud)
// finish interrupted execution of `OP_CALL'
luau_poscall(L, firstArg);
}
else if (FFlag::LuauYieldableContinuations)
else
{
// restore arguments we have protected for C continuation
L->base = L->ci->base;
@ -647,7 +612,7 @@ static void restore_stack_limit(lua_State* L)
int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
{
unsigned short oldnCcalls = L->nCcalls;
unsigned short oldbaseCcalls = FFlag::LuauYieldableContinuations ? L->baseCcalls : 0;
unsigned short oldbaseCcalls = L->baseCcalls;
ptrdiff_t old_ci = saveci(L, L->ci);
bool oldactive = L->isactive;
int status = luaD_rawrunprotected(L, func, u);
@ -681,10 +646,8 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
bool yieldable = L->nCcalls <= L->baseCcalls; // Inlined logic from 'lua_isyieldable' to avoid potential for an out of line call.
// restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
// restore nCcalls and baseCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
L->nCcalls = oldnCcalls;
if (FFlag::LuauYieldableContinuations)
L->baseCcalls = oldbaseCcalls;
// an error occurred, check if we have a protected error callback

View file

@ -14,8 +14,6 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauGcAgainstOom, false)
/*
* Luau uses an incremental non-generational non-moving mark&sweep garbage collector.
*
@ -514,12 +512,7 @@ static size_t propagatemark(global_State* g)
// we could shrink stack at any time but we opt to do it during initial mark to do that just once per cycle
if (g->gcstate == GCSpropagate)
{
if (DFFlag::LuauGcAgainstOom)
shrinkstackprotected(th);
else
shrinkstack(th);
}
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
}
@ -634,12 +627,7 @@ static size_t cleartable(lua_State* L, GCObject* l)
{
// shrink at 37.5% occupancy
if (activevalues < sizenode(h) * 3 / 8)
{
if (DFFlag::LuauGcAgainstOom)
tableresizeprotected(L, h, activevalues);
else
luaH_resizehash(L, h, activevalues);
}
}
}
@ -706,12 +694,7 @@ static void shrinkbuffers(lua_State* L)
global_State* g = L->global;
// check size of string hash
if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2)
{
if (DFFlag::LuauGcAgainstOom)
stringresizeprotected(L, g->strt.size / 2); // table is too big
else
luaS_resize(L, g->strt.size / 2); // table is too big
}
}
static void shrinkbuffersfull(lua_State* L)
@ -722,12 +705,7 @@ static void shrinkbuffersfull(lua_State* L)
while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2)
hashsize /= 2;
if (hashsize != g->strt.size)
{
if (DFFlag::LuauGcAgainstOom)
stringresizeprotected(L, hashsize); // table is too big
else
luaS_resize(L, hashsize); // table is too big
}
}
static bool deletegco(void* context, lua_Page* page, GCObject* gco)

View file

@ -14,8 +14,6 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauHeapNameDetails)
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{
LUAU_ASSERT(!isdead(g, t));
@ -728,20 +726,10 @@ static void enumclosure(EnumContext* ctx, Closure* cl)
char buf[LUA_IDSIZE];
if (FFlag::LuauHeapNameDetails)
{
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
}
else
{
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined);
}
enumnode(ctx, obj2gco(cl), sizeLclosure(cl->nupvalues), buf);
}
@ -809,21 +797,10 @@ static void enumthread(EnumContext* ctx, lua_State* th)
char buf[LUA_IDSIZE];
if (FFlag::LuauHeapNameDetails)
{
if (p->source)
snprintf(buf, sizeof(buf), "thread at %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "thread at %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
}
else
{
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined);
}
enumnode(ctx, obj2gco(th), size, buf);
}
@ -856,8 +833,6 @@ static void enumproto(EnumContext* ctx, Proto* p)
ctx->edge(ctx->context, enumtopointer(obj2gco(p)), p->execdata, "[native]");
}
if (FFlag::LuauHeapNameDetails)
{
char buf[LUA_IDSIZE];
if (p->source)
@ -866,11 +841,6 @@ static void enumproto(EnumContext* ctx, Proto* p)
snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
enumnode(ctx, obj2gco(p), size, buf);
}
else
{
enumnode(ctx, obj2gco(p), size, p->source ? getstr(p->source) : NULL);
}
if (p->sizek)
enumedges(ctx, obj2gco(p), p->k, p->sizek, "constants");

View file

@ -36,10 +36,7 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauYieldableContinuations)
LUAU_FASTFLAG(LuauHeapNameDetails)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_DYNAMIC_FASTFLAG(LuauGcAgainstOom)
static lua_CompileOptions defaultOptions()
{
@ -785,8 +782,6 @@ static void* blockableRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
TEST_CASE("GC")
{
ScopedFastFlag luauGcAgainstOom{DFFlag::LuauGcAgainstOom, true};
runConformance(
"gc.luau",
[](lua_State* L)
@ -1062,8 +1057,6 @@ int passthroughCallWithStateContinuation(lua_State* L, int status)
TEST_CASE("CYield")
{
ScopedFastFlag luauYieldableContinuations{FFlag::LuauYieldableContinuations, true};
runConformance(
"cyield.luau",
[](lua_State* L)
@ -2322,8 +2315,6 @@ TEST_CASE("StringConversion")
TEST_CASE("GCDump")
{
ScopedFastFlag luauHeapNameDetails{FFlag::LuauHeapNameDetails, true};
// internal function, declared in lgc.h - not exposed via lua.h
extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
extern void luaC_enumheap(

View file

@ -12,6 +12,10 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
TEST_SUITE_BEGIN("NonstrictModeTests");
TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
@ -315,4 +319,39 @@ TEST_CASE_FIXTURE(Fixture, "returning_too_many_values")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden_nonstrict")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::DebugLuauMagicTypes, true},
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
};
CheckResult results = check(R"(
--!nonstrict
local function _f(_x: _luau_force_constraint_solving_incomplete) end
)");
LUAU_REQUIRE_NO_ERRORS(results);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "non_standalone_constraint_solving_incomplete_is_hidden_nonstrict")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::DebugLuauMagicTypes, true},
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
};
CheckResult results = check(R"(
--!nonstrict
local function _f(_x: _luau_force_constraint_solving_incomplete) end
math.abs("pls")
)");
LUAU_REQUIRE_ERROR_COUNT(2, results);
CHECK(get<CheckedFunctionCallError>(results.errors[0]));
CHECK(get<ConstraintSolvingIncompleteError>(results.errors[1]));
}
TEST_SUITE_END();

View file

@ -11,9 +11,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauFollowTypeAlias)
LUAU_FASTFLAG(LuauFollowExistingTypeFunction)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauTypeFunctionSerializeFollowMetatable)
@ -2257,7 +2254,6 @@ TEST_CASE_FIXTURE(Fixture, "typeof_is_not_a_valid_type_function_name")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_call")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
CheckResult result = check(R"(
type Test<T> = T?
@ -2279,7 +2275,6 @@ local y: foo<{b: number}> = { b = 2 }
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_values")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
CheckResult result = check(R"(
type Test = { a: number }
@ -2303,8 +2298,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_call_with_reduction")
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
CheckResult result = check(R"(
type Test<T> = rawget<T, "a">
@ -2327,8 +2320,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_implicit_export")
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
fileResolver.source["game/A"] = R"(
type Test<T> = rawget<T, "a">
@ -2356,7 +2347,6 @@ local y: Test.foo<{ a: string }> = "x"
TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_not_too_many_globals")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
CheckResult result = check(R"(
type function get()
@ -2372,7 +2362,6 @@ local function ok(idx: get<>): number return idx end
TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_not_enough_arguments")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
CheckResult result = check(R"(
type Test<A, B> = (a: A, b: B) -> A
@ -2391,7 +2380,6 @@ local function ok(idx: get<>): number return idx end
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_can_call_packs")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
CheckResult result = check(R"(
type Test<T, U...> = (U...) -> T
@ -2414,7 +2402,6 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors")
return;
ScopedFastFlag sff[] = {
{FFlag::LuauUserTypeFunctionAliases, true},
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
};
@ -2439,7 +2426,6 @@ local function ok(idx: get<>): number return idx end
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_unreferenced_do_not_block")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
CheckResult result = check(R"(
type function foo(t)
@ -2458,8 +2444,6 @@ local x: foo<boolean>
TEST_CASE_FIXTURE(Fixture, "udtf_type_alias_registration_follows")
{
ScopedFastFlag luauFollowTypeAlias{FFlag::LuauFollowTypeAlias, true};
LUAU_REQUIRE_ERRORS(check(R"(
export type t110 = ""type--"
function _<t32...,t0...,t0...,t0...>(...):(any)&(any)
@ -2490,7 +2474,6 @@ end
TEST_CASE_FIXTURE(Fixture, "udtf_double_definition")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauFollowExistingTypeFunction{FFlag::LuauFollowExistingTypeFunction, true};
CheckResult result = check(R"(
type function t0<A>()

View file

@ -11,7 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAG(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
TEST_SUITE_BEGIN("TypeAliases");
@ -1257,10 +1256,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases")
TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_bad_name")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauSkipMalformedTypeAliases, true},
};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type typeof = typeof(nil :: any)
@ -1276,7 +1272,6 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_duplicate_with_typeof")
//
// type Foo = typeof(setmetatable({} :: SomeType, {} :: SomeMetatableType))
//
ScopedFastFlag _{FFlag::LuauSkipMalformedTypeAliases, true};
CheckResult result = check(R"(
type A = typeof(nil :: any)
type A = typeof(nil :: any)

View file

@ -16,6 +16,7 @@ using std::nullopt;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic)
TEST_SUITE_BEGIN("TypeInferExternTypes");
@ -894,4 +895,28 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "cyclic_tables_are_assumed_to_be_compatible
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(ExternTypeFixture, "ice_while_checking_script_due_to_scopes_not_being_solver_agnostic")
{
// This is intentional - if LuauSolverV2 is false, but we elect the new solver, we should still follow
// new solver code paths.
// This is necessary to repro an ice that can occur in studio
ScopedFastFlag luauSolverOff{FFlag::LuauSolverV2, false};
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
ScopedFastFlag sff{FFlag::LuauScopeMethodsAreSolverAgnostic, true};
auto result = check(R"(
local function ExitSeat(player, character, seat, weld)
--Find vehicle model
local model
local newParent = seat
repeat
model = newParent
newParent = model.Parent
until newParent.ClassName ~= "Model"
local part, _ = Raycast(seat.Position, dir, dist, {character, model})
end
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -17,9 +17,10 @@ LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauRefineNoRefineAlways)
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
using namespace Luau;
@ -1271,10 +1272,7 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip
TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauAvoidDoubleNegation, true},
{FFlag::LuauRefineTablesWithReadType, true},
};
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
CheckResult result = check(R"(
type T = {tag: "missing", x: nil} | {tag: "exists", x: string}
@ -2248,6 +2246,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too")
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
{FFlag::LuauForceSimplifyConstraint, true},
};
CheckResult result = check(R"(
local function isIndexKey(k, contiguousLength)
return type(k) == "number"
@ -2257,7 +2262,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction"
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
LUAU_REQUIRE_ERROR_COUNT(3, result);
// For some reason we emit three error here.
for (const auto& e : result.errors)
CHECK(get<ExplicitFunctionAnnotationRecommended>(e));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant")
@ -2846,4 +2855,48 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_by_no_refine_should_always_reduce")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "table_name_index_without_prior_assignment_from_branch")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDoNotPrototypeTableIndex, true},
};
// The important part of this test case is:
// - `CharEntry` is represented as a phi node in the data flow graph;
// - We never _set_ `CharEntry.Player` prior to accessing it.
CheckResult results = check(R"(
local GetDictionary : (unknown, boolean) -> { Player: {} }? = nil :: any
local CharEntry = GetDictionary(nil, false)
if not CharEntry then
CharEntry = GetDictionary(nil, true)
end
local x = CharEntry.Player
)");
LUAU_REQUIRE_ERROR_COUNT(1, results);
CHECK(get<OptionalValueAccess>(results.errors[0]));
CHECK_EQ("{ }", toString(requireType("x")));
}
TEST_CASE_FIXTURE(Fixture, "cli_120460_table_access_on_phi_node")
{
ScopedFastFlag _{FFlag::LuauDoNotPrototypeTableIndex, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
local function foo(bar: string): string
local baz: boolean = true
if baz then
local _ = (bar:sub(1))
else
local _ = (bar:sub(1))
end
return bar:sub(2) -- previously this would be `...never`
end
)"));
}
TEST_SUITE_END();

View file

@ -37,6 +37,9 @@ LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
TEST_SUITE_BEGIN("TableTests");
@ -6108,4 +6111,67 @@ TEST_CASE_FIXTURE(Fixture, "oss_1888_and_or_subscriptable")
)"));
}
TEST_CASE_FIXTURE(Fixture, "cli_119126_regression")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck2, true},
};
CheckResult results = check(R"(
type literals = "foo" | "bar" | "foobar"
local exampleA: {[literals]: string} = {
foo = '1',
bar = 2,
foobar = 3,
}
)");
LUAU_REQUIRE_ERROR_COUNT(2, results);
for (const auto& err: results.errors)
{
auto e = get<TypeMismatch>(err);
REQUIRE(e);
CHECK_EQ("string", toString(e->wantedType));
CHECK_EQ("number", toString(e->givenType));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assertion")
{
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDoNotPrototypeTableIndex, true}};
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
type WallHolder = {
__type: "Model",
Wall: {
__type: "BasePart",
age: number,
},
}
local walls = {
{ name = "Part1" },
{ name = "Part2" },
{ name = "Wall" },
}
local baseWall: WallHolder?
for _, wall in walls do
if wall.name == "Wall" then
baseWall = wall :: WallHolder
end
end
assert(baseWall, "Failed to get base wall when creating room props")
local myAge = baseWall.Wall.age
)"));
CHECK_EQ("number", toString(requireType("myAge")));
}
TEST_SUITE_END();

View file

@ -25,8 +25,6 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
@ -35,6 +33,12 @@ LUAU_FASTFLAG(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAG(LuauOccursCheckForRefinement)
LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
using namespace Luau;
@ -2118,10 +2122,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_missing_follow_table_freeze")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::timeout(0.5))
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauAvoidDoubleNegation, true},
};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// We don't care about errors, only that we don't OOM during typechecking.
LUAU_REQUIRE_ERRORS(check(R"(
local _ = _
@ -2153,8 +2155,6 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union")
{
ScopedFastFlag _{FFlag::LuauInsertErrorTypesIntoIndexerResult, true};
LUAU_REQUIRE_ERRORS(check(R"(
local _ = nil
repeat
@ -2442,4 +2442,153 @@ end then _._G else ...
)"));
}
TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauInferActualIfElseExprType, true},
// This is needed so that we don't hide the string literal free types
// behind a `union<_, _>`
{FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck2, true},
};
CheckResult results = check(R"(
--!strict
local item: "foo" = "bar"
item = if true then "foo" else "foo"
local item2: "foo" = if true then "doge" else "doge2"
)");
LUAU_REQUIRE_ERROR_COUNT(3, results);
CHECK_EQ(results.errors[0].location, Location{{2, 28}, {2, 33}});
auto err1 = get<TypeMismatch>(results.errors[0]);
REQUIRE(err1);
CHECK_EQ("\"foo\"", toString(err1->wantedType));
CHECK_EQ("\"bar\"", toString(err1->givenType));
CHECK_EQ(results.errors[1].location, Location{{5, 42}, {5, 48}});
auto err2 = get<TypeMismatch>(results.errors[1]);
REQUIRE(err2);
CHECK_EQ("\"foo\"", toString(err2->wantedType));
CHECK_EQ("\"doge\"", toString(err2->givenType));
CHECK_EQ(results.errors[2].location, Location{{5, 54}, {5, 61}});
auto err3 = get<TypeMismatch>(results.errors[2]);
REQUIRE(err3);
CHECK_EQ("\"foo\"", toString(err3->wantedType));
CHECK_EQ("\"doge2\"", toString(err3->givenType));
}
TEST_CASE_FIXTURE(Fixture, "if_then_else_bidirectional_inference")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauInferActualIfElseExprType, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck2, true},
};
CheckResult results = check(R"(
type foo = {
bar: (() -> string)?,
}
local qux: foo = if false then {} else 10
)");
LUAU_REQUIRE_ERROR_COUNT(1, results);
auto err = get<TypeMismatch>(results.errors[0]);
REQUIRE(err);
CHECK_EQ("number", toString(err->givenType));
CHECK_EQ("foo", toString(err->wantedType));
}
TEST_CASE_FIXTURE(Fixture, "if_then_else_two_errors")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauInferActualIfElseExprType, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck2, true},
};
CheckResult results = check(R"(
type foo = {
bar: () -> string,
}
local qux: foo = if false then {} else 10
)");
LUAU_REQUIRE_ERROR_COUNT(2, results);
auto err1 = get<MissingProperties>(results.errors[0]);
REQUIRE(err1);
CHECK_EQ("foo", toString(err1->superType));
CHECK_EQ("{ }", toString(err1->subType));
auto err2 = get<TypeMismatch>(results.errors[1]);
REQUIRE(err2);
CHECK_EQ("foo", toString(err2->wantedType));
CHECK_EQ("number", toString(err2->givenType));
}
TEST_CASE_FIXTURE(Fixture, "simplify_constraint_can_force")
{
ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauForceSimplifyConstraint, true},
{FFlag::LuauSimplifyOutOfLine2, true},
// NOTE: Feel free to clip this test when this flag is clipped.
{FFlag::LuauPushFunctionTypesInFunctionStatement, false},
};
CheckResult result = check(R"(
--!strict
local foo = nil
bar(function()
if foo then
foo.baz()
end
end)
foo = {}
foo.a = {
foo.b
}
)");
LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError);
}
TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::DebugLuauMagicTypes, true},
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
};
CheckResult results = check(R"(
local function _f(_x: _luau_force_constraint_solving_incomplete) end
)");
LUAU_REQUIRE_NO_ERRORS(results);
}
TEST_CASE_FIXTURE(Fixture, "non_standalone_constraint_solving_incomplete_is_hidden")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::DebugLuauMagicTypes, true},
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
};
CheckResult results = check(R"(
local function _f(_x: _luau_force_constraint_solving_incomplete) end
local x: number = true
)");
LUAU_REQUIRE_ERROR_COUNT(2, results);
CHECK(get<ConstraintSolvingIncompleteError>(results.errors[0]));
CHECK(get<TypeMismatch>(results.errors[1]));
}
TEST_SUITE_END();

View file

@ -9,6 +9,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauEagerGeneralization4);
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch);
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
TEST_SUITE_BEGIN("TypeInferUnknownNever");
@ -329,22 +330,31 @@ TEST_CASE_FIXTURE(Fixture, "length_of_never")
TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
{FFlag::LuauForceSimplifyConstraint, true},
};
CheckResult result = check(R"(
local function ord(x: nil, y)
return x ~= nil and x > y
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2)
{
// FIXME: CLI-152325
CHECK_EQ("(nil, nil & ~nil) -> boolean", toString(requireType("ord")));
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<ExplicitFunctionAnnotationRecommended>(result.errors[0]));
CHECK_EQ("<a>(nil, a) -> false | le<a, nil & ~nil>", toString(requireType("ord")));
}
else
{
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("ord")));
}
}
TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
{