Sync to upstream/release/675 (#1845)

## General 
- Introduce `Frontend::parseModules` for parsing a group of modules at
once.
- Support chained function types in the CST.

## New Type Solver
- Enable write-only table properties (described in [this
RFC](https://rfcs.luau.org/property-writeonly.html)).
- Disable singleton inference for large tables to improve performance.
- Fix a bug that occurs when we try to expand a type alias to itself.
- Catch cancelation during the type-checking phase in addition to during
constraint solving.
- Fix stringification of the empty type pack: `()`.
- Improve errors for calls being rejected on the primitive `function`
type.
- Rework generalization: We now generalize types as soon as the last
constraint relating to them is finished. We think this will reduce the
number of cases where type inference fails to complete and reduce the
number of instances where `*blocked*` types appear in the inference
result.

## VM/Runtime
- Dynamically disable native execution for functions that incur a
slowdown (relative to bytecode execution).
- Improve names for `thread`/`closure`/`proto` in the Luau heap dump.

---

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
This commit is contained in:
Varun Saini 2025-05-27 14:24:46 -07:00 committed by GitHub
parent 92cce5776c
commit 5965818283
Signed by: DevComp
GPG key ID: B5690EEEBB952194
70 changed files with 3153 additions and 925 deletions

View file

@ -175,6 +175,10 @@ private:
std::vector<TypeId> unionsToSimplify;
// Used to keep track of when we are inside a large table and should
// opt *not* to do type inference for singletons.
size_t largeTableDepth = 0;
/**
* Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to.

View file

@ -225,7 +225,7 @@ public:
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const TableCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);

View file

@ -166,6 +166,7 @@ struct Frontend
// Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking
void parse(const ModuleName& name);
void parseModules(const std::vector<ModuleName>& name);
// Parse and typecheck module graph
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess

View file

@ -63,7 +63,7 @@ struct OverloadResolver
InsertionOrderedMap<TypeId, std::pair<OverloadResolver::Analysis, size_t>> resolution;
std::pair<OverloadResolver::Analysis, TypeId> selectOverload(TypeId ty, TypePackId args);
std::pair<OverloadResolver::Analysis, TypeId> selectOverload(TypeId ty, TypePackId args, bool useFreeTypeBounds);
void resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector<AstExpr*>* argExprs);
private:

View file

@ -72,6 +72,11 @@ struct SubtypingResult
/// isSubtype is false, depending on the input types.
SubtypingReasonings reasoning{kEmptyReasoning};
// If this subtype result required testing free types, we might be making
// assumptions about what the free type eventually resolves to. If so,
// those assumptions are recorded here.
std::vector<SubtypeConstraint> assumedConstraints;
SubtypingResult& andAlso(const SubtypingResult& other);
SubtypingResult& orElse(const SubtypingResult& other);
SubtypingResult& withBothComponent(TypePath::Component component);

View file

@ -24,7 +24,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
@ -163,78 +162,39 @@ static bool checkTypeMatch(
UnifierSharedState unifierState(&iceReporter);
SimplifierPtr simplifier = newSimplifier(NotNull{typeArena}, builtinTypes);
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
if (FFlag::LuauAutocompleteUsesModuleForTypeCompatibility)
if (module.checkedInNewSolver)
{
if (module.checkedInNewSolver)
{
TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{
NotNull{&iceReporter}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{
NotNull{&iceReporter}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
Subtyping subtyping{
builtinTypes,
NotNull{typeArena},
NotNull{simplifier.get()},
NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull{&iceReporter}
};
Subtyping subtyping{
builtinTypes,
NotNull{typeArena},
NotNull{simplifier.get()},
NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull{&iceReporter}
};
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
// Cost of normalization can be too high for autocomplete response time requirements
unifier.normalize = false;
unifier.checkInhabited = false;
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
return unifier.canUnify(subTy, superTy).empty();
}
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{
if (FFlag::LuauSolverV2)
{
TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{
NotNull{&iceReporter}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
// Cost of normalization can be too high for autocomplete response time requirements
unifier.normalize = false;
unifier.checkInhabited = false;
Subtyping subtyping{
builtinTypes,
NotNull{typeArena},
NotNull{simplifier.get()},
NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull{&iceReporter}
};
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
// Cost of normalization can be too high for autocomplete response time requirements
unifier.normalize = false;
unifier.checkInhabited = false;
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
return unifier.canUnify(subTy, superTy).empty();
}
return unifier.canUnify(subTy, superTy).empty();
}
}

View file

@ -30,7 +30,7 @@
*/
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
@ -310,8 +310,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization3
if (FFlag::LuauNonReentrantGeneralization3)
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauEagerGeneralization
if (FFlag::LuauEagerGeneralization)
globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2)

View file

@ -12,8 +12,6 @@ LUAU_FASTFLAG(LuauSolverV2)
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
namespace Luau
{
@ -514,10 +512,7 @@ public:
free->scope = replacementForNullScope;
}
else if (auto tt = getMutable<TableType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope;
}
tt->scope = replacementForNullScope;
(*types)[ty] = target;
queue.emplace_back(target);
@ -733,7 +728,7 @@ Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cl
b.deprecatedSuggestion = binding.deprecatedSuggestion;
b.documentationSymbol = binding.documentationSymbol;
b.location = binding.location;
b.typeId = FFlag::LuauDoNotClonePersistentBindings && binding.typeId->persistent ? binding.typeId : cloner.clone(binding.typeId);
b.typeId = binding.typeId->persistent ? binding.typeId : cloner.clone(binding.typeId);
return b;
}

View file

@ -3,7 +3,7 @@
#include "Luau/Constraint.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
namespace Luau
{
@ -17,8 +17,8 @@ Constraint::Constraint(NotNull<Scope> scope, const Location& location, Constrain
struct ReferenceCountInitializer : TypeOnceVisitor
{
DenseHashSet<TypeId>* result;
bool traverseIntoTypeFunctions = true;
explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result)
@ -51,7 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
bool visit(TypeId, const TypeFunctionInstanceType&) override
{
return FFlag::DebugLuauGreedyGeneralization;
return FFlag::LuauEagerGeneralization && traverseIntoTypeFunctions;
}
};
@ -104,10 +104,12 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{
rci.traverse(fchc->argsPack);
}
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::DebugLuauGreedyGeneralization)
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization)
{
rci.traverseIntoTypeFunctions = false;
rci.traverse(fcc->fn);
rci.traverse(fcc->argsPack);
rci.traverseIntoTypeFunctions = true;
}
else if (auto ptc = get<PrimitiveTypeConstraint>(*this))
{
@ -116,12 +118,12 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
else if (auto hpc = get<HasPropConstraint>(*this))
{
rci.traverse(hpc->resultType);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
rci.traverse(hpc->subjectType);
}
else if (auto hic = get<HasIndexerConstraint>(*this))
{
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `indexType`.

View file

@ -33,8 +33,8 @@
LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
@ -42,12 +42,15 @@ LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
namespace Luau
{
@ -251,7 +254,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
rootScope->location = block->location;
module->astScopes[block] = NotNull{scope.get()};
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.emplace_back();
@ -287,7 +290,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
}
);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -306,7 +309,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
}
);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.pop_back();
else
DEPRECATED_interiorTypes.pop_back();
@ -344,13 +347,13 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
// We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes
prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block);
// Pre
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.emplace_back();
visitBlockWithoutChildScope(resumeScope, block);
// Post
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.pop_back();
else
DEPRECATED_interiorTypes.pop_back();
@ -380,12 +383,12 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
freeTypes.insert(ft);
return ft;
@ -402,7 +405,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po
{
FreeTypePack f{scope.get(), polarity};
TypePackId result = arena->addTypePack(TypePackVar{std::move(f)});
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.back().typePacks.push_back(result);
return result;
}
@ -1393,7 +1396,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 = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
bool sigFullyDefined = FFlag::LuauEagerGeneralization ? false : !hasFreeType(sig.signature);
if (sigFullyDefined)
emplaceType<BoundType>(asMutable(functionType), sig.signature);
@ -1453,7 +1456,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 = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
bool sigFullyDefined = FFlag::LuauEagerGeneralization ? false : !hasFreeType(sig.signature);
DefId def = dfg->getDef(function->name);
@ -1767,7 +1770,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
// Place this function as a child of the non-type function scope
scope->children.push_back(NotNull{sig.signatureScope.get()});
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -1785,7 +1788,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
}
);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -1794,7 +1797,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.pop_back();
else
DEPRECATED_interiorTypes.pop_back();
@ -2359,12 +2362,8 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
this,
[checkConstraint, callConstraint](const ConstraintPtr& constraint)
{
if (!(FFlag::DebugLuauGreedyGeneralization && get<PrimitiveTypeConstraint>(*constraint)))
{
constraint->dependencies.emplace_back(checkConstraint);
callConstraint->dependencies.emplace_back(constraint.get());
}
constraint->dependencies.emplace_back(checkConstraint);
callConstraint->dependencies.emplace_back(constraint.get());
}
);
@ -2457,8 +2456,17 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
if (forceSingleton)
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})};
// Consider a table like:
//
// local DICTIONARY = { "aback", "abacus", "abandon", --[[ so on and so forth ]] }
//
// The intent is (probably) not for this to be an array-like table with a massive
// union for the value, but instead a `{ string }`.
if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && largeTableDepth > 0)
return Inference{builtinTypes->stringType};
TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy);
@ -2484,8 +2492,22 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
if (forceSingleton)
return Inference{singletonType};
// Consider a table like:
//
// local FLAGS = {
// Foo = true,
// Bar = false,
// Baz = true,
// -- so on and so forth
// }
//
// The intent is (probably) not for this to be a table where each element
// is potentially `true` or `false` as a singleton, but just `boolean`.
if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && largeTableDepth > 0)
return Inference{builtinTypes->booleanType};
TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy);
@ -2646,7 +2668,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -2664,7 +2686,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
}
);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -3168,7 +3190,10 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
ttv->definitionLocation = expr->location;
ttv->scope = scope.get();
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit))
largeTableDepth++;
if (FFlag::LuauEagerGeneralization)
interiorFreeTypes.back().types.push_back(ty);
else
DEPRECATED_interiorTypes.back().push_back(ty);
@ -3230,7 +3255,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
{
indexKey = *indexKeyLowerBound.begin();
}
else
else
{
indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
unionsToSimplify.push_back(indexKey);
@ -3276,6 +3301,9 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
);
}
if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit))
largeTableDepth--;
return Inference{ty};
}
@ -3425,7 +3453,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
LUAU_ASSERT(nullptr != varargPack);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
// Some of the types in argTypes will eventually be generics, and some
// will not. The ones that are not generic will be pruned when
@ -3490,7 +3518,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
if (expectedType && get<FreeType>(*expectedType))
bindFreeType(*expectedType, actualFunctionType);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
scopeToFunction[signatureScope.get()] = actualFunctionType;
return {
@ -3625,8 +3653,11 @@ TypeId ConstraintGenerator::resolveTableType(const ScopePtr& scope, AstType* ty,
p.readTy = propTy;
break;
case AstTableAccess::Write:
reportError(*prop.accessLocation, GenericError{"write keyword is illegal here"});
p.readTy = propTy;
if (!FFlag::LuauEnableWriteOnlyProperties)
{
reportError(*prop.accessLocation, GenericError{"write keyword is illegal here"});
p.readTy = propTy;
}
p.writeTy = propTy;
break;
default:

View file

@ -33,12 +33,12 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
@ -418,7 +418,7 @@ void ConstraintSolver::run()
}
// Free types that have no constraints at all can be generalized right away.
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
for (TypeId ty : constraintSet.freeTypes)
{
@ -479,7 +479,7 @@ void ConstraintSolver::run()
// expansion types, etc, so we need to follow it.
ty = follow(ty);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
if (seen.contains(ty))
continue;
@ -498,7 +498,7 @@ void ConstraintSolver::run()
if (refCount <= 1)
unblock(ty, Location{});
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
if (FFlag::LuauEagerGeneralization && refCount == 0)
generalizeOneType(ty);
}
}
@ -676,7 +676,7 @@ void ConstraintSolver::initFreeTypeTracking()
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
it->second.insert(c.get());
@ -691,7 +691,7 @@ void ConstraintSolver::initFreeTypeTracking()
}
// Also check flag integrity while we're here
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
LUAU_ASSERT(FFlag::LuauSubtypeGenericsAndNegations);
LUAU_ASSERT(FFlag::LuauNoMoreInjectiveTypeFunctions);
@ -739,7 +739,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity?
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
trackInteriorFreeType(constraint->scope, ty);
return;
@ -806,7 +806,7 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint))
success = tryDispatch(*taec, constraint);
else if (auto fcc = get<FunctionCallConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
success = tryDispatch(*fcc, constraint, force);
else if (auto fcc = get<FunctionCheckConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
else if (auto tcc = get<TableCheckConstraint>(*constraint))
@ -900,7 +900,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
{
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
{
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
ty = follow(ty);
if (auto freeTy = get<FreeType>(ty))
@ -922,7 +922,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
}
}
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
if (constraint->scope->interiorFreeTypePacks)
{
@ -942,7 +942,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
}
}
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
if (c.noGenerics)
{
@ -1158,8 +1158,17 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
{
auto cTarget = follow(c.target);
LUAU_ASSERT(get<PendingExpansionType>(cTarget));
shiftReferences(cTarget, result);
bind(constraint, cTarget, result);
// We do this check here to ensure that we don't bind an alias to itself
if (FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2 && occursCheck(cTarget, result))
{
reportError(OccursCheckFailed{}, constraint->location);
bind(constraint, cTarget, builtinTypes->errorRecoveryType());
}
else
{
shiftReferences(cTarget, result);
bind(constraint, cTarget, result);
}
};
std::optional<TypeFun> tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value)
@ -1239,19 +1248,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// deterministic.
if (TypeId* cached = instantiatedAliases.find(signature))
{
// However, we might now be revealing a malformed mutually recursive
// alias. `instantiatedAliases` can change from underneath us in a
// way that can cause a cached type id to bind to itself if we don't
// do this check.
if (FFlag::LuauGuardAgainstMalformedTypeAliasExpansion && occursCheck(follow(c.target), *cached))
{
reportError(OccursCheckFailed{}, constraint->location);
bindResult(errorRecoveryType());
}
else
{
bindResult(*cached);
}
bindResult(*cached);
return true;
}
@ -1385,13 +1382,13 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
}
}
bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint)
bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint, bool force)
{
TypeId fn = follow(c.fn);
TypePackId argsPack = follow(c.argsPack);
TypePackId result = follow(c.result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
if (isBlocked(fn))
return block(c.fn, constraint);
@ -1521,7 +1518,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
NotNull{&limits},
constraint->location
};
auto [status, overload] = resolver.selectOverload(fn, argsPack);
auto [status, overload] = resolver.selectOverload(fn, argsPack, /*useFreeTypeBounds*/ force);
TypeId overloadToUse = fn;
if (status == OverloadResolver::Analysis::Ok)
overloadToUse = overload;
@ -1531,7 +1528,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy);
@ -1945,7 +1942,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
TypeId upperBound =
arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed});
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound));
@ -1976,7 +1973,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType};
@ -2166,7 +2163,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
{
auto lhsFreeUpperBound = follow(lhsFree->upperBound);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
if (!blocked.empty())
@ -3067,7 +3064,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
{
const TypeId upperBound = follow(ft->upperBound);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
{
@ -3535,7 +3532,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
// Any constraint that might have mutated source may now mutate target
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
auto it = mutatedFreeTypeToConstraint.find(source);
if (it != mutatedFreeTypeToConstraint.end())

View file

@ -18,7 +18,6 @@ LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAGVARIABLE(LuauDfgMatchCGScopes)
LUAU_FASTFLAGVARIABLE(LuauDfgAllowUpdatesInLoops)
namespace Luau
@ -1240,18 +1239,9 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
{
visitExpr(b->left);
if (FFlag::LuauDfgMatchCGScopes)
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr};
}
else
{
visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr};
}
visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
@ -1264,29 +1254,9 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
{
if (FFlag::LuauDfgMatchCGScopes)
{
// In the constraint generator, the condition, consequence, and
// alternative all have distinct scopes.
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(i->condition);
}
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(i->trueExpr);
}
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(i->falseExpr);
}
}
else
{
visitExpr(i->condition);
visitExpr(i->trueExpr);
visitExpr(i->falseExpr);
}
visitExpr(i->condition);
visitExpr(i->trueExpr);
visitExpr(i->falseExpr);
return {defArena->freshCell(Symbol{}, i->location), nullptr};
}

