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 ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint); 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); bool tryDispatch(const PushFunctionTypeConstraint& c, NotNull<const Constraint> constraint);

View file

@ -347,4 +347,18 @@ bool isApproximatelyTruthyType(TypeId ty);
// Unwraps any grouping expressions iteratively. // Unwraps any grouping expressions iteratively.
AstExpr* unwrapGroup(AstExpr* expr); 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 } // namespace Luau

View file

@ -11,6 +11,7 @@
LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSolverAgnosticVisitType)
namespace Luau namespace Luau
{ {
@ -231,7 +232,20 @@ struct GenericTypeVisitor
} }
else if (auto ftv = get<FreeType>(ty)) 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)) if (visit(ty, *ftv))
{ {
@ -281,7 +295,7 @@ struct GenericTypeVisitor
{ {
for (auto& [_name, prop] : ttv->props) for (auto& [_name, prop] : ttv->props)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType)
{ {
if (auto ty = prop.readTy) if (auto ty = prop.readTy)
traverse(*ty); traverse(*ty);
@ -319,7 +333,7 @@ struct GenericTypeVisitor
{ {
for (const auto& [name, prop] : etv->props) for (const auto& [name, prop] : etv->props)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType)
{ {
if (auto ty = prop.readTy) if (auto ty = prop.readTy)
traverse(*ty); traverse(*ty);

View file

@ -21,7 +21,6 @@ LUAU_FASTFLAG(LuauNotAllBinaryTypeFunsHaveDefaults)
LUAU_FASTFLAG(LuauEmptyStringInKeyOf) LUAU_FASTFLAG(LuauEmptyStringInKeyOf)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
LUAU_FASTFLAG(LuauOccursCheckForRefinement) 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 // 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/Constraint.h"
#include "Luau/TypeFunction.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
namespace Luau namespace Luau
{ {
@ -61,9 +63,12 @@ struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId, const TypeFunctionInstanceType&) override bool visit(TypeId, const TypeFunctionInstanceType& tfit) override
{ {
return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; if (FFlag::LuauForceSimplifyConstraint)
return tfit.function->canReduceGenerics;
else
return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions;
} }
}; };
@ -112,9 +117,12 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId, const TypeFunctionInstanceType&) override bool visit(TypeId, const TypeFunctionInstanceType& tfit) override
{ {
return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; 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(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2) LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases)
LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment) LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauFollowTypeAlias)
LUAU_FASTFLAGVARIABLE(LuauFollowExistingTypeFunction)
LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType) LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType)
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements)
LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType)
LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex)
namespace Luau namespace Luau
{ {
@ -584,13 +582,8 @@ void ConstraintGenerator::computeRefinement(
// if we have a negative sense, then we need to negate the discriminant // if we have a negative sense, then we need to negate the discriminant
if (!sense) if (!sense)
{ {
if (FFlag::LuauAvoidDoubleNegation) if (auto nt = get<NegationType>(follow(discriminantTy)))
{ discriminantTy = nt->ty;
if (auto nt = get<NegationType>(follow(discriminantTy)))
discriminantTy = nt->ty;
else
discriminantTy = arena->addType(NegationType{discriminantTy});
}
else else
discriminantTy = arena->addType(NegationType{discriminantTy}); discriminantTy = arena->addType(NegationType{discriminantTy});
} }
@ -958,8 +951,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
auto addToEnvironment = auto addToEnvironment =
[this, &globalNameCollector](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeFun tf, size_t level) [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); if (auto ty = get<TypeFunctionInstanceType>(follow(tf.type)); ty && ty->userFuncData.definition)
ty && ty->userFuncData.definition)
{ {
if (userFuncData.environmentFunction.find(name)) if (userFuncData.environmentFunction.find(name))
return; 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}; scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location};
} }
} }
else if (FFlag::LuauUserTypeFunctionAliases && else if (!get<TypeFunctionInstanceType>(follow(tf.type)))
!get<TypeFunctionInstanceType>(FFlag::LuauFollowTypeAlias ? follow(tf.type) : tf.type))
{ {
if (userFuncData.environmentAlias.find(name)) if (userFuncData.environmentAlias.find(name))
return; 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 // 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) for (auto& [name, tf] : curr->privateTypeBindings)
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf, level); addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf, level);
@ -1931,18 +1922,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
if (!existingFunctionTy) if (!existingFunctionTy)
ice->ice("checkAliases did not populate type function name", function->nameLocation); ice->ice("checkAliases did not populate type function name", function->nameLocation);
if (FFlag::LuauFollowExistingTypeFunction) TypeId unpackedTy = follow(*existingFunctionTy);
{
TypeId unpackedTy = follow(*existingFunctionTy);
if (auto bt = get<BlockedType>(unpackedTy); bt && nullptr == bt->getOwner()) if (auto bt = get<BlockedType>(unpackedTy); bt && nullptr == bt->getOwner())
emplaceType<BoundType>(asMutable(unpackedTy), generalizedTy); emplaceType<BoundType>(asMutable(unpackedTy), generalizedTy);
}
else
{
if (auto bt = get<BlockedType>(*existingFunctionTy); bt && nullptr == bt->getOwner())
emplaceType<BoundType>(asMutable(*existingFunctionTy), generalizedTy);
}
return ControlFlow::None; return ControlFlow::None;
} }
@ -2807,7 +2790,7 @@ Inference ConstraintGenerator::checkIndexName(
if (key) 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)}; return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
@ -3119,7 +3102,10 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifEls
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement)); applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty; TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty;
return Inference{expectedType ? *expectedType : makeUnion(scope, ifElse->location, thenType, elseType)}; if (FFlag::LuauInferActualIfElseExprType)
return Inference{makeUnion(scope, ifElse->location, thenType, elseType)};
else
return Inference{expectedType ? *expectedType : makeUnion(scope, ifElse->location, thenType, elseType)};
} }
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert) Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)

