mirror of
https://github.com/luau-lang/luau.git
synced 2025-08-26 11:27:08 +01:00
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:
parent
6ff0650a8d
commit
0ce993fe6c
30 changed files with 626 additions and 391 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,9 +63,12 @@ struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor
|
|||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -584,13 +582,8 @@ 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});
|
||||
}
|
||||
if (auto nt = get<NegationType>(follow(discriminantTy)))
|
||||
discriminantTy = nt->ty;
|
||||
else
|
||||
discriminantTy = arena->addType(NegationType{discriminantTy});
|
||||
}
|
||||
|
@ -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);
|
||||
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);
|
||||
}
|
||||
if (auto bt = get<BlockedType>(unpackedTy); bt && nullptr == bt->getOwner())
|
||||
emplaceType<BoundType>(asMutable(unpackedTy), 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,7 +3102,10 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifEls
|
|||
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
|
||||
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)
|
||||
|
|
|
@ -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,8 +2286,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
|
|||
continue;
|
||||
|
||||
r = follow(r);
|
||||
if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get<ErrorType>(r))
|
||||
results.insert(r);
|
||||
results.insert(r);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -306,12 +305,9 @@ void Module::clonePublicInterface_DEPRECATED(NotNull<BuiltinTypes> builtinTypes,
|
|||
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
|
||||
|
@ -350,12 +346,9 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
|||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauScopeMethodsAreSolverAgnostic)
|
||||
|
||||
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`.
|
||||
void Scope::inheritAssignments(const ScopePtr& childScope)
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
if (FFlag::LuauScopeMethodsAreSolverAgnostic)
|
||||
{
|
||||
for (const auto& [k, a] : childScope->lvalueTypes)
|
||||
lvalueTypes[k] = a;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
for (const auto& [k, a] : childScope->lvalueTypes)
|
||||
lvalueTypes[k] = a;
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 (module->errors.size() == 1 && get<ConstraintSolvingIncompleteError>(module->errors[0]))
|
||||
module->errors.clear();
|
||||
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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -200,14 +198,11 @@ 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)
|
||||
{
|
||||
// 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 (definition.first->typeParams.empty() && definition.first->typePackParams.empty())
|
||||
check.traverse(follow(definition.first->type));
|
||||
}
|
||||
|
||||
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.second >= curr.second)
|
||||
if (definition.first->typeParams.empty() && definition.first->typePackParams.empty())
|
||||
{
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,4 +380,4 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
|||
return {retTypeId, Reduction::MaybeOk, {}, {}, std::nullopt, ctx->typeFunctionRuntime->messages};
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
|
|
@ -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,25 +1446,16 @@ 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);
|
||||
const TValue* slot = luaH_getnum(reg, ref);
|
||||
api_check(L, slot != luaO_nilobject);
|
||||
|
||||
// similar to how 'luaH_setnum' makes non-nil slot value mutable
|
||||
TValue* mutableSlot = (TValue*)slot;
|
||||
// similar to how 'luaH_setnum' makes non-nil slot value mutable
|
||||
TValue* mutableSlot = (TValue*)slot;
|
||||
|
||||
// NB: no barrier needed because value isn't collectable
|
||||
setnvalue(mutableSlot, g->registryfree);
|
||||
// NB: no barrier needed because value isn't collectable
|
||||
setnvalue(mutableSlot, g->registryfree);
|
||||
|
||||
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;
|
||||
}
|
||||
g->registryfree = ref;
|
||||
}
|
||||
|
||||
void lua_setuserdatatag(lua_State* L, int idx, int tag)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
131
VM/src/ldo.cpp
131
VM/src/ldo.cpp
|
@ -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,80 +260,56 @@ 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;
|
||||
|
||||
if (L->ci != L->base_ci)
|
||||
{
|
||||
// when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls)
|
||||
bool fromyieldableccall = false;
|
||||
Closure* ccl = clvalue(L->ci->func);
|
||||
|
||||
if (L->ci != L->base_ci)
|
||||
if (ccl->isC && ccl->c.cont)
|
||||
{
|
||||
Closure* ccl = clvalue(L->ci->func);
|
||||
|
||||
if (ccl->isC && ccl->c.cont)
|
||||
{
|
||||
fromyieldableccall = true;
|
||||
L->baseCcalls++;
|
||||
}
|
||||
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)
|
||||
{ // 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;
|
||||
// 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)
|
||||
L->top = restorestack(L, old_func) + nresults;
|
||||
}
|
||||
|
||||
if (nresults != LUA_MULTRET && !yielded)
|
||||
L->top = restorestack(L, funcoffset) + 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;
|
||||
}
|
||||
// continuation can break or yield again
|
||||
if (L->status == LUA_BREAK || L->status == LUA_YIELD)
|
||||
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,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.
|
||||
|
||||
// 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;
|
||||
L->baseCcalls = oldbaseCcalls;
|
||||
|
||||
// an error occurred, check if we have a protected error callback
|
||||
if (yieldable && L->global->cb.debugprotectederror)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
shrinkstackprotected(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);
|
||||
}
|
||||
tableresizeprotected(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
|
||||
}
|
||||
stringresizeprotected(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
|
||||
}
|
||||
stringresizeprotected(L, hashsize); // table is too big
|
||||
}
|
||||
|
||||
static bool deletegco(void* context, lua_Page* page, GCObject* gco)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
if (p->source)
|
||||
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
|
||||
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);
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "unnamed", 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);
|
||||
}
|
||||
if (p->source)
|
||||
snprintf(buf, sizeof(buf), "thread at %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
|
||||
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);
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "thread at %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
|
||||
|
||||
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]");
|
||||
}
|
||||
|
||||
if (FFlag::LuauHeapNameDetails)
|
||||
{
|
||||
char buf[LUA_IDSIZE];
|
||||
char buf[LUA_IDSIZE];
|
||||
|
||||
if (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);
|
||||
}
|
||||
if (p->source)
|
||||
snprintf(buf, sizeof(buf), "proto %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
|
||||
else
|
||||
{
|
||||
enumnode(ctx, obj2gco(p), size, p->source ? getstr(p->source) : NULL);
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
|
||||
|
||||
enumnode(ctx, obj2gco(p), size, buf);
|
||||
|
||||
if (p->sizek)
|
||||
enumedges(ctx, obj2gco(p), p->k, p->sizek, "constants");
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -9,6 +9,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4);
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch);
|
||||
LUAU_FASTFLAG(LuauForceSimplifyConstraint)
|
||||
|
||||
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")
|
||||
{
|
||||
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")
|
||||
|
|
Loading…
Add table
Reference in a new issue