View file

@ -18,7 +18,9 @@
#include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
static std::string wrongNumberOfArgsString(
size_t expectedCount,
@ -446,6 +448,12 @@ struct ErrorConverter
return err;
}
if (FFlag::LuauBetterCannotCallFunctionPrimitive)
{
if (auto primitiveTy = get<PrimitiveType>(follow(e.ty)); primitiveTy && primitiveTy->type == PrimitiveType::Function)
return "The type " + toString(e.ty) + " is not precise enough for us to determine the appropriate result type of this call.";
}
return "Cannot call a value of type " + toString(e.ty);
}
std::string operator()(const Luau::ExtraInformation& e) const
@ -655,7 +663,7 @@ struct ErrorConverter
}
// binary operators
const auto binaryOps = FFlag::DebugLuauGreedyGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps;
const auto binaryOps = FFlag::LuauEagerGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps;
if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end())
{
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
@ -710,7 +718,7 @@ struct ErrorConverter
"'";
}
if ((FFlag::DebugLuauGreedyGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name))
if ((FFlag::LuauEagerGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name))
{
return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";

View file

@ -29,10 +29,6 @@ LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
@ -1261,11 +1257,8 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete(
StringCompletionCallback stringCompletionCB
)
{
if (FFlag::LuauBetterCursorInCommentDetection)
{
if (isWithinComment(context.freshParse.commentLocations, cursorPosition))
return {FragmentAutocompleteStatus::Success, std::nullopt};
}
if (isWithinComment(context.freshParse.commentLocations, cursorPosition))
return {FragmentAutocompleteStatus::Success, std::nullopt};
// TODO: we should calculate fragmentEnd position here, by using context.newAstRoot and cursorPosition
try
{

View file

@ -39,12 +39,13 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts)
namespace Luau
{
@ -408,6 +409,38 @@ void Frontend::parse(const ModuleName& name)
parseGraph(buildQueue, name, false);
}
void Frontend::parseModules(const std::vector<ModuleName>& names)
{
LUAU_TIMETRACE_SCOPE("Frontend::parseModules", "Frontend");
DenseHashSet<Luau::ModuleName> seen{{}};
for (const ModuleName& name : names)
{
if (seen.contains(name))
continue;
if (auto it = sourceNodes.find(name); it != sourceNodes.end() && !it->second->hasDirtySourceModule())
{
seen.insert(name);
continue;
}
std::vector<ModuleName> queue;
parseGraph(
queue,
name,
false,
[&seen](const ModuleName& name)
{
return seen.contains(name);
}
);
seen.insert(name);
}
}
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
{
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend");
@ -1403,13 +1436,13 @@ ModulePtr check(
requireCycles
};
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
// FIXME: Delete this flag when clipping FFlag::LuauEagerGeneralization.
//
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors);
@ -1495,6 +1528,52 @@ ModulePtr check(
for (auto& [name, tf] : result->exportedTypeBindings)
tf.type = builtinTypes->errorRecoveryType();
}
else if (FFlag::LuauNewSolverTypecheckCatchTimeouts)
{
try
{
switch (mode)
{
case Mode::Nonstrict:
Luau::checkNonStrict(
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
iceHandler,
NotNull{&unifierState},
NotNull{&dfg},
NotNull{&limits},
sourceModule,
result.get()
);
break;
case Mode::Definition:
// fallthrough intentional
case Mode::Strict:
Luau::check(
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull{&unifierState},
NotNull{&limits},
logger.get(),
sourceModule,
result.get()
);
break;
case Mode::NoCheck:
break;
};
}
catch (const TimeLimitError&)
{
result->timeout = true;
}
catch (const UserCancelError&)
{
result->cancelled = true;
}
}
else
{
switch (mode)

View file

@ -14,9 +14,9 @@
#include "Luau/Substitution.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization)
namespace Luau
{
@ -469,7 +469,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override
{
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
if (!subsumes(scope, ft.scope))
return true;
@ -520,7 +520,7 @@ struct FreeTypeSearcher : TypeVisitor
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
unsealedTables.insert(ty);
else
{
@ -544,22 +544,57 @@ struct FreeTypeSearcher : TypeVisitor
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
if (FFlag::LuauEnableWriteOnlyProperties)
{
if (prop.isReadOnly())
{
traverse(*prop.readTy);
}
else if (prop.isWriteOnly())
{
Polarity p = polarity;
polarity = Polarity::Negative;
traverse(*prop.writeTy);
polarity = p;
}
else if (prop.isShared())
{
Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = p;
}
else
{
LUAU_ASSERT(prop.isReadWrite() && !prop.isShared());
traverse(*prop.readTy);
Polarity p = polarity;
polarity = Polarity::Negative;
traverse(*prop.writeTy);
polarity = p;
}
}
else
{
LUAU_ASSERT(prop.isShared());
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = p;
Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = p;
}
}
}
if (tt.indexer)
{
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
// {[K]: V} is equivalent to three functions: get, set, and iterate
//
@ -617,7 +652,7 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope))
return true;
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount;
@ -1241,7 +1276,7 @@ GeneralizationResult<TypeId> generalizeType(
if (!hasLowerBound && !hasUpperBound)
{
if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1)))
if (!isWithinFunction || (!FFlag::LuauEagerGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1)))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
@ -1273,7 +1308,7 @@ GeneralizationResult<TypeId> generalizeType(
if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && params.useCount == 1))
else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization && params.useCount == 1))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
@ -1390,7 +1425,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t)
@ -1521,15 +1556,51 @@ struct GenericCounter : TypeVisitor
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
if (FFlag::LuauEnableWriteOnlyProperties)
{
if (prop.isReadOnly())
{
traverse(*prop.readTy);
}
else if (prop.isWriteOnly())
{
Polarity p = polarity;
polarity = Polarity::Negative;
traverse(*prop.writeTy);
polarity = p;
}
else if (prop.isShared())
{
Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = p;
}
else
{
LUAU_ASSERT(prop.isReadWrite() && !prop.isShared());
traverse(*prop.readTy);
Polarity p = polarity;
polarity = Polarity::Negative;
traverse(*prop.writeTy);
polarity = p;
}
}
else
{
LUAU_ASSERT(prop.isShared());
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = previous;
Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = p;
}
}
}
@ -1582,7 +1653,7 @@ void pruneUnnecessaryGenerics(
TypeId ty
)
{
if (!FFlag::DebugLuauGreedyGeneralization)
if (!FFlag::LuauEagerGeneralization)
return;
ty = follow(ty);

View file

@ -5,7 +5,7 @@
#include "Luau/Scope.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
namespace Luau
{
@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor
template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{
if (!FFlag::LuauNonReentrantGeneralization3)
if (!FFlag::LuauEagerGeneralization)
return;
InferPolarity infer{arena, scope};

View file

@ -40,7 +40,7 @@ OverloadResolver::OverloadResolver(
{
}
std::pair<OverloadResolver::Analysis, TypeId> OverloadResolver::selectOverload(TypeId ty, TypePackId argsPack)
std::pair<OverloadResolver::Analysis, TypeId> OverloadResolver::selectOverload(TypeId ty, TypePackId argsPack, bool useFreeTypeBounds)
{
auto tryOne = [&](TypeId f)
{
@ -51,6 +51,9 @@ std::pair<OverloadResolver::Analysis, TypeId> OverloadResolver::selectOverload(T
SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes, scope);
subtyping.variance = variance;
if (!useFreeTypeBounds && !r.assumedConstraints.empty())
return false;
if (r.isSubtype)
return true;
}
@ -461,7 +464,7 @@ static std::optional<TypeId> selectOverload(
{
auto resolver =
std::make_unique<OverloadResolver>(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location);
auto [status, overload] = resolver->selectOverload(fn, argsPack);
auto [status, overload] = resolver->selectOverload(fn, argsPack, /*useFreeTypeBounds*/ false);
if (status == OverloadResolver::Analysis::Ok)
return overload;

View file

@ -21,6 +21,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauEagerGeneralization)
namespace Luau
{
@ -680,6 +681,44 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result.isSubtype = ok;
result.isCacheable = false;
}
else if (auto pair = get2<FreeType, FreeType>(subTy, superTy); FFlag::LuauEagerGeneralization && pair)
{
// Any two free types are potentially subtypes of one another because
// both of them could be narrowed to never.
result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else if (auto superFree = get<FreeType>(superTy); superFree && FFlag::LuauEagerGeneralization)
{
// Given SubTy <: (LB <: SuperTy <: UB)
//
// If SubTy <: UB, then it is possible that SubTy <: SuperTy.
// If SubTy </: UB, then it is definitely the case that SubTy </: SuperTy.
//
// It's always possible for SuperTy's upper bound to later be
// constrained, so this relation may not actually hold.
result = isCovariantWith(env, subTy, superFree->upperBound, scope);
if (result.isSubtype)
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else if (auto subFree = get<FreeType>(subTy); subFree && FFlag::LuauEagerGeneralization)
{
// Given (LB <: SubTy <: UB) <: SuperTy
//
// If UB <: SuperTy, then it is certainly the case that SubTy <: SuperTy.
// If SuperTy <: UB and LB <: SuperTy, then it is possible that UB will later be narrowed such that SubTy <: SuperTy.
// If LB </: SuperTy, then SubTy </: SuperTy
if (isCovariantWith(env, subFree->lowerBound, superTy, scope).isSubtype)
{
result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else
result = {false};
}
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
result = isCovariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated);
else if (auto subNegation = get<NegationType>(subTy))
@ -1411,8 +1450,24 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{
SubtypingResult result{true};
if (subTable->props.empty() && !subTable->indexer && superTable->indexer)
return {false};
if (FFlag::LuauEagerGeneralization)
{
if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer)
{
// While it is certainly the case that {} </: {T}, the story is a little bit different for {| |} <: {T}
// The shape of an unsealed tabel is still in flux, so it is probably the case that the unsealed table
// will later gain the necessary indexer as type inference proceeds.
//
// Unsealed tables are always sealed by the time inference completes, so this should never affect the
// type checking phase.
return {false};
}
}
else
{
if (subTable->props.empty() && !subTable->indexer && superTable->indexer)
return {false};
}
for (const auto& [name, superProp] : superTable->props)
{
@ -1451,6 +1506,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{
if (subTable->indexer)
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope));
else if (FFlag::LuauEagerGeneralization && subTable->state != TableState::Sealed)
{
// As above, we assume that {| |} <: {T} because the unsealed table
// on the left will eventually gain the necessary indexer.
return {true};
}
else
return {false};
}

View file

@ -42,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
*/
LUAU_FASTINTVARIABLE(DebugLuauVerboseTypeNames, 0)
LUAU_FASTFLAGVARIABLE(DebugLuauToStringNoLexicalSort)
LUAU_FASTFLAGVARIABLE(LuauFixEmptyTypePackStringification)
namespace Luau
{
@ -479,6 +480,9 @@ struct TypeStringifier
bool wrap = !singleTp && get<TypePack>(follow(tp));
if (FFlag::LuauFixEmptyTypePackStringification)
wrap &= !isEmpty(tp);
if (wrap)
state.emit("(");
@ -687,14 +691,18 @@ struct TypeStringifier
state.emit("(");
if (state.opts.functionTypeArguments)
if (FFlag::LuauFixEmptyTypePackStringification && isEmpty(ftv.argTypes))
{
// if we've got an empty argument pack, we're done.
}
else if (state.opts.functionTypeArguments)
stringify(ftv.argTypes, ftv.argNames);
else
stringify(ftv.argTypes);
state.emit(") -> ");
bool plural = true;
bool plural = FFlag::LuauFixEmptyTypePackStringification ? !isEmpty(ftv.retTypes) : true;
auto retBegin = begin(ftv.retTypes);
auto retEnd = end(ftv.retTypes);
@ -1235,6 +1243,16 @@ struct TypePackStringifier
return;
}
if (FFlag::LuauFixEmptyTypePackStringification)
{
if (tp.head.empty() && (!tp.tail || isEmpty(*tp.tail)))
{
state.emit("()");
state.unsee(&tp);
return;
}
}
bool first = true;
for (const auto& typeId : tp.head)
@ -1782,17 +1800,34 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
state.emit("): ");
size_t retSize = size(ftv.retTypes);
bool hasTail = !finite(ftv.retTypes);
bool wrap = get<TypePack>(follow(ftv.retTypes)) && (hasTail ? retSize != 0 : retSize != 1);
if (FFlag::LuauFixEmptyTypePackStringification)
{
size_t retSize = size(ftv.retTypes);
bool hasTail = !finite(ftv.retTypes);
bool wrap = get<TypePack>(follow(ftv.retTypes)) && (hasTail ? retSize != 0 : retSize > 1);
if (wrap)
state.emit("(");
if (wrap)
state.emit("(");
tvs.stringify(ftv.retTypes);
tvs.stringify(ftv.retTypes);
if (wrap)
state.emit(")");
if (wrap)
state.emit(")");
}
else
{
size_t retSize = size(ftv.retTypes);
bool hasTail = !finite(ftv.retTypes);
bool wrap = get<TypePack>(follow(ftv.retTypes)) && (hasTail ? retSize != 0 : retSize != 1);
if (wrap)
state.emit("(");
tvs.stringify(ftv.retTypes);
if (wrap)
state.emit(")");
}
return result.name;
}

File diff suppressed because it is too large Load diff

View file

@ -27,7 +27,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauFreeTypesMustHaveBounds)
namespace Luau
{

View file

@ -3,7 +3,6 @@
#include "Luau/TypeArena.h"
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau
{

View file

@ -13,6 +13,7 @@
#include <string>
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
@ -307,7 +308,8 @@ public:
std::optional<AstArgumentName>* arg = &argNames.data[i++];
if (el)
new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), Location()));
new (arg)
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
else
new (arg) std::optional<AstArgumentName>();
}

View file

@ -33,6 +33,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck)
@ -2984,10 +2985,22 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT
Set<std::optional<std::string> > missingKeys{{}};
for (const auto& [name, prop] : expectedTableType->props)
{
LUAU_ASSERT(!prop.isWriteOnly());
auto readTy = *prop.readTy;
if (!isOptional(readTy))
missingKeys.insert(name);
if (FFlag::LuauEnableWriteOnlyProperties)
{
if (prop.readTy)
{
if (!isOptional(*prop.readTy))
missingKeys.insert(name);
}
}
else
{
LUAU_ASSERT(!prop.isWriteOnly());
auto readTy = *prop.readTy;
if (!isOptional(readTy))
missingKeys.insert(name);
}
}
bool isArrayLike = false;
@ -3023,11 +3036,25 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT
}
else
{
// TODO: What do we do for write only props?
LUAU_ASSERT(expectedIt->second.readTy);
// Some property is in the expected type: we can test against the specific type.
module->astExpectedTypes[item.value] = *expectedIt->second.readTy;
isSubtype &= testPotentialLiteralIsSubtype(item.value, *expectedIt->second.readTy);
if (FFlag::LuauEnableWriteOnlyProperties)
{
// If the type has a read type, then we have an expected type for it, otherwise, we actually don't
// care what's assigned to it because the only allowed behavior is writing to that property.
if (expectedIt->second.readTy)
{
module->astExpectedTypes[item.value] = *expectedIt->second.readTy;
isSubtype &= testPotentialLiteralIsSubtype(item.value, *expectedIt->second.readTy);
}
}
else
{
// TODO: What do we do for write only props?
LUAU_ASSERT(expectedIt->second.readTy);
// Some property is in the expected type: we can test against the specific type.
module->astExpectedTypes[item.value] = *expectedIt->second.readTy;
isSubtype &= testPotentialLiteralIsSubtype(item.value, *expectedIt->second.readTy);
}
}
}
else if (item.kind == AstExprTable::Item::List)

