Sync to upstream/release/672

This commit is contained in:
Ariel Weiss 2025-05-02 12:25:17 -07:00
parent 2b7a89db49
commit 3adf25898a
58 changed files with 1389 additions and 445 deletions

View file

@ -74,10 +74,6 @@ struct FreeType
// This one got promoted to explicit
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
// Old constructors
explicit FreeType(TypeLevel level);
explicit FreeType(Scope* scope);
FreeType(Scope* scope, TypeLevel level);
int index;
TypeLevel level;

View file

@ -37,10 +37,6 @@ struct TypeArena
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
TypeId freshType_DEPRECATED(TypeLevel level);
TypeId freshType_DEPRECATED(Scope* scope);
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
TypePackId addTypePack(std::initializer_list<TypeId> types);

View file

@ -185,6 +185,8 @@ TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp);
TypePackId getTail(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);

View file

@ -475,26 +475,26 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}");
}
void write(const AstGenericType& genericType)
void write(class AstGenericType* genericType)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericType");
write("name", genericType.name);
if (genericType.defaultValue)
write("luauType", genericType.defaultValue);
write("name", genericType->name);
if (genericType->defaultValue)
write("luauType", genericType->defaultValue);
popComma(c);
writeRaw("}");
}
void write(const AstGenericTypePack& genericTypePack)
void write(class AstGenericTypePack* genericTypePack)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericTypePack");
write("name", genericTypePack.name);
if (genericTypePack.defaultValue)
write("luauType", genericTypePack.defaultValue);
write("name", genericTypePack->name);
if (genericTypePack->defaultValue)
write("luauType", genericTypePack->defaultValue);
popComma(c);
writeRaw("}");
}

View file