View file

@ -35,7 +35,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint) LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints) LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint)
namespace Luau namespace Luau
{ {
@ -905,7 +905,7 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
else if (auto eqc = get<EqualityConstraint>(*constraint)) else if (auto eqc = get<EqualityConstraint>(*constraint))
success = tryDispatch(*eqc, constraint); success = tryDispatch(*eqc, constraint);
else if (auto sc = get<SimplifyConstraint>(*constraint)) else if (auto sc = get<SimplifyConstraint>(*constraint))
success = tryDispatch(*sc, constraint); success = tryDispatch(*sc, constraint, force);
else if (auto pftc = get<PushFunctionTypeConstraint>(*constraint)) else if (auto pftc = get<PushFunctionTypeConstraint>(*constraint))
success = tryDispatch(*pftc, constraint); success = tryDispatch(*pftc, constraint);
else else
@ -2286,8 +2286,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
continue; continue;
r = follow(r); r = follow(r);
if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get<ErrorType>(r)) results.insert(r);
results.insert(r);
} }
if (0 == results.size()) if (0 == results.size())
@ -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); TypeId target = follow(c.ty);
@ -2927,7 +2926,14 @@ bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull<const Co
FindAllUnionMembers finder; FindAllUnionMembers finder;
finder.traverse(target); 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) for (TypeId ty : finder.blockedTys)
block(ty, constraint); block(ty, constraint);
@ -3874,6 +3880,19 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
if (!isReferenceCountedType(target)) if (!isReferenceCountedType(target))
return; 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); auto sourceRefs = unresolvedConstraints.find(source);
if (sourceRefs) if (sourceRefs)
{ {

View file

@ -44,10 +44,11 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypeVisitor) LUAU_FASTFLAGVARIABLE(LuauExpectedTypeVisitor)
LUAU_FASTFLAGVARIABLE(LuauTrackTypeAllocations) LUAU_FASTFLAGVARIABLE(LuauTrackTypeAllocations)
LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete)
namespace Luau namespace Luau
{ {
@ -1576,7 +1577,7 @@ ModulePtr check(
for (auto& [name, tf] : result->exportedTypeBindings) for (auto& [name, tf] : result->exportedTypeBindings)
tf.type = builtinTypes->errorType; tf.type = builtinTypes->errorType;
} }
else if (FFlag::LuauNewSolverTypecheckCatchTimeouts) else
{ {
try try
{ {
@ -1622,40 +1623,15 @@ ModulePtr check(
result->cancelled = true; 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) if (result->errors.size() == 1 && get<ConstraintSolvingIncompleteError>(result->errors[0]) &&
{ !FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete)
case Mode::Nonstrict: result->errors.clear();
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 (FFlag::LuauExpectedTypeVisitor) if (FFlag::LuauExpectedTypeVisitor)

View file

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

View file

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

View file

@ -4,6 +4,8 @@
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauScopeMethodsAreSolverAgnostic)
namespace Luau namespace Luau
{ {
@ -218,17 +220,25 @@ 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`. // Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
void Scope::inheritAssignments(const ScopePtr& childScope) void Scope::inheritAssignments(const ScopePtr& childScope)
{ {
if (!FFlag::LuauSolverV2) if (FFlag::LuauScopeMethodsAreSolverAgnostic)
return; {
for (const auto& [k, a] : childScope->lvalueTypes)
lvalueTypes[k] = a;
}
else
{
if (!FFlag::LuauSolverV2)
return;
for (const auto& [k, a] : childScope->lvalueTypes) for (const auto& [k, a] : childScope->lvalueTypes)
lvalueTypes[k] = a; lvalueTypes[k] = a;
}
} }
// Updates the `this` scope with the refinements from the `childScope` excluding ones that doesn't exist in `this`. // Updates the `this` scope with the refinements from the `childScope` excluding ones that doesn't exist in `this`.
void Scope::inheritRefinements(const ScopePtr& childScope) void Scope::inheritRefinements(const ScopePtr& childScope)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauScopeMethodsAreSolverAgnostic)
{ {
for (const auto& [k, a] : childScope->rvalueRefinements) for (const auto& [k, a] : childScope->rvalueRefinements)
{ {

View file

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

View file

@ -32,13 +32,14 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks) LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
namespace Luau namespace Luau
{ {
@ -48,7 +49,6 @@ namespace Luau
using PrintLineProc = void (*)(const std::string&); using PrintLineProc = void (*)(const std::string&);
extern PrintLineProc luauPrintLine; extern PrintLineProc luauPrintLine;
/* Push a scope onto the end of a stack for the lifetime of the StackPusher instance. /* 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 * TypeChecker2 uses this to maintain knowledge about which scope encloses every
* given AstNode. * given AstNode.
@ -289,11 +289,11 @@ void check(
typeChecker.visit(sourceModule.root); typeChecker.visit(sourceModule.root);
// if the only error we're producing is one about constraint solving being incomplete, we can silence it. if (!FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
// 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 (module->errors.size() == 1 && get<ConstraintSolvingIncompleteError>(module->errors[0]))
if (module->errors.size() == 1 && get<ConstraintSolvingIncompleteError>(module->errors[0])) module->errors.clear();
module->errors.clear(); }
unfreeze(module->interfaceTypes); unfreeze(module->interfaceTypes);
copyErrors(module->errors, module->interfaceTypes, builtinTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes);
@ -539,7 +539,7 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation)
{ {
if (FFlag::DebugLuauMagicTypes) 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) if (auto ann = ref->parameters.data[0].type)
{ {
@ -550,6 +550,11 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation)
return follow(argTy); 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); 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, // 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 // this means that (probably) this was a duplicate type alias or a
// type alias with an illegal name (like `typeof`). // type alias with an illegal name (like `typeof`).
if (FFlag::LuauSkipMalformedTypeAliases && !module->astScopes.contains(stat)) if (!module->astScopes.contains(stat))
return; return;
visitGenerics(stat->generics, stat->genericPacks); 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 // No further validation is necessary in this case. The main logic for
// _luau_print is contained in lookupAnnotation. // _luau_print is contained in lookupAnnotation.
if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print") if (FFlag::DebugLuauMagicTypes && (ty->name == kLuauPrint || ty->name == kLuauForceConstraintSolvingIncomplete))
return; return;
for (const AstTypeOrPack& param : ty->parameters) for (const AstTypeOrPack& param : ty->parameters)
@ -3051,6 +3056,30 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT
auto exprType = follow(lookupType(expr)); auto exprType = follow(lookupType(expr));
expectedType = follow(expectedType); 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 exprTable = expr->as<AstExprTable>();
auto exprTableType = get<TableType>(exprType); auto exprTableType = get<TableType>(exprType);
auto expectedTableType = get<TableType>(expectedType); auto expectedTableType = get<TableType>(expectedType);

View file

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

View file

@ -12,8 +12,6 @@
#include "lua.h" #include "lua.h"
#include "lualib.h" #include "lualib.h"
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
namespace Luau namespace Luau
{ {
@ -200,14 +198,11 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
for (auto typeParam : typeParams) for (auto typeParam : typeParams)
check.traverse(follow(typeParam)); 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)
{ {
// Check that our environment doesn't depend on any type aliases that are blocked if (definition.first->typeParams.empty() && definition.first->typePackParams.empty())
for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) check.traverse(follow(definition.first->type));
{
if (definition.first->typeParams.empty() && definition.first->typePackParams.empty())
check.traverse(follow(definition.first->type));
}
} }
if (!check.blockingTypes.empty()) if (!check.blockingTypes.empty())
@ -281,36 +276,33 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
} }
} }
if (FFlag::LuauUserTypeFunctionAliases) for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias)
{ {
for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) // Filter visibility based on original scope depth
if (definition.second >= curr.second)
{ {
// Filter visibility based on original scope depth if (definition.first->typeParams.empty() && definition.first->typePackParams.empty())
if (definition.second >= curr.second)
{ {
if (definition.first->typeParams.empty() && definition.first->typePackParams.empty()) TypeId ty = follow(definition.first->type);
// This is checked at the top of the function, and should still be true.
LUAU_ASSERT(!isPending(ty, ctx->solver));
TypeFunctionTypeId serializedTy = serialize(ty, runtimeBuilder.get());
// Only register aliases that are representable in type environment
if (runtimeBuilder->errors.empty())
{ {
TypeId ty = follow(definition.first->type); allocTypeUserData(L, serializedTy->type);
// This is checked at the top of the function, and should still be true.
LUAU_ASSERT(!isPending(ty, ctx->solver));
TypeFunctionTypeId serializedTy = serialize(ty, runtimeBuilder.get());
// Only register aliases that are representable in type environment
if (runtimeBuilder->errors.empty())
{
allocTypeUserData(L, serializedTy->type);
lua_setfield(L, -2, name.c_str());
}
}
else
{
lua_pushlightuserdata(L, definition.first);
lua_pushcclosure(L, evaluateTypeAliasCall, name.c_str(), 1);
lua_setfield(L, -2, name.c_str()); lua_setfield(L, -2, name.c_str());
} }
} }
else
{
lua_pushlightuserdata(L, definition.first);
lua_pushcclosure(L, evaluateTypeAliasCall, name.c_str(), 1);
lua_setfield(L, -2, name.c_str());
}
} }
} }
@ -388,4 +380,4 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
return {retTypeId, Reduction::MaybeOk, {}, {}, std::nullopt, ctx->typeFunctionRuntime->messages}; return {retTypeId, Reduction::MaybeOk, {}, {}, std::nullopt, ctx->typeFunctionRuntime->messages};
} }
} } // namespace Luau

View file

@ -15,8 +15,6 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauUnrefExisting, false)
/* /*
* This file contains most implementations of core Lua APIs from lua.h. * This file contains most implementations of core Lua APIs from lua.h.
* *
@ -1448,25 +1446,16 @@ void lua_unref(lua_State* L, int ref)
global_State* g = L->global; global_State* g = L->global;
LuaTable* reg = hvalue(registry(L)); LuaTable* reg = hvalue(registry(L));
if (DFFlag::LuauUnrefExisting) const TValue* slot = luaH_getnum(reg, ref);
{ api_check(L, slot != luaO_nilobject);
const TValue* slot = luaH_getnum(reg, ref);
api_check(L, slot != luaO_nilobject);
// similar to how 'luaH_setnum' makes non-nil slot value mutable // similar to how 'luaH_setnum' makes non-nil slot value mutable
TValue* mutableSlot = (TValue*)slot; TValue* mutableSlot = (TValue*)slot;
// NB: no barrier needed because value isn't collectable // NB: no barrier needed because value isn't collectable
setnvalue(mutableSlot, g->registryfree); setnvalue(mutableSlot, g->registryfree);
g->registryfree = 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) void lua_setuserdatatag(lua_State* L, int idx, int tag)

View file

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

View file

@ -11,8 +11,6 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
LUAU_FASTFLAG(LuauYieldableContinuations)
static void writestring(const char* s, size_t l) static void writestring(const char* s, size_t l)
{ {
fwrite(s, 1, l, stdout); 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 // any errors from this point on are handled by continuation
L->ci->flags |= LUA_CALLINFO_HANDLE; 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); 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 // necessary to accomodate functions that return lots of values
expandstacklimit(L, L->top); expandstacklimit(L, L->top);
@ -358,19 +345,8 @@ static int luaB_xpcally(lua_State* L)
StkId errf = L->base; StkId errf = L->base;
StkId func = L->base + 1; 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)); 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 // necessary to accommodate functions that return lots of values
expandstacklimit(L, L->top); expandstacklimit(L, L->top);

View file

@ -17,8 +17,6 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauYieldableContinuations)
// keep max stack allocation request under 1GB // keep max stack allocation request under 1GB
#define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024)
@ -262,80 +260,56 @@ void luaD_call(lua_State* L, StkId func, int nresults)
if (++L->nCcalls >= LUAI_MAXCCALLS) if (++L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L); luaD_checkCstack(L);
if (FFlag::LuauYieldableContinuations) // when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls)
bool fromyieldableccall = false;
if (L->ci != L->base_ci)
{ {
// when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls) Closure* ccl = clvalue(L->ci->func);
bool fromyieldableccall = false;
if (L->ci != L->base_ci) if (ccl->isC && ccl->c.cont)
{ {
Closure* ccl = clvalue(L->ci->func); fromyieldableccall = true;
L->baseCcalls++;
if (ccl->isC && ccl->c.cont)
{
fromyieldableccall = true;
L->baseCcalls++;
}
} }
ptrdiff_t funcoffset = savestack(L, func);
ptrdiff_t cioffset = saveci(L, L->ci);
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;
}
bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK;
if (fromyieldableccall)
{
// restore original yieldable invariant
// in case of an error, this would either be restored by luaD_pcall or the thread would no longer be resumable
L->baseCcalls--;
// on yield, we have to set the CallInfo top of the C function including slots for expected results, to restore later
if (yielded)
{
CallInfo* callerci = restoreci(L, cioffset);
callerci->top = restorestack(L, funcoffset) + (nresults != LUA_MULTRET ? nresults : 0);
}
}
if (nresults != LUA_MULTRET && !yielded)
L->top = restorestack(L, funcoffset) + nresults;
} }
else
ptrdiff_t funcoffset = savestack(L, func);
ptrdiff_t cioffset = saveci(L, L->ci);
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;
}
bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK;
if (fromyieldableccall)
{ {
ptrdiff_t old_func = savestack(L, func); // restore original yieldable invariant
// in case of an error, this would either be restored by luaD_pcall or the thread would no longer be resumable
L->baseCcalls--;
if (luau_precall(L, func, nresults) == PCRLUA) // on yield, we have to set the CallInfo top of the C function including slots for expected results, to restore later
{ // is a Lua function? if (yielded)
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame {
CallInfo* callerci = restoreci(L, cioffset);
bool oldactive = L->isactive; callerci->top = restorestack(L, funcoffset) + (nresults != LUA_MULTRET ? nresults : 0);
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;
} }
if (nresults != LUA_MULTRET && !yielded)
L->top = restorestack(L, funcoffset) + nresults;
L->nCcalls--; L->nCcalls--;
luaC_checkGC(L); 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 // C continuation; we expect this to be followed by Lua continuations
int n = cl->c.cont(L, 0); 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)
// continuation can break or yield again break;
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); luau_poscall(L, L->top - n);
} }
@ -436,7 +401,7 @@ static void resume(lua_State* L, void* ud)
// finish interrupted execution of `OP_CALL' // finish interrupted execution of `OP_CALL'
luau_poscall(L, firstArg); luau_poscall(L, firstArg);
} }
else if (FFlag::LuauYieldableContinuations) else
{ {
// restore arguments we have protected for C continuation // restore arguments we have protected for C continuation
L->base = L->ci->base; 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) int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
{ {
unsigned short oldnCcalls = L->nCcalls; 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); ptrdiff_t old_ci = saveci(L, L->ci);
bool oldactive = L->isactive; bool oldactive = L->isactive;
int status = luaD_rawrunprotected(L, func, u); int status = luaD_rawrunprotected(L, func, u);
@ -681,11 +646,9 @@ 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. 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; L->nCcalls = oldnCcalls;
L->baseCcalls = oldbaseCcalls;
if (FFlag::LuauYieldableContinuations)
L->baseCcalls = oldbaseCcalls;
// an error occurred, check if we have a protected error callback // an error occurred, check if we have a protected error callback
if (yieldable && L->global->cb.debugprotectederror) if (yieldable && L->global->cb.debugprotectederror)

View file

@ -14,8 +14,6 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauGcAgainstOom, false)
/* /*
* Luau uses an incremental non-generational non-moving mark&sweep garbage collector. * 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 // 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 (g->gcstate == GCSpropagate)
{ shrinkstackprotected(th);
if (DFFlag::LuauGcAgainstOom)
shrinkstackprotected(th);
else
shrinkstack(th);
}
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; 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 // shrink at 37.5% occupancy
if (activevalues < sizenode(h) * 3 / 8) if (activevalues < sizenode(h) * 3 / 8)
{ tableresizeprotected(L, h, activevalues);
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; global_State* g = L->global;
// check size of string hash // check size of string hash
if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2) if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2)
{ stringresizeprotected(L, g->strt.size / 2); // table is too big
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) 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) while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2)
hashsize /= 2; hashsize /= 2;
if (hashsize != g->strt.size) if (hashsize != g->strt.size)
{ stringresizeprotected(L, hashsize); // table is too big
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) static bool deletegco(void* context, lua_Page* page, GCObject* gco)

View file

@ -14,8 +14,6 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauHeapNameDetails)
static void validateobjref(global_State* g, GCObject* f, GCObject* t) static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{ {
LUAU_ASSERT(!isdead(g, t)); LUAU_ASSERT(!isdead(g, t));
@ -728,20 +726,10 @@ static void enumclosure(EnumContext* ctx, Closure* cl)
char buf[LUA_IDSIZE]; 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));
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 else
{ snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
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); enumnode(ctx, obj2gco(cl), sizeLclosure(cl->nupvalues), buf);
} }
@ -809,21 +797,10 @@ static void enumthread(EnumContext* ctx, lua_State* th)
char buf[LUA_IDSIZE]; 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));
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 else
{ snprintf(buf, sizeof(buf), "thread at %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
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); enumnode(ctx, obj2gco(th), size, buf);
} }
@ -856,21 +833,14 @@ static void enumproto(EnumContext* ctx, Proto* p)
ctx->edge(ctx->context, enumtopointer(obj2gco(p)), p->execdata, "[native]"); ctx->edge(ctx->context, enumtopointer(obj2gco(p)), p->execdata, "[native]");
} }
if (FFlag::LuauHeapNameDetails) char buf[LUA_IDSIZE];
{
char buf[LUA_IDSIZE];
if (p->source) if (p->source)
snprintf(buf, sizeof(buf), "proto %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source)); snprintf(buf, sizeof(buf), "proto %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
enumnode(ctx, obj2gco(p), size, buf);
}
else else
{ snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
enumnode(ctx, obj2gco(p), size, p->source ? getstr(p->source) : NULL);
} enumnode(ctx, obj2gco(p), size, buf);
if (p->sizek) if (p->sizek)
enumedges(ctx, obj2gco(p), p->k, p->sizek, "constants"); 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_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauYieldableContinuations)
LUAU_FASTFLAG(LuauHeapNameDetails)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_DYNAMIC_FASTFLAG(LuauGcAgainstOom)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -785,8 +782,6 @@ static void* blockableRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
TEST_CASE("GC") TEST_CASE("GC")
{ {
ScopedFastFlag luauGcAgainstOom{DFFlag::LuauGcAgainstOom, true};
runConformance( runConformance(
"gc.luau", "gc.luau",
[](lua_State* L) [](lua_State* L)
@ -1062,8 +1057,6 @@ int passthroughCallWithStateContinuation(lua_State* L, int status)
TEST_CASE("CYield") TEST_CASE("CYield")
{ {
ScopedFastFlag luauYieldableContinuations{FFlag::LuauYieldableContinuations, true};
runConformance( runConformance(
"cyield.luau", "cyield.luau",
[](lua_State* L) [](lua_State* L)
@ -2322,8 +2315,6 @@ TEST_CASE("StringConversion")
TEST_CASE("GCDump") TEST_CASE("GCDump")
{ {
ScopedFastFlag luauHeapNameDetails{FFlag::LuauHeapNameDetails, true};
// internal function, declared in lgc.h - not exposed via lua.h // 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_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
extern void luaC_enumheap( extern void luaC_enumheap(

View file

@ -12,6 +12,10 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
TEST_SUITE_BEGIN("NonstrictModeTests"); TEST_SUITE_BEGIN("NonstrictModeTests");
TEST_CASE_FIXTURE(Fixture, "infer_nullary_function") TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
@ -315,4 +319,39 @@ TEST_CASE_FIXTURE(Fixture, "returning_too_many_values")
LUAU_REQUIRE_NO_ERRORS(result); 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(); TEST_SUITE_END();

View file

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

View file

@ -11,7 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAG(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
TEST_SUITE_BEGIN("TypeAliases"); 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") TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_bad_name")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauSkipMalformedTypeAliases, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type typeof = typeof(nil :: any) 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)) // type Foo = typeof(setmetatable({} :: SomeType, {} :: SomeMetatableType))
// //
ScopedFastFlag _{FFlag::LuauSkipMalformedTypeAliases, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type A = typeof(nil :: any) type A = typeof(nil :: any)
type A = typeof(nil :: any) type A = typeof(nil :: any)

View file

@ -16,6 +16,7 @@ using std::nullopt;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic)
TEST_SUITE_BEGIN("TypeInferExternTypes"); TEST_SUITE_BEGIN("TypeInferExternTypes");
@ -894,4 +895,28 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "cyclic_tables_are_assumed_to_be_compatible
LUAU_REQUIRE_NO_ERRORS(result); 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(); TEST_SUITE_END();

View file

@ -17,9 +17,10 @@ LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes) LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauRefineNoRefineAlways) LUAU_FASTFLAG(LuauRefineNoRefineAlways)
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
using namespace Luau; 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") TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
{FFlag::LuauAvoidDoubleNegation, true},
{FFlag::LuauRefineTablesWithReadType, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type T = {tag: "missing", x: nil} | {tag: "exists", x: string} 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") 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"( CheckResult result = check(R"(
local function isIndexKey(k, contiguousLength) local function isIndexKey(k, contiguousLength)
return type(k) == "number" return type(k) == "number"
@ -2257,7 +2262,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction"
end 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") 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); 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(); TEST_SUITE_END();

View file

@ -37,6 +37,9 @@ LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr) LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
TEST_SUITE_BEGIN("TableTests"); 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(); TEST_SUITE_END();

View file

@ -25,8 +25,6 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
@ -35,6 +33,12 @@ LUAU_FASTFLAG(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAG(LuauOccursCheckForRefinement) LUAU_FASTFLAG(LuauOccursCheckForRefinement)
LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties) LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
using namespace Luau; 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)) TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::timeout(0.5))
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag _{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauAvoidDoubleNegation, true},
};
// We don't care about errors, only that we don't OOM during typechecking. // We don't care about errors, only that we don't OOM during typechecking.
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(
local _ = _ local _ = _
@ -2153,8 +2155,6 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union")
{ {
ScopedFastFlag _{FFlag::LuauInsertErrorTypesIntoIndexerResult, true};
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(
local _ = nil local _ = nil
repeat 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(); TEST_SUITE_END();

View file

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