View file

@ -47,8 +47,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -283,7 +283,7 @@ struct TypeFunctionReducer
}
else if (is<GenericType>(ty))
{
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
return SkipTestResult::Generic;
else
return SkipTestResult::Irreducible;
@ -305,7 +305,7 @@ struct TypeFunctionReducer
}
else if (is<GenericTypePack>(ty))
{
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
return SkipTestResult::Generic;
else
return SkipTestResult::Irreducible;
@ -1101,7 +1101,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
operandTy = follow(operandTy);
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
@ -1698,7 +1698,7 @@ 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 (FFlag::LuauEagerGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
@ -1745,7 +1745,7 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
@ -2119,7 +2119,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(i)));
const bool targetIsPending = FFlag::DebugLuauGreedyGeneralization
const bool targetIsPending = FFlag::LuauEagerGeneralization
? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy)
: isPending(targetTy, ctx->solver);
@ -2206,7 +2206,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
// Simplification considers free and generic types to be
// 'blocking', but that's not suitable for refine<>.
@ -3335,7 +3335,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
, ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::DebugLuauGreedyGeneralization}
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization}
, singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction}

View file

@ -14,7 +14,6 @@
#include <type_traits>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode)
// Maximum number of steps to follow when traversing a path. May not always
// equate to the number of components in a path, depending on the traversal
@ -157,16 +156,12 @@ Path PathBuilder::build()
PathBuilder& PathBuilder::readProp(std::string name)
{
if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauSolverV2);
components.push_back(Property{std::move(name), true});
return *this;
}
PathBuilder& PathBuilder::writeProp(std::string name)
{
if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauSolverV2);
components.push_back(Property{std::move(name), false});
return *this;
}

View file