@ -32,7 +32,6 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
@ -1550,7 +1549,6 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
if (FFlag::LuauFollowTableFreeze)
inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType))
{

View file

@ -51,16 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
bool visit(TypeId, const TypeFunctionInstanceType&) override
{
// We do not consider reference counted types that are inside a type
// function to be part of the reachable reference counted types.
// Otherwise, code can be constructed in just the right way such
// that two type functions both claim to mutate a free type, which
// prevents either type function from trying to generalize it, so
// we potentially get stuck.
//
// The default behavior here is `true` for "visit the child types"
// of this type, hence:
return false;
return FFlag::DebugLuauGreedyGeneralization;
}
};
@ -130,8 +121,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
}
else if (auto hic = get<HasIndexerConstraint>(*this))
{
if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
// `HasIndexerConstraint` should not mutate `indexType`.
}
else if (auto apc = get<AssignPropConstraint>(*this))
{
@ -150,6 +143,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`.
}
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
{
rci.traverse(rpc->ty);
}
else if (auto rpc = get<ReducePackConstraint>(*this))
{
rci.traverse(rpc->tp);

View file

@ -39,7 +39,6 @@ LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
@ -52,6 +51,7 @@ LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
namespace Luau
{
@ -1409,7 +1409,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location};
bool sigFullyDefined = !hasFreeType(sig.signature);
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
if (sigFullyDefined)
emplaceType<BoundType>(asMutable(functionType), sig.signature);
@ -1471,7 +1471,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
bool sigFullyDefined = !hasFreeType(sig.signature);
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
DefId def = dfg->getDef(function->name);
@ -2388,11 +2388,14 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
argEndCheckpoint,
this,
[checkConstraint, callConstraint](const ConstraintPtr& constraint)
{
if (!(FFlag::DebugLuauGreedyGeneralization && get<PrimitiveTypeConstraint>(*constraint)))
{
constraint->dependencies.emplace_back(checkConstraint);
callConstraint->dependencies.emplace_back(constraint.get());
}
}
);
return InferencePack{rets, {refinementArena.variadic(returnRefinements)}};
@ -2496,8 +2499,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
}
else
{
FreeType ft =
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}});
ft.upperBound = builtinTypes->stringType;
freeTy = arena->addType(ft);
@ -2524,8 +2526,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
}
else
{
FreeType ft =
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
ft.lowerBound = singletonType;
ft.upperBound = builtinTypes->booleanType;
freeTy = arena->addType(ft);
@ -3076,7 +3077,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (ty)
{
TypeIds* localDomain = localTypes.find(*ty);
if (localDomain)
if (localDomain && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && local->upvalue))
localDomain->insert(rhsType);
}
else
@ -3107,6 +3108,8 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (annotatedTy)
addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy});
// This is vestigial.
if (!FFlag::LuauDoNotAddUpvalueTypesToLocalType)
if (TypeIds* localDomain = localTypes.find(*ty))
localDomain->insert(rhsType);
}
@ -3410,13 +3413,22 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
bodyScope->varargPack = std::nullopt;
}
LUAU_ASSERT(nullptr != varargPack);
if (FFlag::DebugLuauGreedyGeneralization)
{
genericTypes = argTypes;
// Some of the types in argTypes will eventually be generics, and some
// will not. The ones that are not generic will be pruned when
// GeneralizationConstraint dispatches.
genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end());
varargPack = follow(varargPack);
returnType = follow(returnType);
if (varargPack == returnType)
genericTypePacks = {varargPack};
else
genericTypePacks = {varargPack, returnType};
}
LUAU_ASSERT(nullptr != varargPack);
// If there is both an annotation and an expected type, the annotation wins.
// Type checking will sort out any discrepancies later.
if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation)

View file

@ -681,7 +681,6 @@ void ConstraintSolver::initFreeTypeTracking()
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
@ -2082,6 +2081,42 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
if (auto lhsFree = getMutable<FreeType>(lhsType))
{
auto lhsFreeUpperBound = follow(lhsFree->upperBound);
if (FFlag::DebugLuauGreedyGeneralization)
{
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
if (!blocked.empty())
{
for (TypeId t : blocked)
block(t, constraint);
return false;
}
else if (maybeTy)
{
bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{*maybeTy, builtinTypes->nilType}}) : *maybeTy);
unify(constraint, rhsType, *maybeTy);
return true;
}
else
{
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(upperTable);
upperTable->props[c.propName] = rhsType;
// Food for thought: Could we block if simplification encounters a blocked type?
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
bind(constraint, c.propType, rhsType);
return true;
}
}
else
{
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
lhsType = lhsFreeUpperBound;
else
@ -2102,6 +2137,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
return true;
}
}
}
// Handle the case that lhsType is a table that already has the property or
// a matching indexer. This also handles unions and intersections.
@ -2873,8 +2909,21 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
{
const TypeId upperBound = follow(ft->upperBound);
if (FFlag::DebugLuauGreedyGeneralization)
{
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
{
TablePropLookupResult res = lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
// If the upper bound is a table that already has the property, we don't need to extend its bounds.
if (res.propType || get<PrimitiveType>(upperBound))
return res;
}
}
else
{
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
}
// TODO: The upper bound could be an intersection that contains suitable tables or extern types.

View file

@ -17,7 +17,8 @@ LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
namespace Luau
{
@ -501,12 +502,26 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
}
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow)
{
// If the control flow from the `if` or `else` block is non-linear,
// then we should assume that the _other_ branch is the one taken.
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
scope->inherit(elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inherit(thenScope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
else
{
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
if (thencf == elsecf)
return thencf;
@ -1164,7 +1179,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
// In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(l->local))
if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue))
{
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
scope->bindings[l->local] = updated;

View file

@ -2,6 +2,7 @@
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau
{
@ -339,11 +340,11 @@ std::string getBuiltinDefinitionSource()
}
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC(
export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
"singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic",
is: (self: type, arg: string) -> boolean,
@ -390,6 +391,10 @@ export type type = {
ispack: (self: type) -> boolean,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
@ -409,12 +414,44 @@ declare types: {
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
optional: @checked (arg: type) -> type,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
std::string getTypeFunctionDefinitionSource()
{
return kBuiltinDefinitionTypesSrc;
std::string result = kBuiltinDefinitionTypeMethodSrc;
if (FFlag::LuauTypeFunOptional)
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
else
result += kBuiltinDefinitionTypesLibSrc;
return result;
}
} // namespace Luau

View file

@ -28,8 +28,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
@ -737,7 +735,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
// requires that we find the local/global `m` and place it in the environment.
// The default behaviour here is to return false, and have individual visitors override
// the specific behaviour they need.
return FFlag::LuauMixedModeDefFinderTraversesTypeOf;
return true;
}
bool visit(AstStatTypeAlias* alias) override
@ -1227,31 +1225,6 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq
return incremental;
}
ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
{
ModulePtr incrementalModule = std::make_shared<Module>();
incrementalModule->name = result->name;
incrementalModule->humanReadableName = "Incremental$" + result->humanReadableName;
incrementalModule->internalTypes.owningModule = incrementalModule.get();
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
incrementalModule->allocator = std::move(alloc);
// Don't need to keep this alive (it's already on the source module)
copyModuleVec(incrementalModule->scopes, result->scopes);
copyModuleMap(incrementalModule->astTypes, result->astTypes);
copyModuleMap(incrementalModule->astTypePacks, result->astTypePacks);
copyModuleMap(incrementalModule->astExpectedTypes, result->astExpectedTypes);
// Don't need to clone astOriginalCallTypes
copyModuleMap(incrementalModule->astOverloadResolvedTypes, result->astOverloadResolvedTypes);
// Don't need to clone astForInNextTypes
copyModuleMap(incrementalModule->astForInNextTypes, result->astForInNextTypes);
// Don't need to clone astResolvedTypes
// Don't need to clone astResolvedTypePacks
// Don't need to clone upperBoundContributors
copyModuleMap(incrementalModule->astScopes, result->astScopes);
// Don't need to clone declared Globals;
return incrementalModule;
}
void mixedModeCompatibility(
const ScopePtr& bottomScopeStale,
const ScopePtr& myFakeScope,
@ -1315,10 +1288,8 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
ModulePtr incrementalModule = nullptr;
if (FFlag::LuauAllFreeTypesHaveScopes)
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
else if (FFlag::LuauCloneIncrementalModule)
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
else
incrementalModule = copyModule(stale, std::move(astAllocator));
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
incrementalModule->checkedInNewSolver = true;
@ -1372,8 +1343,6 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
if (FFlag::LuauCloneIncrementalModule)
{
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
cg.rootScope = freshChildOfNearestScope.get();
@ -1394,22 +1363,7 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
for (auto p : cg.scopes)
incrementalModule->scopes.emplace_back(std::move(p));
}
}
else
{
// Any additions to the scope must occur in a fresh scope
cg.rootScope = stale->getModuleScope().get();
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
// closest Scope -> children = { ...., freshChildOfNearestScope}
// We need to trim nearestChild from the scope hierarchy
closestScope->children.emplace_back(freshChildOfNearestScope.get());
cg.visitFragmentRoot(freshChildOfNearestScope, root);
// Trim nearestChild from the closestScope
Scope* back = closestScope->children.back().get();
LUAU_ASSERT(back == freshChildOfNearestScope.get());
closestScope->children.pop_back();
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
if (FFlag::LuauAllFreeTypesHaveScopes)

View file

@ -1403,7 +1403,10 @@ std::optional<TypeId> generalize(
}
for (TypeId unsealedTableTy : fts.unsealedTables)
{
if (!generalizationTarget || unsealedTableTy == *generalizationTarget)
sealTable(scope, unsealedTableTy);
}
for (const auto& [freePackId, params] : fts.typePacks)
{
@ -1463,29 +1466,93 @@ std::optional<TypeId> generalize(
struct GenericCounter : TypeVisitor
{
struct CounterState
{
size_t count = 0;
Polarity polarity = Polarity::None;
};
NotNull<DenseHashSet<TypeId>> cachedTypes;
DenseHashMap<TypeId, size_t> generics{nullptr};
DenseHashMap<TypePackId, size_t> genericPacks{nullptr};
DenseHashMap<TypeId, CounterState> generics{nullptr};
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
Polarity polarity = Polarity::Positive;
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
: cachedTypes(cachedTypes)
{
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->persistent)
return false;
polarity = invert(polarity);
traverse(ft.argTypes);
polarity = invert(polarity);
traverse(ft.retTypes);
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->persistent)
return false;
const Polarity previous = polarity;
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = previous;
}
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
polarity = previous;
}
return false;
}
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
bool visit(TypeId ty, const GenericType&) override
{
size_t* count = generics.find(ty);
if (count)
++*count;
auto state = generics.find(ty);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
size_t* count = genericPacks.find(tp);
if (count)
++*count;
auto state = genericPacks.find(tp);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
@ -1521,21 +1588,30 @@ void pruneUnnecessaryGenerics(
generic = follow(generic);
auto g = get<GenericType>(generic);
if (g && !g->explicitName)
counter.generics[generic] = 0;
counter.generics[generic] = {};
}
for (TypePackId genericPack : functionTy->genericPacks)
// It is sometimes the case that a pack in the generic list will become a
// pack that (transitively) has a generic tail. If it does, we need to add
// that generic tail to the generic pack list.
for (size_t i = 0; i < functionTy->genericPacks.size(); ++i)
{
genericPack = follow(genericPack);
auto g = get<GenericTypePack>(genericPack);
if (g && !g->explicitName)
counter.genericPacks[genericPack] = 0;
TypePackId genericPack = follow(functionTy->genericPacks[i]);
TypePackId tail = getTail(genericPack);
if (tail != genericPack)
functionTy->genericPacks.push_back(tail);
if (auto g = get<GenericTypePack>(tail); g && !g->explicitName)
counter.genericPacks[genericPack] = {};
}
counter.traverse(ty);
for (const auto& [generic, count] : counter.generics)
for (const auto& [generic, state] : counter.generics)
{
if (count == 1)
if (state.count == 1 && state.polarity != Polarity::Mixed)
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
}
@ -1557,9 +1633,9 @@ void pruneUnnecessaryGenerics(
functionTy->generics.erase(it, functionTy->generics.end());
for (const auto& [genericPack, count] : counter.genericPacks)
for (const auto& [genericPack, state] : counter.genericPacks)
{
if (count == 1)
if (state.count == 1)
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
}

View file

@ -11,7 +11,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau
{
@ -164,7 +163,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
}
else
{
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level});
return arena->freshType(builtinTypes, scope, level);
}
}

View file

@ -22,10 +22,9 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
@ -218,7 +217,7 @@ struct NonStrictTypeChecker
return *fst;
else if (auto ftp = get<FreeTypePack>(pack))
{
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, ftp->scope) : arena->addType(FreeType{ftp->scope});
TypeId result = arena->freshType(builtinTypes, ftp->scope);
TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -341,13 +340,10 @@ struct NonStrictTypeChecker
{
ctx.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes)
{
if (local->annotation)
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
}
}
else
ctx = NonStrictContext::disjunction(builtinTypes, arena, visit(stat), ctx);
}
@ -431,8 +427,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatFor* forStatement)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (forStatement->var->annotation)
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(forStatement->var->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
@ -454,14 +449,11 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatForIn* forInStatement)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
for (auto var : forInStatement->vars)
{
if (var->annotation)
visit(var->annotation);
}
}
if (FFlag::LuauNonStrictVisitorImprovements)
{
@ -511,7 +503,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatTypeAlias* typeAlias)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(typeAlias->generics, typeAlias->genericPacks);
visit(typeAlias->type);
@ -527,7 +519,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareFunction* declFn)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(declFn->generics, declFn->genericPacks);
visit(declFn->params);
@ -539,7 +531,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareGlobal* declGlobal)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(declGlobal->type);
return {};
@ -547,7 +539,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareExternType* declClass)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
if (declClass->indexer)
{
@ -823,22 +815,16 @@ struct NonStrictTypeChecker
}
remainder.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes)
{
if (local->annotation)
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
}
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(exprFn->generics, exprFn->genericPacks);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (exprFn->returnAnnotation)
visit(exprFn->returnAnnotation);
}
else
{
if (exprFn->returnAnnotation_DEPRECATED)
@ -889,7 +875,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(typeAssertion->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
@ -930,7 +916,11 @@ struct NonStrictTypeChecker
void visit(AstType* ty)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes);
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If this node is `nullptr`, early exit.
if (!ty)
return;
if (auto t = ty->as<AstTypeReference>())
return visit(t);
@ -1139,7 +1129,11 @@ struct NonStrictTypeChecker
void visit(AstTypePack* pack)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes);
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If there is no pack node, early exit.
if (!pack)
return;
if (auto p = pack->as<AstTypePackExplicit>())
return visit(p);

View file

@ -6,6 +6,7 @@
#include "Luau/DenseHash.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Set.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypePairHash.h"
#include "Luau/TypeUtils.h"
@ -17,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
namespace Luau
{
@ -316,11 +318,13 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Subset;
else if (get<UnknownType>(right))
if (get<UnknownType>(right))
return Relation::Coincident;
else if (get<ErrorType>(right))
if (get<ErrorType>(right))
return Relation::Disjoint;
else
return Relation::Superset;
}
@ -331,7 +335,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Coincident;
else
return Relation::Superset;
}
@ -364,26 +368,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (isTypeVariable(left) || isTypeVariable(right))
return Relation::Intersects;
if (FFlag::LuauSimplificationTableExternType)
{
// if either type is a type function, we cannot know if they'll be related.
if (get<TypeFunctionInstanceType>(left) || get<TypeFunctionInstanceType>(right))
return Relation::Intersects;
}
if (get<ErrorType>(left))
{
if (get<ErrorType>(right))
return Relation::Coincident;
else if (get<AnyType>(right))
return Relation::Subset;
else
return Relation::Disjoint;
}
if (get<ErrorType>(right))
else if (get<ErrorType>(right))
return flip(relate(right, left, seen));
if (get<NeverType>(left))
{
if (get<NeverType>(right))
return Relation::Coincident;
else
return Relation::Subset;
}
if (get<NeverType>(right))
else if (get<NeverType>(right))
return flip(relate(right, left, seen));
if (auto ut = get<IntersectionType>(left))
@ -447,7 +458,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (lp->type == rp->type)
return Relation::Coincident;
else
return Relation::Disjoint;
}
@ -455,9 +466,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
return Relation::Superset;
else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
return Relation::Superset;
else
return Relation::Disjoint;
}
@ -465,14 +477,14 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<FunctionType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
}
if (lp->type == PrimitiveType::Table)
{
if (get<TableType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
}
@ -487,11 +499,12 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (get<PrimitiveType>(right))
return flip(relate(right, left, seen));
if (auto rs = get<SingletonType>(right))
{
if (ls->variant == rs->variant)
return Relation::Coincident;
else
return Relation::Disjoint;
}
}
@ -502,10 +515,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Function)
return Relation::Subset;
else
return Relation::Disjoint;
}
else
return Relation::Intersects;
}
@ -515,10 +528,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Table)
return Relation::Subset;
else
return Relation::Disjoint;
}
else if (auto rt = get<TableType>(right))
if (auto rt = get<TableType>(right))
{
// TODO PROBABLY indexers and metatables.
if (1 == rt->props.size())
@ -538,14 +552,42 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
*/
if (lt->props.size() > 1 && r == Relation::Superset)
return Relation::Intersects;
else
return r;
}
else if (1 == lt->props.size())
if (1 == lt->props.size())
return flip(relate(right, left, seen));
else
return Relation::Intersects;
}
if (FFlag::LuauSimplificationTableExternType)
{
if (auto re = get<ExternType>(right))
{
Relation overall = Relation::Coincident;
for (auto& [name, prop] : lt->props)
{
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
{
Relation propRel = relate(prop.type(), propInExternType->second.type());
if (propRel == Relation::Disjoint)
return Relation::Disjoint;
if (propRel == Relation::Coincident)
continue;
overall = Relation::Intersects;
}
}
return overall;
}
}
// TODO metatables
return Relation::Disjoint;
@ -557,9 +599,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (isSubclass(ct, rct))
return Relation::Subset;
else if (isSubclass(rct, ct))
if (isSubclass(rct, ct))
return Relation::Superset;
else
return Relation::Disjoint;
}

View file

@ -7,13 +7,11 @@
#include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h"
#include "Luau/ToString.h"
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypePack.h"
#include "Luau/TypePath.h"
@ -33,7 +31,7 @@ struct VarianceFlipper
Subtyping::Variance* variance;
Subtyping::Variance oldValue;
VarianceFlipper(Subtyping::Variance* v)
explicit VarianceFlipper(Subtyping::Variance* v)
: variance(v)
, oldValue(*v)
{

View file

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
@ -706,8 +705,6 @@ struct Printer_DEPRECATED
writer.keyword("do");
for (const auto& s : block->body)
visualize(*s);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatIf>())
@ -2323,8 +2320,6 @@ struct Printer
{
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type)
if (func.generics.size > 0 || func.genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
@ -2395,13 +2390,19 @@ struct Printer
if (cstNode)
advance(cstNode->returnSpecifierPosition);
writer.symbol(":");
writer.space();
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!cstNode)
writer.space();
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
}
else
{
writer.space();
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
}
}
visualizeBlock(*func.body);
advance(func.body->location.end);

View file

@ -506,31 +506,6 @@ FreeType::FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId uppe
{
}
// Old constructors
FreeType::FreeType(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(nullptr)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope)
: index(Unifiable::freshIndex())
, level{}
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope, TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
GenericType::GenericType()
: index(Unifiable::freshIndex())
, name("g" + std::to_string(index))

View file

@ -50,33 +50,6 @@ TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLe
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(TypeLevel level)
{
TypeId allocated = types.allocate(FreeType{level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(Scope* scope)
{
TypeId allocated = types.allocate(FreeType{scope});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
{
TypeId allocated = types.allocate(FreeType{scope, level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::freshTypePack(Scope* scope, Polarity polarity)
{
TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity});

View file

@ -30,7 +30,6 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
@ -2114,10 +2113,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
}
else
{
expectedRets = module->internalTypes.addTypePack(
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
);
expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})});
}
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
@ -2380,8 +2376,7 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
return *fst;
else if (auto ftp = get<FreeTypePack>(pack))
{
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope)
: module->internalTypes.addType(FreeType{ftp->scope});
TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope);
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));

View file

@ -1839,10 +1839,20 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
return {rhsTy, Reduction::MaybeOk, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not
if (FFlag::DebugLuauGreedyGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
else
{
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
@ -1876,10 +1886,20 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (FFlag::DebugLuauGreedyGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
else
{
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
// Algebra Reduction Rules for comparison type functions
// Note that comparing to never tells you nothing about the other operand
@ -2240,8 +2260,12 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(i)));
const bool targetIsPending = FFlag::DebugLuauGreedyGeneralization
? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy)
: isPending(targetTy, ctx->solver);
// check to see if both operand types are resolved enough, and wait to reduce if not
if (isPending(targetTy, ctx->solver))
if (targetIsPending)
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
else
{
@ -2326,8 +2350,32 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (FFlag::DebugLuauGreedyGeneralization)
{
// Simplification considers free and generic types to be
// 'blocking', but that's not suitable for refine<>.
//
// If we are only blocked on those types, we consider
// the simplification a success and reduce.
if (std::all_of(
begin(result.blockedTypes),
end(result.blockedTypes),
[](auto&& v)
{
return is<FreeType, GenericType>(follow(v));
}
))
{
return {result.result, {}};
}
else
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
}
else
{
if (!result.blockedTypes.empty())
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
}
return {result.result, {}};
}
}
@ -3527,7 +3575,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
, ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineTypeFunction}
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::DebugLuauGreedyGeneralization}
, singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction}

View file

@ -15,6 +15,7 @@
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
namespace Luau
{
@ -315,6 +316,38 @@ static int getSingletonValue(lua_State* L)
luaL_error(L, "type.value: can't call `value` method on `%s` type", getTag(L, self).c_str());
}
// Luau: `types.optional(ty: type) -> type`
// Returns the type instance representing an optional version of `ty`.
// If `ty` is a union, this adds `nil` to the components of the union.
// Otherwise, makes a union of the two things.
static int createOptional(lua_State* L)
{
LUAU_ASSERT(FFlag::LuauTypeFunOptional);
int argumentCount = lua_gettop(L);
if (argumentCount != 1)
luaL_error(L, "types.optional: expected 1 argument, but got %d", argumentCount);
TypeFunctionTypeId argument = getTypeUserData(L, 1);
std::vector<TypeFunctionTypeId> components;
if (auto unionTy = get<TypeFunctionUnionType>(argument))
{
components.reserve(unionTy->components.size() + 1);
components.insert(components.begin(), unionTy->components.begin(), unionTy->components.end());
}
else
components.emplace_back(argument);
components.emplace_back(allocateTypeFunctionType(L, TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::NilType)));
allocTypeUserData(L, TypeFunctionUnionType{components});
return 1;
}
// Luau: `types.unionof(...: type) -> type`
// Returns the type instance representing union
static int createUnion(lua_State* L)
@ -1524,6 +1557,7 @@ void registerTypesLibrary(lua_State* L)
{"copy", deepCopy},
{"generic", createGeneric},
{(FFlag::LuauTypeFunOptional) ? "optional" : nullptr, (FFlag::LuauTypeFunOptional) ? createOptional : nullptr},
{nullptr, nullptr}
};

View file

@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
@ -799,8 +798,7 @@ struct Demoter : Substitution
{
auto ftv = get<FreeType>(ty);
LUAU_ASSERT(ftv);
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level))
: addType(FreeType{demotedLevel(ftv->level)});
return arena->freshType(builtins, demotedLevel(ftv->level));
}
TypePackId clean(TypePackId tp) override
@ -5410,8 +5408,7 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
TypeId TypeChecker::freshType(TypeLevel level)
{
return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level)
: currentModule->internalTypes.addType(Type(FreeType(level)));
return currentModule->internalTypes.freshType(builtinTypes, level);
}
TypeId TypeChecker::singletonType(bool value)

View file

@ -208,6 +208,26 @@ TypePackIterator end(TypePackId tp)
return TypePackIterator{};
}
TypePackId getTail(TypePackId tp)
{
DenseHashSet<TypePackId> seen{nullptr};
while (tp)
{
tp = follow(tp);
if (seen.contains(tp))
break;
seen.insert(tp);
if (auto pack = get<TypePack>(tp); pack && pack->tail)
tp = *pack->tail;
else
break;
}
return follow(tp);
}
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
{
TypePackId lhsId = const_cast<TypePackId>(&lhs);

View file

@ -11,7 +11,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
@ -328,7 +327,7 @@ TypePack extendTypePack(
trackInteriorFreeType(ftp->scope, t);
}
else
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
t = arena.freshType(builtinTypes, ftp->scope);
}
newPack.head.push_back(t);

View file

@ -22,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau
{
@ -1559,7 +1558,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (FFlag::LuauSolverV2)
return freshType(NotNull{types}, builtinTypes, scope);
else
return FFlag::LuauFreeTypesMustHaveBounds ? types->freshType(builtinTypes, scope, level) : types->freshType_DEPRECATED(scope, level);
return types->freshType(builtinTypes, scope, level);
};
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});

View file

@ -20,7 +20,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
@ -555,7 +554,7 @@ AstStat* Parser::parseDo()
Location endLocation = lexer.current().location;
body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd)
if (body->hasEnd)
body->location.end = endLocation.end;
if (FFlag::LuauStoreCSTData2 && options.storeCstData)

View file

@ -87,6 +87,11 @@ private:
[[nodiscard]] Error navigateToAlias(const std::string& alias, const std::string& value);
[[nodiscard]] Error navigateToAndPopulateConfig(const std::string& desiredAlias);
[[nodiscard]] Error resetToRequirer();
[[nodiscard]] Error jumpToAlias(const std::string& aliasPath);
[[nodiscard]] Error navigateToParent(std::optional<std::string> previousComponent);
[[nodiscard]] Error navigateToChild(const std::string& component);
NavigationContext& navigationContext;
ErrorHandler& errorHandler;
Luau::Config config;

View file

@ -10,24 +10,11 @@
#include <optional>
#include <utility>
static constexpr char kRequireErrorAmbiguous[] = "require path could not be resolved to a unique file";
static constexpr char kRequireErrorGeneric[] = "error requiring module";
namespace Luau::Require
{
using Error = std::optional<std::string>;
static Error toError(NavigationContext::NavigateResult result)
{
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
if (result == NavigationContext::NavigateResult::Ambiguous)
return kRequireErrorAmbiguous;
else
return kRequireErrorGeneric;
}
static std::string extractAlias(std::string_view path)
{
// To ignore the '@' alias prefix when processing the alias
@ -53,7 +40,7 @@ Navigator::Status Navigator::navigate(std::string path)
{
std::replace(path.begin(), path.end(), '\\', '/');
if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier())))
if (Error error = resetToRequirer())
{
errorHandler.reportError(*error);
return Status::ErrorReported;
@ -98,7 +85,7 @@ Error Navigator::navigateImpl(std::string_view path)
// If the alias is "@self", we reset to the requirer's context and
// navigate directly from there.
if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier())))
if (Error error = resetToRequirer())
return error;
if (Error error = navigateThroughPath(path))
return error;
@ -114,7 +101,7 @@ Error Navigator::navigateImpl(std::string_view path)
if (pathType == PathType::RelativeToCurrent || pathType == PathType::RelativeToParent)
{
if (Error error = toError(navigationContext.toParent()))
if (Error error = navigateToParent(std::nullopt))
return error;
if (Error error = navigateThroughPath(path))
return error;
@ -133,6 +120,7 @@ Error Navigator::navigateThroughPath(std::string_view path)
components = splitPath(components.second);
}
std::optional<std::string> previousComponent;
while (!(components.first.empty() && components.second.empty()))
{
if (components.first == "." || components.first.empty())
@ -142,14 +130,15 @@ Error Navigator::navigateThroughPath(std::string_view path)
}
else if (components.first == "..")
{
if (Error error = toError(navigationContext.toParent()))
if (Error error = navigateToParent(previousComponent))
return error;
}
else
{
if (Error error = toError(navigationContext.toChild(std::string{components.first})))
if (Error error = navigateToChild(std::string{components.first}))
return error;
}
previousComponent = components.first;
components = splitPath(components.second);
}
@ -167,11 +156,11 @@ Error Navigator::navigateToAlias(const std::string& alias, const std::string& va
}
else if (pathType == PathType::Aliased)
{
return "@" + alias + " cannot point to other aliases";
return "alias \"@" + alias + "\" cannot point to an aliased path (\"" + value + "\")";
}
else
{
if (Error error = toError(navigationContext.jumpToAlias(value)))
if (Error error = jumpToAlias(value))
return error;
}
@ -189,7 +178,7 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
{
std::optional<std::string> configContents = navigationContext.getConfig();
if (!configContents)
return "could not get configuration file contents";
return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\"";
Luau::ConfigOptions opts;
Luau::ConfigOptions::AliasOptions aliasOpts;
@ -205,4 +194,56 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
return std::nullopt;
}
Error Navigator::resetToRequirer()
{
NavigationContext::NavigateResult result = navigationContext.reset(navigationContext.getRequirerIdentifier());
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage = "could not reset to requiring context";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
Error Navigator::jumpToAlias(const std::string& aliasPath)
{
NavigationContext::NavigateResult result = navigationContext.jumpToAlias(aliasPath);
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage = "could not jump to alias \"" + aliasPath + "\"";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
Error Navigator::navigateToParent(std::optional<std::string> previousComponent)
{
NavigationContext::NavigateResult result = navigationContext.toParent();
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage;
if (previousComponent)
errorMessage = "could not get parent of component \"" + *previousComponent + "\"";
else
errorMessage = "could not get parent of requiring context";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
Error Navigator::navigateToChild(const std::string& component)
{
NavigationContext::NavigateResult result = navigationContext.toChild(component);
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage = "could not resolve child component \"" + component + "\"";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
} // namespace Luau::Require

View file

@ -106,7 +106,9 @@ struct luarequire_Configuration
luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
// Executes the module and places the result on the stack. Returns the
// number of results placed on the stack.
// number of results placed on the stack. Returning -1 directs the requiring
// thread to yield. In this case, this thread should be resumed with the
// module result pushed onto its stack.
int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents);
};
@ -130,3 +132,10 @@ LUALIB_API int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuratio
// result will always be immediately returned when the given path is required.
// Expects the path and table to be passed as arguments on the stack.
LUALIB_API int luarequire_registermodule(lua_State* L);
// Clears the entry associated with the given cache key from the require cache.
// Expects the cache key to be passed as an argument on the stack.
LUALIB_API int luarequire_clearcacheentry(lua_State* L);
// Clears all entries from the require cache.
LUALIB_API int luarequire_clearcache(lua_State* L);

View file

@ -6,6 +6,8 @@
#include "lua.h"
#include "lualib.h"
#include <string>
static constexpr size_t initalFileBufferSize = 1024;
static constexpr size_t initalIdentifierBufferSize = 64;
@ -111,14 +113,16 @@ std::optional<std::string> RuntimeNavigationContext::getStringFromCWriter(
}
RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L)
RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L, std::string requiredPath)
: L(L)
, errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ")
{
}
void RuntimeErrorHandler::reportError(std::string message)
{
luaL_errorL(L, "%s", message.c_str());
std::string fullError = errorPrefix + std::move(message);
luaL_errorL(L, "%s", fullError.c_str());
}
} // namespace Luau::Require

View file

@ -4,6 +4,8 @@
#include "Luau/RequireNavigator.h"
#include "Luau/Require.h"
#include <string>
struct lua_State;
struct luarequire_Configuration;
@ -48,11 +50,12 @@ private:
class RuntimeErrorHandler : public ErrorHandler
{
public:
RuntimeErrorHandler(lua_State* L);
RuntimeErrorHandler(lua_State* L, std::string requiredPath);
void reportError(std::string message) override;
private:
lua_State* L;
std::string errorPrefix;
};
} // namespace Luau::Require

View file

@ -53,7 +53,7 @@ static int pushrequireclosureinternal(
lua_pushlightuserdata(L, ctx);
// require-like closure captures config and ctx as upvalues
lua_pushcclosure(L, requirelikefunc, debugname, 2);
lua_pushcclosurek(L, requirelikefunc, debugname, 2, Luau::Require::lua_requirecont);
return 1;
}
@ -77,3 +77,13 @@ int luarequire_registermodule(lua_State* L)
{
return Luau::Require::registerModuleImpl(L);
}
int luarequire_clearcacheentry(lua_State* L)
{
return Luau::Require::clearCacheEntry(L);
}
int luarequire_clearcache(lua_State* L)
{
return Luau::Require::clearCache(L);
}

View file

@ -50,7 +50,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
luaL_error(L, "require is not supported in this context");
RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname};
RuntimeErrorHandler errorHandler{L}; // Errors reported directly to lua_State.
RuntimeErrorHandler errorHandler{L, path}; // Errors reported directly to lua_State.
Navigator navigator(navigationContext, errorHandler);
@ -61,7 +61,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
if (!navigationContext.isModulePresent())
{
luaL_errorL(L, "no module present at resolved path");
errorHandler.reportError("no module present at resolved path");
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
}
@ -118,24 +118,13 @@ static int checkRegisteredModules(lua_State* L, const char* path)
return 1;
}
int lua_requireinternal(lua_State* L, const char* requirerChunkname)
int lua_requirecont(lua_State* L, int status)
{
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!lrc)
luaL_error(L, "unable to find require configuration");
// Number of stack arguments present before this continuation is called.
const int numStackArgs = 2;
const int numResults = lua_gettop(L) - numStackArgs;
const char* cacheKey = luaL_checkstring(L, 2);
void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2));
const char* path = luaL_checkstring(L, 1);
if (checkRegisteredModules(L, path) == 1)
return 1;
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
if (resolvedRequire.status == ResolvedRequire::Status::Cached)
return 1;
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str());
if (numResults > 1)
luaL_error(L, "module must return a single value");
@ -151,7 +140,7 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname)
lua_pushvalue(L, -2);
// (-3) result, (-2) cache table, (-1) result
lua_setfield(L, -2, resolvedRequire.cacheKey.c_str());
lua_setfield(L, -2, cacheKey);
// (-2) result, (-1) cache table
lua_pop(L, 1);
@ -161,6 +150,43 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname)
return numResults;
}
int lua_requireinternal(lua_State* L, const char* requirerChunkname)
{
// If modifying the state of the stack, please update numStackArgs in the
// lua_requirecont continuation function.
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!lrc)
luaL_error(L, "unable to find require configuration");
void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2));
// (1) path
const char* path = luaL_checkstring(L, 1);
if (checkRegisteredModules(L, path) == 1)
return 1;
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
if (resolvedRequire.status == ResolvedRequire::Status::Cached)
return 1;
// (1) path, (2) cacheKey
lua_pushstring(L, resolvedRequire.cacheKey.c_str());
int numArgsBeforeLoad = lua_gettop(L);
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str());
if (numResults == -1)
{
if (lua_gettop(L) != numArgsBeforeLoad)
luaL_error(L, "stack cannot be modified when require yields");
return lua_yield(L, 0);
}
return lua_requirecont(L, LUA_OK);
}
int lua_proxyrequire(lua_State* L)
{
const char* requirerChunkname = luaL_checkstring(L, 2);
@ -170,7 +196,14 @@ int lua_proxyrequire(lua_State* L)
int lua_require(lua_State* L)
{
lua_Debug ar;
lua_getinfo(L, 1, "s", &ar);
int level = 1;
do
{
if (!lua_getinfo(L, level++, "s", &ar))
luaL_error(L, "require is not supported in this context");
} while (ar.what[0] == 'C');
return lua_requireinternal(L, ar.source);
}
@ -199,4 +232,21 @@ int registerModuleImpl(lua_State* L)
return 0;
}
int clearCacheEntry(lua_State* L)
{
const char* cacheKey = luaL_checkstring(L, 1);
luaL_findtable(L, LUA_REGISTRYINDEX, requiredCacheTableKey, 1);
lua_pushnil(L);
lua_setfield(L, -2, cacheKey);
lua_pop(L, 1);
return 0;
}
int clearCache(lua_State* L)
{
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey);
return 0;
}
} // namespace Luau::Require

View file

@ -8,7 +8,11 @@ namespace Luau::Require
int lua_require(lua_State* L);
int lua_proxyrequire(lua_State* L);
int lua_requirecont(lua_State* L, int status);
int registerModuleImpl(lua_State* L);
int clearCacheEntry(lua_State* L);
int clearCache(lua_State* L);
} // namespace Luau::Require

View file

@ -599,4 +599,63 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit")
CHECK(toJson(root->body.data[1]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericType")
{
AstStatBlock* root = expectParse(R"(
a = function<b, c>()
end
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[{"type":"AstGenericType","name":"b"},{"type":"AstGenericType","name":"c"}],"genericPacks":[],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,28 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypeWithDefault")
{
AstStatBlock* root = expectParse(R"(
type Foo<X = string> = X
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"1,8 - 1,32","name":"Foo","generics":[{"type":"AstGenericType","name":"X","luauType":{"type":"AstTypeReference","location":"1,21 - 1,27","name":"string","nameLocation":"1,21 - 1,27","parameters":[]}}],"genericPacks":[],"value":{"type":"AstTypeReference","location":"1,31 - 1,32","name":"X","nameLocation":"1,31 - 1,32","parameters":[]},"exported":false})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePack")
{
AstStatBlock* root = expectParse(R"(
a = function<b..., c...>()
end
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"b"},{"type":"AstGenericTypePack","name":"c"}],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,34 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePackWithDefault")
{
AstStatBlock* root = expectParse(R"(
type Foo<X... = ...string> = any
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"1,8 - 1,40","name":"Foo","generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"X","luauType":{"type":"AstTypePackVariadic","location":"1,24 - 1,33","variadicType":{"type":"AstTypeReference","location":"1,27 - 1,33","name":"string","nameLocation":"1,27 - 1,33","parameters":[]}}}],"value":{"type":"AstTypeReference","location":"1,37 - 1,40","name":"any","nameLocation":"1,37 - 1,40","parameters":[]},"exported":false})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_SUITE_END();

View file

@ -13,6 +13,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
struct DataFlowGraphFixture
{
@ -421,6 +422,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "property_lookup_on_a_phi_node_3")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
dfg(R"(
local x = 5
@ -439,15 +442,14 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_
DefId x4 = getDef<AstExprLocal, 3>(); // x = "five"
CHECK(x1 != x2);
CHECK(x2 != x3);
CHECK(x2 == x3);
CHECK(x3 != x4);
const Phi* phi = get<Phi>(x2);
REQUIRE(phi);
REQUIRE(phi->operands.size() == 3);
REQUIRE(phi->operands.size() == 2);
CHECK(phi->operands.at(0) == x1);
CHECK(phi->operands.at(1) == x3);
CHECK(phi->operands.at(2) == x4);
CHECK(phi->operands.at(1) == x4);
}
TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions_properties")

View file

@ -28,6 +28,8 @@
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauTypeFunOptional)
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
#define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__)
@ -182,6 +184,9 @@ private:
struct BuiltinsFixture : Fixture
{
explicit BuiltinsFixture(bool prepareAutocomplete = false);
// For the purpose of our tests, we're always the latest version of type functions.
ScopedFastFlag sff_optionalInTypeFunctionLib{FFlag::LuauTypeFunOptional, true};
};
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments);

View file

@ -24,10 +24,6 @@
using namespace Luau;
LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauCloneIncrementalModule)
LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility)
@ -72,8 +68,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType
{
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
ScopedFastFlag luauFreeTypesMustHaveBounds{FFlag::LuauFreeTypesMustHaveBounds, true};
ScopedFastFlag luauCloneIncrementalModule{FFlag::LuauCloneIncrementalModule, true};
ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true};
ScopedFastFlag luauClonedTableAndFunctionTypesMustHaveScopes{FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes, true};
ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true};
@ -1562,8 +1556,6 @@ return module)";
{
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
ScopedFastFlag sff2{FFlag::LuauCloneIncrementalModule, true};
ScopedFastFlag sff3{FFlag::LuauFreeTypesMustHaveBounds, true};
checkAndExamine(source, "module", "{ }");
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
@ -2782,7 +2774,6 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "fragment_ac_must_travers
// This test ensures that we traverse typeof expressions for defs that are being referred to in the fragment
// In this case, we want to ensure we populate the incremental environment with the reference to `m`
// Without this, we would ice as we will refer to the local `m` before it's declaration
ScopedFastFlag sff{FFlag::LuauMixedModeDefFinderTraversesTypeOf, true};
const std::string source = R"(
--!strict
local m = {}
@ -2860,7 +2851,6 @@ type V = {h : number, i : U?}
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "generalization_crash_when_old_solver_freetypes_have_no_bounds_set")
{
ScopedFastFlag sff{FFlag::LuauFreeTypesMustHaveBounds, true};
const std::string source = R"(
local UserInputService = game:GetService("UserInputService");
@ -2892,7 +2882,6 @@ end)
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation")
{
ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true};
ToStringOptions opt;
opt.exhaustive = true;
opt.exhaustive = true;
@ -2962,7 +2951,6 @@ return module)";
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_shouldnt_crash_on_cross_module_mutation")
{
ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true};
const std::string source = R"(local module = {}
function module.
return module

View file

@ -17,7 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
namespace
{
@ -1015,7 +1015,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments")
LUAU_REQUIRE_NO_ERRORS(resultA);
CheckResult resultB = frontend.check("B");
if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictVisitTypes2)
LUAU_REQUIRE_NO_ERRORS(resultB);
else
LUAU_REQUIRE_ERROR_COUNT(1, resultB);

View file

@ -17,7 +17,7 @@
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
using namespace Luau;
@ -668,7 +668,7 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict")
{
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true};
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes2, true};
CheckResult result = check(Mode::Nonstrict, R"(
--!nonstrict
@ -683,7 +683,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict")
TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict_2")
{
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true};
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes2, true};
CheckResult result = check(Mode::Nonstrict, R"(
--!nonstrict
@ -707,4 +707,13 @@ end
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "incomplete_function_annotation")
{
CheckResult result = check(Mode::Nonstrict, R"(
local x: () ->
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauParseStringIndexer)
@ -2869,8 +2868,6 @@ TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_
TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token")
{
ScopedFastFlag _{FFlag::LuauFixDoBlockEndLocation, true};
AstStatBlock* stat = parse(R"(
do
local x = 1

View file

@ -332,6 +332,13 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePath")
assertOutputContainsAll({"true", "result from dependency"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePathWithinPcall")
{
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency";
runCode(L, "return pcall(require, \"" + path + "\")");
assertOutputContainsAll({"true", "result from dependency"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireRelativeToRequiringFile")
{
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
@ -372,7 +379,9 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity")
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_file_requirer";
runProtectedRequire(ambiguousPath);
assertOutputContainsAll({"false", "require path could not be resolved to a unique file"});
assertOutputContainsAll(
{"false", "error requiring module \"./ambiguous/file/dependency\": could not resolve child component \"dependency\" (ambiguous)"}
);
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity")
@ -380,7 +389,9 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity")
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_directory_requirer";
runProtectedRequire(ambiguousPath);
assertOutputContainsAll({"false", "require path could not be resolved to a unique file"});
assertOutputContainsAll(
{"false", "error requiring module \"./ambiguous/directory/dependency\": could not resolve child component \"dependency\" (ambiguous)"}
);
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau")
@ -466,6 +477,61 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCachedResult")
assertOutputContainsAll({"true"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCacheEntry")
{
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
std::string cacheKey = absolutePath + ".luau";
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, cacheKey.c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
runProtectedRequire(relativePath);
assertOutputContainsAll({"true", "result from dependency", "required into module"});
// Check cache for the absolute path as a cache key
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, cacheKey.c_str());
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
lua_pushcfunction(L, luarequire_clearcacheentry, nullptr);
lua_pushstring(L, cacheKey.c_str());
lua_call(L, 1, 0);
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, cacheKey.c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared");
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCache")
{
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
std::string cacheKey = absolutePath + ".luau";
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, cacheKey.c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
runProtectedRequire(relativePath);
assertOutputContainsAll({"true", "result from dependency", "required into module"});
// Check cache for the absolute path as a cache key
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, cacheKey.c_str());
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
lua_pushcfunction(L, luarequire_clearcache, nullptr);
lua_call(L, 0, 0);
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, cacheKey.c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared");
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule")
{
lua_pushcfunction(L, luarequire_registermodule, nullptr);

View file

@ -1249,8 +1249,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "bill")
CHECK(isSubtype(b, a).isSubtype);
}
// TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()")
TEST_CASE_FIXTURE(SubtypeFixture, "fred")
TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()")
{
auto makeTheType = [&]()
{

View file

@ -341,7 +341,10 @@ TEST_CASE("function_spaces_around_tokens")
TEST_CASE("function_with_types_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true}
};
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -392,9 +395,8 @@ TEST_CASE("function_with_types_spaces_around_tokens")
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any ): string end )";
CHECK_EQ(code, transpile(code, {}, true).code);
// TODO(CLI-139347): re-enable test once return type positions are supported
// code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any) :string end )";
// CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any) :string end )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code);

View file

@ -18,6 +18,7 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
LUAU_FASTFLAG(LuauMetatablesHaveLength)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauIndexAnyIsAny)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauHasPropProperBlock)
@ -1674,4 +1675,70 @@ print(test.a)
CHECK("Type 'add<string, string>' does not have key 'a'" == toString(result.errors[1]));
}
struct TFFixture
{
TypeArena arena_;
NotNull<TypeArena> arena{&arena_};
BuiltinTypes builtinTypes_;
NotNull<BuiltinTypes> builtinTypes{&builtinTypes_};
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack);
InternalErrorReporter ice;
UnifierSharedState unifierState{&ice};
SimplifierPtr simplifier = EqSatSimplification::newSimplifier(arena, builtinTypes);
Normalizer normalizer{arena, builtinTypes, NotNull{&unifierState}};
TypeCheckLimits limits;
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
const ScopedFastFlag sff[1] = {
{FFlag::DebugLuauGreedyGeneralization, true},
};
BuiltinTypeFunctions builtinTypeFunctions;
TypeFunctionContext tfc{
arena,
builtinTypes,
NotNull{globalScope.get()},
NotNull{simplifier.get()},
NotNull{&normalizer},
NotNull{&runtime},
NotNull{&ice},
NotNull{&limits}
};
};
TEST_CASE_FIXTURE(TFFixture, "refine<G, ~(false?)>")
{
TypeId g = arena->addType(GenericType{globalScope.get(), Polarity::Negative});
TypeId refineTy = arena->addType(TypeFunctionInstanceType{
builtinTypeFunctions.refineFunc, {g, builtinTypes->truthyType}
});
FunctionGraphReductionResult res = reduceTypeFunctions(refineTy, Location{}, tfc);
CHECK(res.reducedTypes.size() == 1);
CHECK(res.errors.size() == 0);
CHECK(res.irreducibleTypes.size() == 0);
CHECK(res.blockedTypes.size() == 0);
}
TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>")
{
TypeId aType = arena->freshType(builtinTypes, globalScope.get());
TypeId bType = arena->freshType(builtinTypes, globalScope.get());
TypeId orType = arena->addType(TypeFunctionInstanceType{
builtinTypeFunctions.orFunc, {aType, bType}
});
FunctionGraphReductionResult res = reduceTypeFunctions(orType, Location{}, tfc);
CHECK(res.reducedTypes.size() == 1);
}
TEST_SUITE_END();

View file

@ -15,7 +15,6 @@ LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works")
@ -370,6 +369,43 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works")
CHECK(toString(tpm->givenTp) == "boolean | number | string");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type function numberhuh()
return types.optional(types.number)
end
-- forcing an error here to check the exact type of the union
local function ok(idx: numberhuh<>): never return idx end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tpm);
CHECK(toString(tpm->givenTp) == "number?");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works_on_unions")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type function foobar()
local ty = types.unionof(types.string, types.number, types.boolean)
return types.optional(ty)
end
-- forcing an error here to check the exact type of the union
local function ok(idx: foobar<>): never return idx end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tpm);
CHECK(toString(tpm->givenTp) == "(boolean | number | string)?");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};

View file

@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
TEST_SUITE_BEGIN("TypeAliases");
@ -1197,7 +1197,7 @@ TEST_CASE_FIXTURE(Fixture, "bound_type_in_alias_segfault")
export type FieldConfigMap<TSource, TContext> = Map<string, FieldConfig<TSource, TContext>>
)");
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
LUAU_CHECK_ERROR_COUNT(2, result);
else
LUAU_CHECK_NO_ERRORS(result);

View file

@ -12,6 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
TEST_SUITE_BEGIN("BuiltinTests");
@ -472,7 +473,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2 && FFlag::DebugLuauGreedyGeneralization)
CHECK("{ [number]: string | string | string, n: number }" == toString(requireType("t")));
else if (FFlag::LuauSolverV2)
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));
else
CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t")));

View file

@ -24,11 +24,13 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauFormatUseLastPosition)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
TEST_SUITE_BEGIN("TypeInferFunctions");
@ -90,7 +92,7 @@ TEST_CASE_FIXTURE(Fixture, "check_function_bodies")
if (FFlag::LuauSolverV2)
{
const TypePackMismatch* tm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tm);
REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]);
CHECK(toString(tm->wantedTp) == "number");
CHECK(toString(tm->givenTp) == "boolean");
}
@ -3032,6 +3034,10 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
auto tm2 = get<TypePackMismatch>(result.errors[1]);
REQUIRE(tm2);
CHECK(toString(tm2->wantedTp) == "string");
if (FFlag::DebugLuauGreedyGeneralization)
CHECK(toString(tm2->givenTp) == "unknown & ~(false?)");
else
CHECK(toString(tm2->givenTp) == "~(false?)");
}
else
@ -3213,12 +3219,13 @@ TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generali
TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func")
{
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReduceUnionFollowUnionType, true}};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}};
// This block ends up minting a type like:
// Previously, this block minted a type like:
//
// t2 where t1 = union<t2, t1> | union<t2, t1> | union<t2, t1> ; t2 = union<t2, t1>
//
// ... due to how upvalues contributed to the locally inferred types.
CheckResult result = check(R"(
local _ = ...
function _()
@ -3226,12 +3233,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func")
end
_[function(...) repeat until _(_[l100]) _ = _ end] += _
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
auto err0 = get<UnknownSymbol>(result.errors[0]);
CHECK(err0);
CHECK_EQ(err0->name, "l100");
auto err1 = get<NotATable>(result.errors[1]);
CHECK(err1);
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack")

View file

@ -17,6 +17,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
TEST_SUITE_BEGIN("TypeInferOperators");
@ -27,8 +28,18 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
local x:string|number = s
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
{
// FIXME: Regression
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
CHECK("number | string" == toString(*requireType("x")));
}
else
{
CHECK_EQ(toString(*requireType("s")), "number | string");
CHECK_EQ(toString(*requireType("x")), "number | string");
}
}
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
@ -39,8 +50,18 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
local y = x or "s"
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
{
// FIXME: Regression.
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
CHECK("number | string | string" == toString(*requireType("y")));
}
else
{
CHECK_EQ(toString(*requireType("s")), "number | string");
CHECK_EQ(toString(*requireType("y")), "number | string");
}
}
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
@ -50,6 +71,13 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
local x:string = s
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
{
// FIXME: Regression
CHECK("(string & ~(false?)) | string" == toString(requireType("s")));
}
else
CHECK_EQ(*requireType("s"), *builtinTypes->stringType);
}

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Normalize.h"
#include "Luau/Scope.h"
#include "Luau/Type.h"
#include "Luau/TypeInfer.h"
#include "Fixture.h"
@ -11,10 +12,12 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauSimplyRefineNotNil)
LUAU_FASTFLAG(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauSimplificationTableExternType)
using namespace Luau;
@ -113,10 +116,18 @@ struct RefinementExternTypeFixture : BuiltinsFixture
{"Position", Property{vec3}},
};
TypeId optionalPart = arena.addType(UnionType{{part, builtinTypes->nilType}});
TypeId weldConstraint = frontend.globals.globalTypes.addType(ExternType{"WeldConstraint", {}, inst, std::nullopt, {}, nullptr, "Test", {}});
getMutable<ExternType>(weldConstraint)->props = {
{"Part0", Property{optionalPart}},
{"Part1", Property{optionalPart}},
};
frontend.globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3};
frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};
frontend.globals.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder};
frontend.globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part};
frontend.globals.globalScope->exportedTypeBindings["WeldConstraint"] = TypeFun{{}, weldConstraint};
for (const auto& [name, ty] : frontend.globals.globalScope->exportedTypeBindings)
persist(ty.type);
@ -752,11 +763,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
{
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
}
else
{
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK_EQ("string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
CHECK_EQ("nil", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
CHECK_EQ("string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string")
@ -1578,9 +1600,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_param_of_type_folder_or_p
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_known_ahead_of_time")
{
// CLI-115087 - The new solver does not consistently combine tables with
// class types when they appear in the upper bounds of a free type.
DOES_NOT_PASS_NEW_SOLVER_GUARD();
ScopedFastFlag sff{FFlag::LuauSimplificationTableExternType, true};
CheckResult result = check(R"(
local function f(x): Instance
@ -1596,8 +1616,41 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_know
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2)
{
CHECK_EQ("t1 where t1 = Instance & { read IsA: (t1, string) -> (unknown, ...unknown) }", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("t1 where t1 = Instance & { read IsA: (t1, string) -> (unknown, ...unknown) }", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_should_not_refine_extern_types_to_never")
{
CheckResult result = check(R"(
local weld: WeldConstraint = nil :: any
assert(weld.Part1)
print(weld) -- hover type incorrectly becomes `never`
assert(weld.Part1.Name == "RootPart")
local part1 = assert(weld.Part1)
local pos = part1.Position
)");
if (FFlag::LuauSolverV2)
{
// CLI-142467: this is a major regression that we need to address.
CHECK_EQ("never", toString(requireTypeAtPosition({3, 15})));
CHECK_EQ("any", toString(requireTypeAtPosition({6, 29})));
}
else
{
CHECK_EQ("WeldConstraint", toString(requireTypeAtPosition({3, 15})));
CHECK_EQ("Vector3", toString(requireTypeAtPosition({6, 29})));
}
}
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_part")
@ -1605,6 +1658,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_pa
// CLI-117135 - RefinementTests.x_is_not_instance_or_else_not_part not correctly applying refinements to a function parameter
if (FFlag::LuauSolverV2)
return;
CheckResult result = check(R"(
local function f(x: Part | Folder | string)
if typeof(x) ~= "Instance" or not x:IsA("Part") then
@ -1827,6 +1881,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_a_param_that_got_resolved
// CLI-117134 - Applying a refinement causes an optional value access error.
if (FFlag::LuauSolverV2)
return;
CheckResult result = check(R"(
type Id<T> = T
@ -2447,6 +2502,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
end
)"));
if (FFlag::DebugLuauGreedyGeneralization)
CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24})));
else
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24})));
}

View file

@ -22,7 +22,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauFollowTableFreeze)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
@ -4723,10 +4722,10 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
end
)");
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2 && !FFlag::DebugLuauGreedyGeneralization)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<NotATable>(result.errors[0]));
LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_ERROR(result, NotATable);
CHECK_EQ("(unknown, *error-type*) -> *error-type*", toString(requireType("foo")));
}
else
@ -5335,7 +5334,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauFollowTableFreeze, true},
};
auto result = check(R"(

View file

@ -2023,7 +2023,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving"
LUAU_REQUIRE_NO_ERROR(results, ConstraintSolvingIncompleteError);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "konnichiwa" * doctest::timeout(0.25))
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_unification_aborts_eventually" * doctest::timeout(0.25))
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, false},

View file

@ -13,6 +13,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
TEST_SUITE_BEGIN("TypePackTests");
@ -96,6 +97,9 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
CHECK_EQ("<a, b..., c...>((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply")));
else
CHECK_EQ("<a, b..., c...>((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply")));
}

View file

@ -5,6 +5,8 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
using namespace Luau;
@ -263,6 +265,8 @@ TEST_CASE_FIXTURE(TypeStateFixture, "local_assigned_in_only_one_branch_that_fall
TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_and_else_branch_also_assigns_but_is_met_with_return")
{
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
CheckResult result = check(R"(
local x = nil
if math.random() > 0.5 then
@ -275,11 +279,13 @@ TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_and_else_branch_also_as
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("number?" == toString(requireType("y")));
CHECK("number" == toString(requireType("y")));
}
TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_but_is_met_with_return_and_else_branch_assigns")
{
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
CheckResult result = check(R"(
local x = nil
if math.random() > 0.5 then
@ -292,7 +298,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_but_is_met_with_return_
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("string?" == toString(requireType("y")));
CHECK("string" == toString(requireType("y")));
}
TEST_CASE_FIXTURE(TypeStateFixture, "invalidate_type_refinements_upon_assignments")
@ -338,8 +344,9 @@ TEST_CASE_FIXTURE(TypeStateFixture, "local_t_is_assigned_a_fresh_table_with_x_as
}
#endif
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments")
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
CheckResult result = check(R"(
local x = nil
@ -352,12 +359,16 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignmen
f()
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 18})));
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto err = get<TypeMismatch>(result.errors[0]);
CHECK_EQ("number?", toString(err->wantedType));
CHECK_EQ("string", toString(err->givenType));
CHECK("number?" == toString(requireTypeAtPosition({4, 18})));
}
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments_2")
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type_2")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
CheckResult result = check(R"(
local t = {x = nil}
@ -370,9 +381,13 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignmen
f()
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("{ x: nil } | { x: number } | { x: string }" == toString(requireTypeAtPosition({4, 18}), {true}));
CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 20})));
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto err = get<TypeMismatch>(result.errors[0]);
CHECK_EQ("t | { x: number }", toString(err->wantedType));
CHECK_EQ("{ x: string }", toString(err->givenType));
CHECK("{ x: nil } | { x: number }" == toString(requireTypeAtPosition({4, 18}), {true}));
CHECK("number?" == toString(requireTypeAtPosition({4, 20})));
}
TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions")
@ -555,4 +570,280 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalized_type_variables_are_bad" *
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1547_simple")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local rand = 0
function a()
rand = (rand % 4) + 1;
end
)"));
auto randTy = getType("rand");
REQUIRE(randTy);
CHECK_EQ("number", toString(*randTy));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1547")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local rand = 0
function a()
rand = (rand % 4) + 1;
end
function b()
rand = math.max(rand - 1, 0);
end
)"));
auto randTy = getType("rand");
REQUIRE(randTy);
CHECK_EQ("number", toString(*randTy));
}
TEST_CASE_FIXTURE(Fixture, "modify_captured_table_field")
{
LUAU_REQUIRE_NO_ERRORS(check(R"(
local state = { x = 0 }
function incr()
state.x = state.x + 1
end
)"));
auto randTy = getType("state");
REQUIRE(randTy);
CHECK_EQ("{ x: number }", toString(*randTy, {true}));
}
TEST_CASE_FIXTURE(Fixture, "oss_1561")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
loadDefinition(R"(
declare class Vector3
X: number
Y: number
Z: number
end
declare Vector3: {
new: (number?, number?, number?) -> Vector3
}
)");
LUAU_REQUIRE_NO_ERRORS(check(R"(
local targetVelocity: Vector3 = Vector3.new()
function set2D(X: number, Y: number)
targetVelocity = Vector3.new(X, Y, targetVelocity.Z)
end
)"));
CHECK_EQ("(number, number) -> ()", toString(requireType("set2D")));
}
TEST_CASE_FIXTURE(Fixture, "oss_1575")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local flag = true
local function Flip()
flag = not flag
end
)"));
}
TEST_CASE_FIXTURE(Fixture, "capture_upvalue_in_returned_function")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
function def()
local i : number = 0
local function Counter()
i = i + 1
return i
end
return Counter
end
)"));
CHECK_EQ("() -> () -> number", toString(requireType("def")));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_else_branch")
{
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
CheckResult result = check(R"(
--!strict
local x
local coinflip : () -> boolean = (nil :: any)
if coinflip () then
x = "I win."
else
error("You lose.")
end
print(x)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("string", toString(requireTypeAtPosition({11, 14})));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_if_branch")
{
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
CheckResult result = check(R"(
--!strict
local x
local coinflip : () -> boolean = (nil :: any)
if coinflip () then
error("You lose.")
else
x = "I win."
end
print(x)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("string", toString(requireTypeAtPosition({11, 14})));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring")
{
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
CheckResult result = check(R"(
--!strict
type Payload = { payload: number }
local function decode(s: string): Payload?
return (nil :: any)
end
local function decodeEx(s: string): Payload
local p = decode(s)
if not p then
error("failed to decode payload!!!")
end
return p
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
};
CheckResult result = check(R"(
--!strict
local x = nil
while math.random() > 0.5 do
x = 42
return
end
print(x)
)");
LUAU_REQUIRE_NO_ERRORS(result);
// CLI-142447: This should probably be `nil` given that the `while` loop
// unconditionally returns, but `number?` is sound, if incomplete.
CHECK_EQ("number?", toString(requireTypeAtPosition({10, 14})));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "type_refinement_in_loop")
{
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
CheckResult result = check(R"(
--!strict
local function onEachString(t: { string | number })
for _, v in t do
if type(v) ~= "string" then
continue
end
print(v)
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number | string", toString(requireTypeAtPosition({4, 24})));
CHECK_EQ("string", toString(requireTypeAtPosition({7, 22})));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_if_branch_and_do_nothing_in_else")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
};
CheckResult result = check(R"(
--!strict
local x
local coinflip : () -> boolean = (nil :: any)
if coinflip () then
error("You lose.")
else
end
print(x)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("nil", toString(requireTypeAtPosition({10, 14})));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "assign_in_an_if_branch_without_else")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
};
CheckResult result = check(R"(
--!strict
local x
local coinflip : () -> boolean = (nil :: any)
if coinflip () then
x = "I win."
end
print(x)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("string?", toString(requireTypeAtPosition({9, 14})));
}
TEST_SUITE_END();

View file

@ -1 +0,0 @@
return {"result from global_library"}

View file

@ -1 +0,0 @@
return {"result from library"}