@ -12,8 +12,7 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs)
namespace Luau
@ -307,7 +306,7 @@ TypePack extendTypePack(
TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2)
@ -572,8 +571,6 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
void trackInteriorFreeType(Scope* scope, TypeId ty)
{
if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauSolverV2);
for (; scope; scope = scope->parent.get())
{
if (scope->interiorFreeTypes)
@ -591,7 +588,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{
LUAU_ASSERT(tp);
if (!FFlag::LuauNonReentrantGeneralization3)
if (!FFlag::LuauEagerGeneralization)
return;
for (; scope; scope = scope->parent.get())

View file

@ -18,8 +18,8 @@
#include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauEagerGeneralization)
namespace Luau
{
@ -329,12 +329,12 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
for (TypePackId genericPack : subFn->genericPacks)
{
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
genericPack = follow(genericPack);
// TODO: Clip this follow() with DebugLuauGreedyGeneralization
// TODO: Clip this follow() with LuauEagerGeneralization
const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
if (gen)
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
@ -413,16 +413,27 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
{
const Property& superProp = superPropOpt->second;
if (subProp.isReadOnly() && superProp.isReadOnly())
result &= unify(*subProp.readTy, *superPropOpt->second.readTy);
else if (subProp.isReadOnly())
result &= unify(*subProp.readTy, superProp.type());
else if (superProp.isReadOnly())
result &= unify(subProp.type(), *superProp.readTy);
if (FFlag::LuauEnableWriteOnlyProperties)
{
if (subProp.readTy && superProp.readTy)
result &= unify(*subProp.readTy, *superProp.readTy);
if (subProp.writeTy && superProp.writeTy)
result &= unify(*superProp.writeTy, *subProp.writeTy);
}
else
{
result &= unify(subProp.type(), superProp.type());
result &= unify(superProp.type(), subProp.type());
if (subProp.isReadOnly() && superProp.isReadOnly())
result &= unify(*subProp.readTy, *superPropOpt->second.readTy);
else if (subProp.isReadOnly())
result &= unify(*subProp.readTy, superProp.type());
else if (superProp.isReadOnly())
result &= unify(subProp.type(), *superProp.readTy);
else
{
result &= unify(subProp.type(), superProp.type());
result &= unify(superProp.type(), subProp.type());
}
}
}
}
@ -454,7 +465,7 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
{
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
if (FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauEagerGeneralization)
{
// FIXME: We can probably do something more efficient here.
result &= unify(superTable->indexer->indexType, subTable->indexer->indexType);

View file

@ -245,6 +245,8 @@ private:
};
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
// Remove with FFlagLuauStoreCSTData2
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
AstType* parseFunctionTypeTail(

File diff suppressed because it is too large Load diff

View file

@ -121,6 +121,8 @@ void create(lua_State* L, SharedCodeGenContext* codeGenContext);
// Enable or disable native execution according to `enabled` argument
void setNativeExecutionEnabled(lua_State* L, bool enabled);
void disableNativeExecutionForFunction(lua_State* L, const int level) noexcept;
// Given a name, this function must return the index of the type which matches the type array used all CompilationOptions and AssemblyOptions
// If the type is unknown, 0xff has to be returned
using UserdataRemapperCallback = uint8_t(void* context, const char* name, size_t nameLength);

View file

@ -12,6 +12,8 @@
#include "lstate.h"
LUAU_DYNAMIC_FASTFLAG(AddReturnExectargetCheck);
namespace Luau
{
namespace CodeGen
@ -179,6 +181,14 @@ void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers)
build.ldr(x1, mem(rClosure, offsetof(Closure, l.p))); // cl->l.p aka proto
if (DFFlag::AddReturnExectargetCheck)
{
// Get new instruction location
CODEGEN_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8);
build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata)));
build.cbz(x4, helpers.exitContinueVmClearNativeFlag);
}
CODEGEN_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8);
build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, proto->code
@ -188,9 +198,12 @@ void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers)
build.ldr(x2, mem(x2, offsetof(CallInfo, savedpc))); // cip->savedpc
build.sub(x2, x2, rCode);
// Get new instruction location and jump to it
CODEGEN_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8);
build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata)));
if (!DFFlag::AddReturnExectargetCheck)
{
// Get new instruction location and jump to it
CODEGEN_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8);
build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata)));
}
build.ldr(w2, mem(x3, x2));
build.add(x4, x4, x2);
build.br(x4);

View file

@ -671,6 +671,21 @@ void setNativeExecutionEnabled(lua_State* L, bool enabled)
L->global->ecb.enter = enabled ? onEnter : onEnterDisabled;
}
void disableNativeExecutionForFunction(lua_State* L, const int level) noexcept
{
CODEGEN_ASSERT(unsigned(level) < unsigned(L->ci - L->base_ci));
const CallInfo* ci = L->ci - level;
const TValue* o = ci->func;
CODEGEN_ASSERT(ttisfunction(o));
Proto* proto = clvalue(o)->l.p;
CODEGEN_ASSERT(proto);
CODEGEN_ASSERT(proto->codeentry != proto->code);
onDestroyFunction(L, proto);
}
static uint8_t userdataRemapperWrap(lua_State* L, const char* str, size_t len)
{
if (BaseCodeGenContext* codegenCtx = getCodeGenContext(L))

View file

@ -14,6 +14,9 @@
#include <utility>
LUAU_DYNAMIC_FASTFLAGVARIABLE(AddReturnExectargetCheck, false);
namespace Luau
{
namespace CodeGen
@ -458,6 +461,7 @@ void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers)
// Registers alive: r9 (cip)
RegisterX64 proto = rcx;
RegisterX64 execdata = rbx;
RegisterX64 exectarget = r10;
// Change closure
build.mov(rax, qword[cip + offsetof(CallInfo, func)]);
@ -471,6 +475,13 @@ void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers)
build.test(byte[cip + offsetof(CallInfo, flags)], LUA_CALLINFO_NATIVE);
build.jcc(ConditionX64::Zero, helpers.exitContinueVm); // Continue in interpreter if function has no native data
if (DFFlag::AddReturnExectargetCheck)
{
build.mov(exectarget, qword[proto + offsetof(Proto, exectarget)]);
build.test(exectarget, exectarget);
build.jcc(ConditionX64::Zero, helpers.exitContinueVmClearNativeFlag);
}
// Change constants
build.mov(rConstants, qword[proto + offsetof(Proto, k)]);
@ -486,7 +497,15 @@ void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers)
// Get new instruction location and jump to it
build.mov(edx, dword[execdata + rax]);
build.add(rdx, qword[proto + offsetof(Proto, exectarget)]);
if (DFFlag::AddReturnExectargetCheck)
{
build.add(rdx, exectarget);
}
else
{
build.add(rdx, qword[proto + offsetof(Proto, exectarget)]);
}
build.jmp(rdx);
}

View file

@ -15,6 +15,7 @@
#include <stdio.h>
LUAU_FASTFLAG(LuauCurrentLineBounds)
LUAU_FASTFLAGVARIABLE(LuauHeapNameDetails)
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{
@ -728,10 +729,20 @@ static void enumclosure(EnumContext* ctx, Closure* cl)
char buf[LUA_IDSIZE];
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source));
if (FFlag::LuauHeapNameDetails)
{
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
}
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined);
{
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined);
}
enumnode(ctx, obj2gco(cl), sizeLclosure(cl->nupvalues), buf);
}
@ -799,10 +810,21 @@ static void enumthread(EnumContext* ctx, lua_State* th)
char buf[LUA_IDSIZE];
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source));
if (FFlag::LuauHeapNameDetails)
{
if (p->source)
snprintf(buf, sizeof(buf), "thread at %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "thread at %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
}
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined);
{
if (p->source)
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined);
}
enumnode(ctx, obj2gco(th), size, buf);
}
@ -835,7 +857,21 @@ static void enumproto(EnumContext* ctx, Proto* p)
ctx->edge(ctx->context, enumtopointer(obj2gco(p)), p->execdata, "[native]");
}
enumnode(ctx, obj2gco(p), size, p->source ? getstr(p->source) : NULL);
if (FFlag::LuauHeapNameDetails)
{
char buf[LUA_IDSIZE];
if (p->source)
snprintf(buf, sizeof(buf), "proto %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
else
snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
enumnode(ctx, obj2gco(p), size, buf);
}
else
{
enumnode(ctx, obj2gco(p), size, p->source ? getstr(p->source) : NULL);
}
if (p->sizek)
enumedges(ctx, obj2gco(p), p->k, p->sizek, "constants");

View file

@ -19,7 +19,7 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
using namespace Luau;
@ -4454,10 +4454,10 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
auto ac = autocomplete('1');
if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization)
{
// This `if` statement is because `LuauNonReentrantGeneralization3`
// sets some flags
// This `if` statement is because `LuauEagerGeneralization`
// sets some flags
CHECK(ac.entryMap.count("BaseMethod") > 0);
CHECK(ac.entryMap.count("Method") > 0);
}

View file

@ -40,6 +40,7 @@ LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
LUAU_FASTFLAG(LuauYieldableContinuations)
LUAU_FASTFLAG(LuauCurrentLineBounds)
LUAU_FASTFLAG(LuauLoadNoOomThrow)
LUAU_FASTFLAG(LuauHeapNameDetails)
static lua_CompileOptions defaultOptions()
{
@ -2280,6 +2281,8 @@ TEST_CASE("StringConversion")
TEST_CASE("GCDump")
{
ScopedFastFlag luauHeapNameDetails{FFlag::LuauHeapNameDetails, true};
// internal function, declared in lgc.h - not exposed via lua.h
extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
extern void luaC_enumheap(
@ -2320,7 +2323,19 @@ TEST_CASE("GCDump")
lua_State* CL = lua_newthread(L);
lua_pushstring(CL, "local x x = {} local function f() x[1] = math.abs(42) end function foo() coroutine.yield() end foo() return f");
lua_pushstring(CL, R"(
local x
x = {}
local function f()
x[1] = math.abs(42)
end
function foo()
coroutine.yield()
end
foo()
return f
)");
lua_pushstring(CL, "=GCDump");
lua_loadstring(CL);
lua_resume(CL, nullptr, 0);
@ -2365,8 +2380,19 @@ TEST_CASE("GCDump")
{
EnumContext& context = *(EnumContext*)ctx;
if (tt == LUA_TUSERDATA)
CHECK(strcmp(name, "u42") == 0);
if (name)
{
std::string_view sv{name};
if (tt == LUA_TUSERDATA)
CHECK(sv == "u42");
else if (tt == LUA_TPROTO)
CHECK((sv == "proto unnamed:1 =GCDump" || sv == "proto foo:7 =GCDump" || sv == "proto f:4 =GCDump"));
else if (tt == LUA_TFUNCTION)
CHECK((sv == "test" || sv == "unnamed:1 =GCDump" || sv == "foo:7 =GCDump" || sv == "f:4 =GCDump"));
else if (tt == LUA_TTHREAD)
CHECK(sv == "thread at unnamed:1 =GCDump");
}
context.nodes[gco] = {gco, tt, memcat, size, name ? name : ""};
},

View file

@ -234,8 +234,6 @@ TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_property_wrong")
TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_cyclic_tables_are_not_different")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
local function id<a>(x: a): a
return x
@ -1473,8 +1471,6 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "equal_metatable")
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_normal")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
local metaFoo = {
metaBar = 5

View file

@ -26,13 +26,6 @@ using namespace Luau;
LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAG(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAG(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauBetterScopeSelection)
LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
@ -67,14 +60,8 @@ struct FragmentAutocompleteFixtureImpl : BaseType
{
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true};
ScopedFastFlag luauClonedTableAndFunctionTypesMustHaveScopes{FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes, true};
ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true};
ScopedFastFlag luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true};
ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true};
ScopedFastFlag luauBetterScopeSelection{FFlag::LuauBetterScopeSelection, true};
ScopedFastFlag luauBlockDiffFragmentSelection{FFlag::LuauBlockDiffFragmentSelection, true};
ScopedFastFlag luauAutocompleteUsesModuleForTypeCompatibility{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true};
ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true};
ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true};
ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true};
@ -2526,7 +2513,6 @@ l
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "do_not_recommend_results_in_multiline_comment")
{
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
std::string source = R"(--[[
)";
std::string dest = R"(--[[
@ -2547,7 +2533,6 @@ a
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_simple")
{
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
const std::string source = R"(
-- sel
-- retur
@ -2583,7 +2568,6 @@ bar
baz
]]
)";
ScopedFastFlag sff{FFlag::LuauBetterCursorInCommentDetection, true};
autocompleteFragmentInBothSolvers(
source,
source,
@ -2628,7 +2612,6 @@ baz
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments")
{
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
const std::string source = R"(
-- sel
-- retur
@ -2708,7 +2691,6 @@ if x == 5
local x = 5
if x == 5 then -- a comment
)";
ScopedFastFlag sff = {FFlag::LuauBetterCursorInCommentDetection, true};
autocompleteFragmentInBothSolvers(
source,
updated,
@ -2964,7 +2946,6 @@ return module
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "ice_caused_by_mixed_mode_use")
{
ScopedFastFlag sff{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true};
const std::string source =
std::string("--[[\n\tPackage link auto-generated by Rotriever\n]]\nlocal PackageIndex = script.Parent._Index\n\nlocal Package = ") +
"require(PackageIndex[\"ReactOtter\"][\"ReactOtter\"])\n\nexport type Goal = Package.Goal\nexport type SpringOptions " +

View file

@ -15,7 +15,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
@ -226,7 +226,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a")
TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})")
{
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true};
TableType tt;
tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType};
@ -260,7 +260,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?")
TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()")
{
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true};
auto [aTy, aFree] = freshType();
auto [bTy, bFree] = freshType();

View file

@ -8,13 +8,13 @@
using namespace Luau;
LUAU_FASTFLAG(LuauNonReentrantGeneralization3);
LUAU_FASTFLAG(LuauEagerGeneralization);
TEST_SUITE_BEGIN("InferPolarity");
TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }")
{
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true};
TypeArena arena;
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack);

View file

@ -10,7 +10,7 @@
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LintRedundantNativeAttribute);
LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauNonReentrantGeneralization3);
LUAU_FASTFLAG(LuauEagerGeneralization);
using namespace Luau;
@ -1942,7 +1942,7 @@ print(foo:bar(2.0))
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
{
// FIXME: For now this flag causes a stack overflow on Windows.
ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization3, false};
ScopedFastFlag _{FFlag::LuauEagerGeneralization, false};
LintResult result = lint(R"(
local t = {}

View file

@ -280,8 +280,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
TypeArena arena;
TypeId freeTy = freshType(NotNull{&arena}, builtinTypes, nullptr);
TypePackVar freeTp(FreeTypePack{TypeLevel{}});

View file

@ -14,8 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1177,57 +1176,6 @@ end
)");
}
#if 0
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_limit_function_intersection_complexity")
{
ScopedFastInt luauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 80};
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
ScopedFastFlag _[] = {
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::DebugLuauGreedyGeneralization, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}
};
CheckResult result = check(R"(
function _(_).readu32(l0)
return ({[_(_(_))]=_,[_(if _ then _)]=_,n0=_,})[_],nil
end
_(_)[_(n32)] %= _(_(_))
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures")
{
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
ScopedFastFlag _[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::LuauSimplifyOutOfLine, true},
{FFlag::LuauNonReentrantGeneralization3, false},
{FFlag::DebugLuauGreedyGeneralization, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
};
CheckResult result = check(R"(
function _(_,"").readu32(l0)
return ({[_(_(_))]=_,[_(if _ then _,_())]=_,[""]=_,})[_],nil
end
_().readu32 %= _(_(_(_),_))
)");
LUAU_REQUIRE_ERRORS(result);
}
#endif
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_flatten_type_pack_cycle")
{
ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}};
@ -1258,7 +1206,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::DebugLuauGreedyGeneralization, true}
{FFlag::LuauEagerGeneralization, true}
};
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};

View file

@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauParseStringIndexer)
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix)
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
@ -1260,8 +1261,6 @@ until false
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_local_function")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
try
{
parse(R"(-- i am line 1
@ -1295,8 +1294,6 @@ end
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_failsafe_earlier")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
try
{
parse(R"(-- i am line 1
@ -2902,6 +2899,8 @@ TEST_CASE_FIXTURE(Fixture, "function_start_locations_are_before_attributes")
TEST_CASE_FIXTURE(Fixture, "for_loop_with_single_var_has_comma_positions_of_size_zero")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
ParseOptions parseOptions;
parseOptions.storeCstData = true;
@ -3364,8 +3363,6 @@ TEST_CASE_FIXTURE(Fixture, "AstName_comparison")
TEST_CASE_FIXTURE(Fixture, "generic_type_list_recovery")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
try
{
parse(R"(

View file

@ -17,6 +17,7 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauEagerGeneralization)
using namespace Luau;
@ -1647,4 +1648,19 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
CHECK(result.isSubtype);
}
TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes")
{
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true};
TypeId argTy = arena.freshType(builtinTypes, moduleScope.get());
FreeType* freeArg = getMutable<FreeType>(argTy);
REQUIRE(freeArg);
freeArg->lowerBound = arena.addType(SingletonType{StringSingleton{"five"}});
freeArg->upperBound = builtinTypes->stringType;
SubtypingResult result = isSubtype(builtinTypes->stringType, argTy);
CHECK(result.isSubtype);
REQUIRE(1 == result.assumedConstraints.size());
}
TEST_SUITE_END();

View file

@ -322,8 +322,6 @@ n3 [label="TableType 3"];
TEST_CASE_FIXTURE(Fixture, "free")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
Type type{TypeVariant{FreeType{TypeLevel{0, 0}, builtinTypes->neverType, builtinTypes->unknownType}}};
ToDotOptions opts;
opts.showPointers = false;

View file

@ -6,6 +6,7 @@
#include "Fixture.h"
#include "Luau/TypeChecker2.h"
#include "Luau/TypePack.h"
#include "ScopedFlags.h"
#include "doctest.h"
@ -14,6 +15,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAG(LuauFixEmptyTypePackStringification)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
TEST_SUITE_BEGIN("ToString");
@ -492,6 +494,17 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_array_uses_array_syntax")
CHECK_EQ("{string}", toString(Type{ttv}));
}
TEST_CASE_FIXTURE(Fixture, "the_empty_type_pack_should_be_parenthesized")
{
ScopedFastFlag sff{FFlag::LuauFixEmptyTypePackStringification, true};
TypePackVar emptyTypePack{TypePack{}};
CHECK_EQ(toString(&emptyTypePack), "()");
auto unitToUnit = Type{FunctionType{&emptyTypePack, &emptyTypePack}};
CHECK_EQ(toString(&unitToUnit), "() -> ()");
}
TEST_CASE_FIXTURE(Fixture, "generic_packs_are_stringified_differently_from_generic_types")
{

View file

@ -12,8 +12,10 @@
using namespace Luau;
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
LUAU_FASTFLAG(LuauCSTForReturnTypeFunctionTail)
TEST_SUITE_BEGIN("TranspilerTests");
@ -47,6 +49,7 @@ TEST_CASE("string_literals_containing_utf8")
TEST_CASE("if_stmt_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( if This then Once() end)";
CHECK_EQ(one, transpile(one).code);
@ -95,15 +98,31 @@ TEST_CASE("elseif_chains_indent_sensibly")
TEST_CASE("strips_type_annotations")
{
const std::string code = R"( local s: string= 'hello there' )";
const std::string expected = R"( local s = 'hello there' )";
CHECK_EQ(expected, transpile(code).code);
if (FFlag::LuauStoreCSTData2)
{
const std::string expected = R"( local s = 'hello there' )";
CHECK_EQ(expected, transpile(code).code);
}
else
{
const std::string expected = R"( local s = 'hello there' )";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("strips_type_assertion_expressions")
{
const std::string code = R"( local s= some_function() :: any+ something_else() :: number )";
const std::string expected = R"( local s= some_function() + something_else() )";
CHECK_EQ(expected, transpile(code).code);
if (FFlag::LuauStoreCSTData2)
{
const std::string expected = R"( local s= some_function() + something_else() )";
CHECK_EQ(expected, transpile(code).code);
}
else
{
const std::string expected = R"( local s= some_function() + something_else() )";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("function_taking_ellipsis")
@ -130,6 +149,7 @@ TEST_CASE("for_loop")
TEST_CASE("for_loop_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( for index = 1, 10 do call(index) end )";
CHECK_EQ(one, transpile(one).code);
@ -154,6 +174,7 @@ TEST_CASE("for_in_loop")
TEST_CASE("for_in_loop_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( for k, v in ipairs(x) do end )";
CHECK_EQ(one, transpile(one).code);
@ -172,6 +193,7 @@ TEST_CASE("for_in_loop_spaces_around_tokens")
TEST_CASE("for_in_single_variable")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( for key in pairs(x) do end )";
CHECK_EQ(one, transpile(one).code);
}
@ -184,6 +206,7 @@ TEST_CASE("while_loop")
TEST_CASE("while_loop_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( while f(x) do print() end )";
CHECK_EQ(one, transpile(one).code);
@ -205,6 +228,7 @@ TEST_CASE("repeat_until_loop")
TEST_CASE("repeat_until_loop_condition_on_new_line")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
repeat
print()
@ -236,6 +260,7 @@ TEST_CASE("local_assignment")
TEST_CASE("local_assignment_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( local x = 1 )";
CHECK_EQ(one, transpile(one).code);
@ -269,6 +294,7 @@ TEST_CASE("local_function")
TEST_CASE("local_function_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( local function p(o, m, ...) end )";
CHECK_EQ(one, transpile(one).code);
@ -287,6 +313,7 @@ TEST_CASE("function")
TEST_CASE("function_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string two = R"( function p(o, m, ...) end )";
CHECK_EQ(two, transpile(two).code);
@ -315,6 +342,7 @@ TEST_CASE("function_spaces_around_tokens")
TEST_CASE("function_with_types_spaces_around_tokens")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
};
@ -375,7 +403,7 @@ TEST_CASE("function_with_types_spaces_around_tokens")
TEST_CASE("returns_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( return 1 )";
CHECK_EQ(one, transpile(one).code);
@ -388,7 +416,7 @@ TEST_CASE("returns_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -437,7 +465,7 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -498,7 +526,7 @@ TEST_CASE("table_literal_closing_brace_at_correct_position")
TEST_CASE("table_literal_with_semicolon_separators")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
local t = { x = 1; y = 2 }
)";
@ -508,7 +536,7 @@ TEST_CASE("table_literal_with_semicolon_separators")
TEST_CASE("table_literal_with_trailing_separators")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
local t = { x = 1, y = 2, }
)";
@ -518,7 +546,7 @@ TEST_CASE("table_literal_with_trailing_separators")
TEST_CASE("table_literal_with_spaces_around_separator")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
local t = { x = 1 , y = 2 }
)";
@ -528,7 +556,7 @@ TEST_CASE("table_literal_with_spaces_around_separator")
TEST_CASE("table_literal_with_spaces_around_equals")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
local t = { x = 1 }
)";
@ -538,7 +566,7 @@ TEST_CASE("table_literal_with_spaces_around_equals")
TEST_CASE("table_literal_multiline_with_indexers")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
local t = {
["my first value"] = "x";
@ -566,7 +594,15 @@ TEST_CASE("spaces_between_keywords_even_if_it_pushes_the_line_estimation_off")
// Luau::Parser doesn't exactly preserve the string representation of numbers in Lua, so we can find ourselves
// falling out of sync with the original code. We need to push keywords out so that there's at least one space between them.
const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )";
CHECK_EQ(code, transpile(code).code);
if (FFlag::LuauStoreCSTData2)
{
CHECK_EQ(code, transpile(code).code);
}
else
{
const std::string expected = R"( if math.abs(raySlope) < 0.01 then return 0 end)";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("numbers")
@ -578,26 +614,34 @@ TEST_CASE("numbers")
TEST_CASE("infinity")
{
const std::string code = R"( local a = 1e500 local b = 1e400 )";
CHECK_EQ(code, transpile(code).code);
if (FFlag::LuauStoreCSTData2)
{
CHECK_EQ(code, transpile(code).code);
}
else
{
const std::string expected = R"( local a = 1e500 local b = 1e500 )";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("numbers_with_separators")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = 123_456_789 )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("hexadecimal_numbers")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = 0xFFFF )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("binary_numbers")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = 0b0101 )";
CHECK_EQ(code, transpile(code).code);
}
@ -610,28 +654,28 @@ TEST_CASE("single_quoted_strings")
TEST_CASE("double_quoted_strings")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = "hello world" )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("simple_interp_string")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = `hello world` )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("raw_strings")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = [[ hello world ]] )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("raw_strings_with_blocks")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = [==[ hello world ]==] )";
CHECK_EQ(code, transpile(code).code);
}
@ -650,7 +694,7 @@ TEST_CASE("escaped_strings_2")
TEST_CASE("escaped_strings_newline")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
print("foo \
bar")
@ -660,14 +704,14 @@ TEST_CASE("escaped_strings_newline")
TEST_CASE("escaped_strings_raw")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("position_correctly_updated_when_writing_multiline_string")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
call([[
testing
@ -713,56 +757,56 @@ TEST_CASE("function_call_parentheses_multiple_args_no_space")
TEST_CASE("function_call_parentheses_multiple_args_space_before_commas")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call(arg1 ,arg3 ,arg3) )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("function_call_spaces_before_parentheses")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call () )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("function_call_spaces_within_parentheses")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call( ) )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("function_call_string_double_quotes")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call "string" )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("function_call_string_single_quotes")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call 'string' )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("function_call_string_no_space")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call'string' )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("function_call_table_literal")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call { x = 1 } )";
CHECK_EQ(code, transpile(code).code);
}
TEST_CASE("function_call_table_literal_no_space")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call{x=1} )";
CHECK_EQ(code, transpile(code).code);
}
@ -807,7 +851,7 @@ TEST_CASE("emit_a_do_block_in_cases_of_potentially_ambiguous_syntax")
TEST_CASE_FIXTURE(Fixture, "parentheses_multiline")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
local test = (
x
@ -819,6 +863,9 @@ local test = (
TEST_CASE_FIXTURE(Fixture, "stmt_semicolon")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local test = 1; )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -828,6 +875,7 @@ TEST_CASE_FIXTURE(Fixture, "stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon")
{
ScopedFastFlag sff{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
do
return;
@ -838,6 +886,9 @@ TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon")
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"(
if init then
x = string.sub(x, utf8.offset(x, init));
@ -848,6 +899,9 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"(
if (t < 1) then return c/2*t*t + b end;
)";
@ -856,6 +910,9 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"(
for i,v in ... do
end;
@ -865,6 +922,9 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"(
while true do
end;
@ -874,6 +934,9 @@ TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"(
function foo()
end;
@ -951,7 +1014,16 @@ TEST_CASE("a_table_key_can_be_the_empty_string")
TEST_CASE("always_emit_a_space_after_local_keyword")
{
std::string code = "do local aZZZZ = Workspace.P1.Shape local bZZZZ = Enum.PartType.Cylinder end";
CHECK_EQ(code, transpile(code).code);
if (FFlag::LuauStoreCSTData2)
{
CHECK_EQ(code, transpile(code).code);
}
else
{
std::string expected = "do local aZZZZ = Workspace.P1 .Shape local bZZZZ= Enum.PartType.Cylinder end";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE_FIXTURE(Fixture, "types_should_not_be_considered_cyclic_if_they_are_not_recursive")
@ -988,13 +1060,23 @@ TEST_CASE_FIXTURE(Fixture, "type_lists_should_be_emitted_correctly")
end
)";
std::string expected = R"(
std::string expected = FFlag::LuauStoreCSTData2 ? R"(
local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
end
local b:(...string)->(...number)=function(...:string): ...number
end
local c:()->()=function(): ()
end
)"
: R"(
local a:(string,number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
end
local b:(...string)->(...number)=function(...:string): ...number
end
local c:()->()=function(): ()
end
)";
@ -1034,7 +1116,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_assertion")
TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "local a = 5 :: number";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1051,7 +1133,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else")
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5";
CHECK_EQ(code, transpile(code).code);
@ -1059,7 +1141,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
local x = if yes
then nil
@ -1075,7 +1157,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "local a = if 1 then 2 else 3";
CHECK_EQ(code, transpile(code).code);
@ -1112,7 +1194,7 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
return
if a then "was a" else
@ -1140,7 +1222,7 @@ local a: Import.Type
TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( local _: Foo.Type )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1169,6 +1251,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
};
std::string code = R"( local _: Type )";
@ -1190,6 +1273,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
};
std::string code = R"( for i: number = 1, 10 do end )";
@ -1230,7 +1314,7 @@ local b: Packed<(number, string)>
TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type _ = Packed< T...> )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1288,7 +1372,11 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_2")
TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
{
std::string code = "local a: nil | (string & number)";
CHECK_EQ(code, transpile(code, {}, true).code);
if (FFlag::LuauStoreCSTData2)
CHECK_EQ(code, transpile(code, {}, true).code);
else
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested")
@ -1308,6 +1396,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested_2")
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = "type FnB<U...> = () -> U... & T";
@ -1317,6 +1406,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function")
TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: | string | number";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1326,6 +1418,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string | number";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1335,6 +1430,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: & string & number";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1344,6 +1442,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand")
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string & number";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1353,6 +1454,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string | (Foo & Bar)";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1377,6 +1481,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string | nil";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1408,7 +1515,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_varargs")
TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = "local _ = a.name";
CHECK_EQ(one, transpile(one, {}, true).code);
@ -1421,7 +1528,7 @@ TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "sparkles.Color = Color3.new()";
CHECK_EQ(code, transpile(code, {}, true).code);
}
@ -1435,7 +1542,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_index_expr")
TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = "local _ = a[2]";
CHECK_EQ(one, transpile(one, {}, true).code);
@ -1479,7 +1586,7 @@ local _ = # e
TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
local _ = 1+1
local _ = 1 +1
@ -1521,7 +1628,7 @@ a ..= ' - result'
TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = R"( a += 1 )";
CHECK_EQ(one, transpile(one, {}, true).code);
@ -1538,7 +1645,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_assign_multiple")
TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = "a = 1";
CHECK_EQ(one, transpile(one).code);
@ -1574,7 +1681,11 @@ local f: <T,S...>(T, S...)->(number) = foo
TEST_CASE_FIXTURE(Fixture, "transpile_union_reverse")
{
std::string code = "local a: nil | number";
CHECK_EQ(code, transpile(code, {}, true).code);
if (FFlag::LuauStoreCSTData2)
CHECK_EQ(code, transpile(code, {}, true).code);
else
CHECK_EQ("local a: number?", transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple")
@ -1689,6 +1800,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
{
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local _ = `hello {name}` )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1696,6 +1810,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
{
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local _ = `hello {
name
}!` )";
@ -1705,6 +1822,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
{
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"(
error(
`a {b} c`
@ -1716,6 +1836,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( local _ = `hello \
world!` )";
@ -1724,6 +1845,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
{
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1738,6 +1862,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions")
TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type function foo() end )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1753,6 +1878,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type X = typeof(x) )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1777,14 +1903,14 @@ TEST_CASE("transpile_single_quoted_string_types")
TEST_CASE("transpile_double_quoted_string_types")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( type a = "hello world" )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE("transpile_raw_string_types")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type a = [[ hello world ]] )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1794,14 +1920,14 @@ TEST_CASE("transpile_raw_string_types")
TEST_CASE("transpile_escaped_string_types")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( type a = "\\b\\t\\n\\\\" )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE("transpile_type_table_semicolon_separators")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"(
type Foo = {
bar: number;
@ -1813,7 +1939,7 @@ TEST_CASE("transpile_type_table_semicolon_separators")
TEST_CASE("transpile_type_table_access_modifiers")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
type Foo = {
read bar: number,
@ -1834,7 +1960,7 @@ TEST_CASE("transpile_type_table_access_modifiers")
TEST_CASE("transpile_type_table_spaces_between_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type Foo = { bar: number, } )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1877,7 +2003,7 @@ TEST_CASE("transpile_type_table_spaces_between_tokens")
TEST_CASE("transpile_type_table_preserve_original_indexer_style")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
type Foo = {
[number]: string
@ -1893,7 +2019,7 @@ TEST_CASE("transpile_type_table_preserve_original_indexer_style")
TEST_CASE("transpile_type_table_preserve_indexer_location")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
type Foo = {
[number]: string,
@ -1922,7 +2048,7 @@ TEST_CASE("transpile_type_table_preserve_indexer_location")
TEST_CASE("transpile_type_table_preserve_property_definition_style")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
type Foo = {
["$$typeof1"]: string,
@ -1934,7 +2060,7 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style")
TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
type Foo = {
[ "$$typeof1"]: string,
@ -1946,6 +2072,9 @@ TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens")
TEST_CASE("transpile_types_preserve_parentheses_style")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( type Foo = number )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -1985,6 +2114,7 @@ end
TEST_CASE("transpile_type_function_unnamed_arguments")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = () -> () )";
@ -2021,6 +2151,7 @@ TEST_CASE("transpile_type_function_unnamed_arguments")
TEST_CASE("transpile_type_function_named_arguments")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = (x: string) -> () )";
@ -2051,6 +2182,7 @@ TEST_CASE("transpile_type_function_named_arguments")
TEST_CASE("transpile_type_function_generics")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = <X, Y, Z...>() -> () )";
@ -2087,6 +2219,7 @@ TEST_CASE("transpile_type_function_generics")
TEST_CASE("transpile_type_function_return_types")
{
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = () -> () )";
@ -2132,6 +2265,23 @@ TEST_CASE("transpile_type_function_return_types")
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE("transpile_chained_function_types")
{
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
{FFlag::LuauCSTForReturnTypeFunctionTail, true},
};
std::string code = R"( type Foo = () -> () -> () )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = () -> () -> () )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = () -> () -> () )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE("fuzzer_nil_optional")
{
const std::string code = R"( local x: nil? )";
@ -2140,7 +2290,7 @@ TEST_CASE("fuzzer_nil_optional")
TEST_CASE("transpile_function_attributes")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
@native
function foo()

View file

@ -14,7 +14,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
@ -1725,7 +1725,7 @@ struct TFFixture
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
const ScopedFastFlag sff[1] = {
{FFlag::DebugLuauGreedyGeneralization, true},
{FFlag::LuauEagerGeneralization, true},
};
BuiltinTypeFunctions builtinTypeFunctions;

View file

@ -9,6 +9,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -1986,13 +1987,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization)
{
// FIXME: CLI-151985
// This test breaks because we can't see that eq<type?, b> is already fully reduced.
return;
}
CheckResult result = check(R"(
type function compare(arg)
return types.singleton(types.singleton(false) == arg)
end
local function ok(idx: compare<false>): true return idx end
local function ok(idx: compare<true>): false return idx end
local function ok1(idx: compare<false>): true return idx end
local function ok2(idx: compare<true>): false return idx end
)");
LUAU_REQUIRE_NO_ERRORS(result);
@ -2002,6 +2010,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization)
{
// FIXME: CLI-151985
// This test breaks because we can't see that eq<type?, b> is already fully reduced.
return;
}
CheckResult result = check(R"(
type function compare(arg)
return types.singleton(types.singleton("") == arg)

View file

@ -12,7 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAG(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
@ -355,8 +355,6 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
// Check that recursive intersection type doesn't generate an OOM
TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
function _(l0:(t0)&((t0)&(((t0)&((t0)->()))->(typeof(_),typeof(# _)))),l39,...):any
end
@ -970,9 +968,6 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_locations")
*/
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_lose_track_of_PendingExpansionTypes_after_substitution")
{
// CLI-114134 - We need egraphs to properly simplify these types.
DOES_NOT_PASS_NEW_SOLVER_GUARD();
fileResolver.source["game/ReactCurrentDispatcher"] = R"(
export type BasicStateAction<S> = ((S) -> S) | S
export type Dispatch<A> = (A) -> ()
@ -1258,7 +1253,7 @@ TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_mod
TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases")
{
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion, true};
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2, true};
// This used to crash under the new solver: we would like this to continue
// to not crash.
@ -1298,5 +1293,16 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_duplicate_with_typeof")
CHECK(get<DuplicateTypeDefinition>(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "fuzzer_more_cursed_aliases")
{
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2, true};
LUAU_REQUIRE_ERRORS(check(R"(
export type t138 = t0<t138>
export type t0<t0,t10,t10,t109> = t0
)"));
}
TEST_SUITE_END();

View file

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

View file

@ -129,8 +129,6 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "we_can_infer_that_a_parameter_must_be_a_pa
TEST_CASE_FIXTURE(ExternTypeFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
function makeClone(o)
return BaseClass.Clone(o)
@ -472,9 +470,6 @@ Type 'number' could not be converted into 'string')";
TEST_CASE_FIXTURE(ExternTypeFixture, "class_type_mismatch_with_name_conflict")
{
// CLI-116433
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
local i = ChildClass.New()
type ChildClass = { x: number }

View file

@ -22,7 +22,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1038,9 +1038,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "calling_function_with_anytypepack_doesnt_lea
TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
{
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
--!strict
@ -1472,9 +1469,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "variadic_any_is_compatible_with_a_generic_Ty
TEST_CASE_FIXTURE(Fixture, "infer_anonymous_function_arguments_outside_call")
{
// FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
type Table = { x: number, y: number }
local f: (Table) -> number = function(t) return t.x + t.y end
@ -1694,7 +1688,7 @@ t.f = function(x)
end
)");
if (FFlag::DebugLuauGreedyGeneralization && FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
{
// FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(3, result);
@ -1779,7 +1773,7 @@ t.f = function(x)
end
)");
if (FFlag::DebugLuauGreedyGeneralization && FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
{
// FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(2, result);
@ -1981,10 +1975,15 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_
CHECK_EQ("<a>(a) -> a", toString(requireType("f")));
if (FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
{
LUAU_CHECK_NO_ERRORS(result);
CHECK("<a>({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g")));
}
else if (FFlag::LuauSolverV2)
{
// FIXME CLI-143852: Depends on interleaving generalization and type function reduction.
LUAU_REQUIRE_ERRORS(result);
LUAU_CHECK_ERRORS(result);
CHECK_EQ("({ read p: unknown }) -> (*error-type* | ~(false?))?", toString(requireType("g")));
}
else
@ -2941,7 +2940,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
{
// The new solver should ideally be able to do better here, but this is no worse than the old solver.
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
auto tm1 = get<TypeMismatch>(result.errors[0]);

View file

@ -334,9 +334,6 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
{
// CLI-116476 Subtyping between type alias and an equivalent but not named type isn't working.
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
type X = { x: (number) -> number }
type Y = { y: (string) -> string }

View file

@ -325,9 +325,6 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_non_function")
{
// We report a spuriouus duplicate error here.
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
local bad_iter = 5
@ -1130,7 +1127,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_on_never_gives_never")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2)
CHECK("nil" == toString(requireType("ans")));
CHECK("nil" == toString(requireType("ans")));
else
CHECK(toString(requireType("ans")) == "never");
}
@ -1404,7 +1401,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1413")
local bar = foo - foo + foo
foo = bar
end
end
end
)"));
}

View file

@ -13,7 +13,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
@ -787,7 +787,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23})));
}

View file

@ -17,7 +17,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
TEST_SUITE_BEGIN("TypeInferOperators");
@ -29,7 +29,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
// FIXME: Regression
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
@ -51,7 +51,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
// FIXME: Regression.
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
@ -72,7 +72,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
// FIXME: Regression
CHECK("(string & ~(false?)) | string" == toString(requireType("s")));
@ -634,7 +634,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
local a = -foo
)");
if (FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("string" == toString(requireType("a")));
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
// FIXME: This error is a bit weird.
CHECK("({ @metatable { __unm: (boolean) -> string }, { value: number } }) -> string" == toString(tm->wantedType, {true}));
CHECK("(boolean) -> string" == toString(tm->givenType));
}
else if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);

View file

@ -13,6 +13,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTINT(LuauTypeInferIterationLimit)
@ -48,7 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end
)";
const std::string expected = R"(
const std::string expected = FFlag::LuauStoreCSTData2 ? R"(
function f(a:{fn:()->(a,b...)}): ()
if type(a) == 'boolean' then
local a1:boolean=a
@ -56,9 +57,18 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
local a2:{fn:()->(a,b...)}=a
end
end
)"
: R"(
function f(a:{fn:()->(a,b...)}): ()
if type(a) == 'boolean'then
local a1:boolean=a
elseif a.fn()then
local a2:{fn:()->(a,b...)}=a
end
end
)";
const std::string expectedWithNewSolver = R"(
const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData2 ? R"(
function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a
@ -66,9 +76,18 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
local a2:{fn:()->(unknown,...unknown)}&(class|function|nil|number|string|thread|buffer|table)=a
end
end
)"
: R"(
function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean'then
local a1:{fn:()->(unknown,...unknown)}&boolean=a
elseif a.fn()then
local a2:{fn:()->(unknown,...unknown)}&(class|function|nil|number|string|thread|buffer|table)=a
end
end
)";
const std::string expectedWithEqSat = R"(
const std::string expectedWithEqSat = FFlag::LuauStoreCSTData2 ? R"(
function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a
@ -76,6 +95,15 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
local a2:{fn:()->(unknown,...unknown)}&negate<boolean>=a
end
end
)"
: R"(
function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean'then
local a1:{fn:()->(unknown,...unknown)}&boolean=a
elseif a.fn()then
local a2:{fn:()->(unknown,...unknown)}&negate<boolean>=a
end
end
)";
if (FFlag::LuauSolverV2 && !FFlag::DebugLuauEqSatSimplification)

View file

@ -10,11 +10,13 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauSimplificationTableExternType)
LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
using namespace Luau;
@ -108,6 +110,13 @@ struct RefinementExternTypeFixture : BuiltinsFixture
{"IsA", Property{isA}},
};
TypeId scriptConnection = arena.addType(ExternType("ExternScriptConnection", {}, inst, std::nullopt, {}, nullptr, "Test", {}));
TypePackId disconnectArgs = arena.addTypePack({scriptConnection});
TypeId disconnect = arena.addType(FunctionType{disconnectArgs, builtinTypes->emptyTypePack});
getMutable<ExternType>(scriptConnection)->props = {
{"Disconnect", Property{disconnect}},
};
TypeId folder = frontend.globals.globalTypes.addType(ExternType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test", {}});
TypeId part = frontend.globals.globalTypes.addType(ExternType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test", {}});
getMutable<ExternType>(part)->props = {
@ -123,6 +132,7 @@ struct RefinementExternTypeFixture : BuiltinsFixture
frontend.globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3};
frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};
frontend.globals.globalScope->exportedTypeBindings["ExternScriptConnection"] = TypeFun{{}, scriptConnection};
frontend.globals.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder};
frontend.globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part};
frontend.globals.globalScope->exportedTypeBindings["WeldConstraint"] = TypeFun{{}, weldConstraint};
@ -760,7 +770,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
{
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
@ -2504,7 +2514,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
end
)"));
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24})));
else
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24})));
@ -2648,5 +2658,77 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451")
)"));
}
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type Disconnectable = {
Disconnect: (self: Disconnectable) -> (...any);
} | {
disconnect: (self: Disconnectable) -> (...any)
} | ExternScriptConnection
local x: Disconnectable = workspace.ChildAdded:Connect(function()
print("child added")
end)
if type(x.Disconnect) == "function" then
x:Disconnect()
end
)");
if (FFlag::LuauTypeCheckerStricterIndexCheck)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Key 'Disconnect' is missing from 't2 where t1 = ExternScriptConnection | t2 | { Disconnect: (t1) -> (...any) } ; t2 = { disconnect: (t1) -> (...any) }' in the type 't1 where t1 = ExternScriptConnection | { Disconnect: (t1) -> (...any) } | { disconnect: (t1) -> (...any) }'");
if (FFlag::LuauBetterCannotCallFunctionPrimitive)
CHECK_EQ(toString(result.errors[1]), "The type function is not precise enough for us to determine the appropriate result type of this call.");
else
CHECK_EQ(toString(result.errors[1]), "Cannot call a value of type function");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauBetterCannotCallFunctionPrimitive)
CHECK_EQ(toString(result.errors[0]), "The type function is not precise enough for us to determine the appropriate result type of this call.");
else
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type function");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1835")
{
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
local t: {name: string}? = nil
function f()
local name = if t then t.name else "name"
end
)"));
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
local t: {name: string}? = nil
function f()
if t then end
local name = if t then t.name else "name"
end
)"));
CheckResult result = check(R"(
local t: {name: string}? = nil
if t then end
print(t.name)
local name = if t then t.name else "name"
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<OptionalValueAccess>(result.errors[0]));
}
TEST_SUITE_END();

View file

@ -21,8 +21,8 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -30,6 +30,11 @@ LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
TEST_SUITE_BEGIN("TableTests");
@ -594,9 +599,6 @@ TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_assignmen
TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_function_call")
{
// CLI-114873
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
--!strict
function get(x) return x.opts["MYOPT"] end
@ -700,7 +702,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization3)
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization)
CHECK("<a>({a}) -> ()" == toString(requireType("swap")));
else if (FFlag::LuauSolverV2)
CHECK("({unknown}) -> ()" == toString(requireType("swap")));
@ -1153,8 +1155,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_inferred")
TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
type VectorMt = { __add: (Vector, number) -> Vector }
local vectorMt: VectorMt
@ -2210,7 +2210,6 @@ local Test: {Table} = {
TEST_CASE_FIXTURE(Fixture, "common_table_element_general")
{
// CLI-115275 - Bidirectional inference does not always propagate indexer types into the expression
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
@ -2380,7 +2379,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
local c : string = t.m("hi")
)");
if (FFlag::DebugLuauGreedyGeneralization && FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
{
// FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(2, result);
@ -2651,7 +2650,6 @@ Type 'number' could not be converted into 'string' in an invariant context)";
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table")
{
// Table properties like HasSuper.p must be invariant. The new solver rightly rejects this program.
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
@ -2702,9 +2700,6 @@ Table type '{ x: number, y: number }' not compatible with type 'Super' because t
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer")
{
// CLI-114791 Bidirectional inference should be able to cause the inference engine to forget that a table literal has some property
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
--!strict
type Super = { x : number }
@ -3752,7 +3747,12 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap
TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type")
{
ScopedFastFlag sffs[] = {{FFlag::LuauNonReentrantGeneralization3, true}, {FFlag::LuauReportSubtypingErrors, true}};
ScopedFastFlag sff[] = {
{FFlag::LuauReportSubtypingErrors, true},
{FFlag::LuauEagerGeneralization, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}
};
CheckResult result = check(R"(
local function f(s)
@ -4412,7 +4412,7 @@ TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported
CHECK(Location{{5, 18}, {5, 23}} == result.errors[3].location);
}
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_indexers_are_unsupported")
{
CheckResult result = check(R"(
type T = {read [string]: number}
@ -4442,6 +4442,22 @@ TEST_CASE_FIXTURE(Fixture, "infer_write_property")
CHECK("({ y: number }) -> ()" == toString(requireType("f")));
}
TEST_CASE_FIXTURE(Fixture, "new_solver_supports_read_write_properties")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
ScopedFastFlag sff2{FFlag::LuauEnableWriteOnlyProperties, true};
CheckResult result = check(R"(
type W = {read x: number}
type X = {write x: boolean}
type Y = {read ["prop"]: boolean}
type Z = {write ["prop"]: string}
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
{
CheckResult result = check(R"(
@ -4495,6 +4511,59 @@ TEST_CASE_FIXTURE(Fixture, "write_to_read_only_property")
CHECK(PropertyAccessViolation::CannotWrite == pav->context);
}
TEST_CASE_FIXTURE(Fixture, "write_to_write_only_property")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
ScopedFastFlag writeOnly{FFlag::LuauEnableWriteOnlyProperties, true};
CheckResult result = check(R"(
function f(t: {write x: number})
t.x = 5
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "bidirectional_typechecking_with_write_only_property")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
ScopedFastFlag writeOnly{FFlag::LuauEnableWriteOnlyProperties, true};
CheckResult result = check(R"(
function f(t: {write x: number})
t.x = 5
end
f({ x = 2 })
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "read_from_write_only_property")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
ScopedFastFlag writeOnly{FFlag::LuauEnableWriteOnlyProperties, true};
CheckResult result = check(R"(
function f(t: {write x: number})
local foo = t.x
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("Property x of table '{ write x: number }' is write-only" == toString(result.errors[0]));
PropertyAccessViolation* pav = get<PropertyAccessViolation>(result.errors[0]);
REQUIRE(pav);
CHECK("{ write x: number }" == toString(pav->table, {true}));
CHECK("x" == pav->key);
CHECK(PropertyAccessViolation::CannotRead == pav->context);
}
TEST_CASE_FIXTURE(Fixture, "write_to_unusually_named_read_only_property")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
@ -4510,7 +4579,7 @@ TEST_CASE_FIXTURE(Fixture, "write_to_unusually_named_read_only_property")
CHECK("Property \"hello world\" of table '{ read [\"hello world\"]: number }' is read-only" == toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_solver")
TEST_CASE_FIXTURE(Fixture, "write_annotations_are_supported_with_the_new_solver")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
@ -4519,10 +4588,15 @@ TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauEnableWriteOnlyProperties)
LUAU_REQUIRE_NO_ERRORS(result);
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("write keyword is illegal here" == toString(result.errors[0]));
CHECK(Location{{1, 23}, {1, 28}} == result.errors[0].location);
CHECK("write keyword is illegal here" == toString(result.errors[0]));
CHECK(Location{{1, 23}, {1, 28}} == result.errors[0].location);
}
}
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
@ -4549,10 +4623,8 @@ TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported
CHECK(Location{{5, 18}, {5, 23}} == result.errors[3].location);
}
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_indexers_are_unsupported")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
type T = {read [string]: number}
type U = {write [string]: boolean}
@ -4571,7 +4643,11 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff[] = {{FFlag::LuauNonReentrantGeneralization3, true}};
ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}
};
CheckResult result = check(R"(
function oc(player, speaker)
@ -4622,7 +4698,7 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
end
)");
if (FFlag::LuauSolverV2 && !FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization)
{
LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_ERROR(result, NotATable);
@ -4670,7 +4746,7 @@ TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_
LUAU_REQUIRE_NO_ERRORS(result);
// FIXME CLI-114134. We need to simplify types more consistently.
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f")));
else
CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f")));
@ -5212,8 +5288,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "write_only_table_field_duplicate")
}
)");
LUAU_CHECK_ERROR_COUNT(1, result);
CHECK_EQ("write keyword is illegal here", toString(result.errors[0]));
if (FFlag::LuauEnableWriteOnlyProperties)
LUAU_REQUIRE_NO_ERRORS(result);
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("write keyword is illegal here", toString(result.errors[0]));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert")
@ -5661,5 +5742,81 @@ TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_assignment")
CHECK_EQ("boolean", toString(tm->wantedType));
}
TEST_CASE_FIXTURE(Fixture, "disable_singleton_inference_on_large_tables")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck, true},
{FFlag::LuauDisablePrimitiveInferenceInLargeTables, true},
};
ScopedFastInt _{FInt::LuauPrimitiveInferenceInTableLimit, 2};
CheckResult result = check(R"(
type Word = "foo" | "bar"
local words: { Word } = { "foo", "bar", "foo" }
)");
// We expect this to have errors now, as we've set the limit for inference
// in tables so low.
LUAU_REQUIRE_ERROR_COUNT(3, result);
}
TEST_CASE_FIXTURE(Fixture, "disable_singleton_inference_on_large_nested_tables")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck, true},
{FFlag::LuauDisablePrimitiveInferenceInLargeTables, true},
};
ScopedFastInt _{FInt::LuauPrimitiveInferenceInTableLimit, 2};
CheckResult result = check(R"(
type Word = "foo" | "bar"
local words: {{ Word }} = {{ "foo", "bar", "foo" }}
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
}
TEST_CASE_FIXTURE(Fixture, "large_table_inference_does_not_bleed")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck, true},
{FFlag::LuauDisablePrimitiveInferenceInLargeTables, true},
};
ScopedFastInt _{FInt::LuauPrimitiveInferenceInTableLimit, 2};
CheckResult result = check(R"(
type Word = "foo" | "bar"
local words: { Word } = { "foo", "bar", "foo" }
local otherWords: { Word } = {"foo"}
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
for (const auto& err: result.errors)
// Check that all of the errors are localized to `words`, not `otherWords`
CHECK(err.location.begin.line == 2);
}
#if 0
TEST_CASE_FIXTURE(Fixture, "extremely_large_table" * doctest::timeout(2.0))
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDisablePrimitiveInferenceInLargeTables, true},
};
const std::string source =
"local res = {\n" +
rep("\"foo\",\n", 100'000) +
"}";
LUAU_REQUIRE_NO_ERRORS(check(source));
CHECK_EQ("{string}", toString(requireType("res"), {true}));
}
#endif
TEST_SUITE_END();

View file

@ -28,7 +28,7 @@ LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauStringPartLengthLimit)
@ -37,6 +37,8 @@ LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
using namespace Luau;
@ -445,7 +447,7 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
#endif
ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100};
ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100};
ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization3, false};
ScopedFastFlag _{FFlag::LuauEagerGeneralization, false};
CheckResult result = check(R"(("foo"))" + rep(":lower()", limit));
@ -1258,9 +1260,6 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
{
// CLI-114134
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
local obj = {}
@ -2042,8 +2041,10 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert")
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauHasPropProperBlock, true},
{FFlag::LuauNonReentrantGeneralization3, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}
{FFlag::LuauEagerGeneralization, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
};
auto result = check(R"(
@ -2078,8 +2079,10 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauNonReentrantGeneralization3, true},
{FFlag::LuauEagerGeneralization, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
};
CheckResult result = check(R"(
@ -2106,15 +2109,19 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2")
LUAU_REQUIRE_NO_ERROR(result, ConstraintSolvingIncompleteError);
}
#if 0
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauHasPropProperBlock, true},
{FFlag::LuauNonReentrantGeneralization3, true},
{FFlag::LuauEagerGeneralization, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauStringPartLengthLimit, true},
{FFlag::LuauSimplificationRecheckAssumption, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
};
LUAU_REQUIRE_ERRORS(check(R"(
@ -2128,6 +2135,8 @@ local _
)"));
}
#endif
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_missing_follow_table_freeze")
{
ScopedFastFlag _{FFlag::LuauMagicFreezeCheckBlocked2, true};

View file

@ -12,10 +12,11 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauFixEmptyTypePackStringification)
TEST_SUITE_BEGIN("TypePackTests");
@ -99,7 +100,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
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")));
@ -339,7 +340,10 @@ local c: Packed<string, number, boolean>
REQUIRE(ttvA->instantiatedTypeParams.size() == 1);
REQUIRE(ttvA->instantiatedTypePackParams.size() == 1);
CHECK_EQ(toString(ttvA->instantiatedTypeParams[0], {true}), "number");
CHECK_EQ(toString(ttvA->instantiatedTypePackParams[0], {true}), "");
if (FFlag::LuauFixEmptyTypePackStringification)
CHECK_EQ(toString(ttvA->instantiatedTypePackParams[0], {true}), "()");
else
CHECK_EQ(toString(ttvA->instantiatedTypePackParams[0], {true}), "");
auto ttvB = get<TableType>(requireType("b"));
REQUIRE(ttvB);

View file

@ -8,11 +8,12 @@ LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauDfgMatchCGScopes)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
using namespace Luau;
@ -414,7 +415,13 @@ TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions")
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_future_assignments")
{
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauNonReentrantGeneralization3, true}};
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauReportSubtypingErrors, true},
{FFlag::LuauEagerGeneralization, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
};
CheckResult result = check(R"(
local f
@ -851,24 +858,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assign_in_an_if_branch_without_else")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_table_freeze_in_binary_expr")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauPreprocessTypestatedArgument, true},
{FFlag::LuauDfgMatchCGScopes, true},
};
// Previously this would ICE due to mismatched scopes between the
// constraint generator and the data flow graph.
LUAU_REQUIRE_ERRORS(check(R"(
local _
if _ or table.freeze(_,_) or table.freeze(_,_) then
end
)"));
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// CLI-154237: This currently throws an exception due to a mismatch between
// the scopes created in the data flow graph versus the constraint generator.
CHECK_THROWS_AS(
check(R"(
local _
if _ or table.freeze(_,_) or table.freeze(_,_) then
end
)"),
Luau::InternalCompilerError
);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_in_conditional")
{
ScopedFastFlag _{FFlag::LuauDfgMatchCGScopes, true};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// NOTE: This _probably_ should be disallowed, but it is representing that
// type stating functions in short circuiting binary expressions do not
// reflect their type states.
@ -883,21 +888,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_in_conditional")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_table_freeze_in_conditional_expr")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauPreprocessTypestatedArgument, true},
{FFlag::LuauDfgMatchCGScopes, true},
};
// Previously this would ICE due to mismatched scopes between the
// constraint generator and the data flow graph.
LUAU_REQUIRE_ERRORS(check(R"(
--!strict
local _
if
if table.freeze(_,_) then _ else _
then
end
)"));
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// CLI-154237: This currently throws an exception due to a mismatch between
// the scopes created in the data flow graph versus the constraint generator.
CHECK_THROWS_AS(
check(R"(
local _
if
if table.freeze(_,_) then _ else _
then
end
)"),
Luau::InternalCompilerError
);
}
TEST_SUITE_END();

View file

@ -10,7 +10,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
TEST_SUITE_BEGIN("UnionTypes");
@ -902,7 +902,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauGreedyGeneralization)
if (FFlag::LuauEagerGeneralization)
CHECK_EQ(
"<a>(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))
);

View file

@ -8,7 +8,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions);
LUAU_FASTFLAG(DebugLuauGreedyGeneralization);
TEST_SUITE_BEGIN("TypeInferUnknownNever");

View file

@ -17,7 +17,6 @@ using namespace Luau::TypePath;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds);
struct TypePathFixture : Fixture
{

View file

@ -103,6 +103,12 @@ def main():
parser.add_argument(
"path", action="store", help="Path to the Luau.UnitTest executable"
)
parser.add_argument(
"--fflags",
dest="flags",
action="store",
help="Set extra FFlags",
)
parser.add_argument(
"--dump",
dest="dump",
@ -136,9 +142,11 @@ def main():
failList = loadFailList()
flags = ["true", "LuauSolverV2"]
flags = "true,LuauSolverV2"
if args.flags:
flags += "," + args.flags
commandLine = [args.path, "--reporters=xml", "--fflags=" + ",".join(flags)]
commandLine = [args.path, "--reporters=xml", "--fflags=" + flags]
if args.random_seed:
commandLine.append("--random-seed=" + str(args.random_seed))